Index: llvm/lib/Target/SPIRV/CMakeLists.txt =================================================================== --- llvm/lib/Target/SPIRV/CMakeLists.txt +++ llvm/lib/Target/SPIRV/CMakeLists.txt @@ -16,6 +16,7 @@ SPIRVAsmPrinter.cpp SPIRVCallLowering.cpp SPIRVGlobalRegistry.cpp + SPIRVGlobalTypesAndRegNumPass.cpp SPIRVInstrInfo.cpp SPIRVInstructionSelector.cpp SPIRVISelLowering.cpp Index: llvm/lib/Target/SPIRV/MCTargetDesc/SPIRVInstPrinter.cpp =================================================================== --- llvm/lib/Target/SPIRV/MCTargetDesc/SPIRVInstPrinter.cpp +++ llvm/lib/Target/SPIRV/MCTargetDesc/SPIRVInstPrinter.cpp @@ -11,6 +11,8 @@ //===----------------------------------------------------------------------===// #include "SPIRVInstPrinter.h" +#include "SPIRV.h" +#include "SPIRVBaseInfo.h" #include "llvm/CodeGen/Register.h" #include "llvm/MC/MCAsmInfo.h" #include "llvm/MC/MCExpr.h" @@ -62,7 +64,111 @@ void SPIRVInstPrinter::printInst(const MCInst *MI, uint64_t Address, StringRef Annot, const MCSubtargetInfo &STI, raw_ostream &OS) { + const unsigned int OpCode = MI->getOpcode(); printInstruction(MI, Address, OS); + + if (OpCode == SPIRV::OpDecorate) { + printOpDecorate(MI, OS); + } else if (OpCode == SPIRV::OpExtInstImport) { + recordOpExtInstImport(MI); + } else if (OpCode == SPIRV::OpExtInst) { + printOpExtInst(MI, OS); + } else { + // Print any extra operands for variadic instructions + MCInstrDesc MCDesc = MII.get(OpCode); + if (MCDesc.isVariadic()) { + const unsigned NumFixedOps = MCDesc.getNumOperands(); + const unsigned LastFixedIndex = NumFixedOps - 1; + const int FirstVariableIndex = NumFixedOps; + if (NumFixedOps > 0 && + MCDesc.OpInfo[LastFixedIndex].OperandType == MCOI::OPERAND_UNKNOWN) { + // For instructions where a custom type (not reg or immediate) comes as + // the last operand before the variable_ops. This is usually a StringImm + // operand, but there are a few other cases. + switch (OpCode) { + case SPIRV::OpTypeImage: + OS << ' '; + printAccessQualifier(MI, FirstVariableIndex, OS); + break; + case SPIRV::OpVariable: + OS << ' '; + printOperand(MI, FirstVariableIndex, OS); + break; + case SPIRV::OpEntryPoint: { + // Print the interface ID operands, skipping the name's string literal + printRemainingVariableOps(MI, NumFixedOps, OS, false, true); + break; + } + case SPIRV::OpExecutionMode: + case SPIRV::OpExecutionModeId: + case SPIRV::OpLoopMerge: { + // Print any literals after the OPERAND_UNKNOWN argument normally + printRemainingVariableOps(MI, NumFixedOps, OS); + break; + } + default: + break; // printStringImm has already been handled + } + } else { + // For instructions with no fixed ops or a reg/immediate as the final + // fixed operand, we can usually print the rest with "printOperand", but + // check for a few cases with custom types first. + switch (OpCode) { + case SPIRV::OpLoad: + case SPIRV::OpStore: + OS << ' '; + printMemoryOperand(MI, FirstVariableIndex, OS); + printRemainingVariableOps(MI, FirstVariableIndex + 1, OS); + break; + case SPIRV::OpImageSampleImplicitLod: + case SPIRV::OpImageSampleDrefImplicitLod: + case SPIRV::OpImageSampleProjImplicitLod: + case SPIRV::OpImageSampleProjDrefImplicitLod: + case SPIRV::OpImageFetch: + case SPIRV::OpImageGather: + case SPIRV::OpImageDrefGather: + case SPIRV::OpImageRead: + case SPIRV::OpImageWrite: + case SPIRV::OpImageSparseSampleImplicitLod: + case SPIRV::OpImageSparseSampleDrefImplicitLod: + case SPIRV::OpImageSparseSampleProjImplicitLod: + case SPIRV::OpImageSparseSampleProjDrefImplicitLod: + case SPIRV::OpImageSparseFetch: + case SPIRV::OpImageSparseGather: + case SPIRV::OpImageSparseDrefGather: + case SPIRV::OpImageSparseRead: + case SPIRV::OpImageSampleFootprintNV: + OS << ' '; + printImageOperand(MI, FirstVariableIndex, OS); + printRemainingVariableOps(MI, NumFixedOps + 1, OS); + break; + case SPIRV::OpCopyMemory: + case SPIRV::OpCopyMemorySized: { + const unsigned int NumOps = MI->getNumOperands(); + for (unsigned int i = NumFixedOps; i < NumOps; ++i) { + OS << ' '; + printMemoryOperand(MI, i, OS); + if (MI->getOperand(i).getImm() & MemoryOperand::Aligned) { + assert(i + 1 < NumOps && "Missing alignment operand"); + OS << ' '; + printOperand(MI, i + 1, OS); + i += 1; + } + } + break; + } + case SPIRV::OpConstantI: + case SPIRV::OpConstantF: + printOpConstantVarOps(MI, NumFixedOps, OS); + break; + default: + printRemainingVariableOps(MI, NumFixedOps, OS); + break; + } + } + } + } + printAnnotation(OS, Annot); } @@ -71,7 +177,41 @@ } void SPIRVInstPrinter::printOpDecorate(const MCInst *MI, raw_ostream &O) { - llvm_unreachable("Unimplemented printOpDecorate"); + // The fixed operands have already been printed, so just need to decide what + // type of decoration operands to print based on the Decoration type. + MCInstrDesc MCDesc = MII.get(MI->getOpcode()); + unsigned int NumFixedOps = MCDesc.getNumOperands(); + + if (NumFixedOps != MI->getNumOperands()) { + auto DecOp = MI->getOperand(NumFixedOps - 1); + auto Dec = static_cast(DecOp.getImm()); + + O << ' '; + + switch (Dec) { + case Decoration::BuiltIn: + printBuiltIn(MI, NumFixedOps, O); + break; + case Decoration::UniformId: + printScope(MI, NumFixedOps, O); + break; + case Decoration::FuncParamAttr: + printFunctionParameterAttribute(MI, NumFixedOps, O); + break; + case Decoration::FPRoundingMode: + printFPRoundingMode(MI, NumFixedOps, O); + break; + case Decoration::FPFastMathMode: + printFPFastMathMode(MI, NumFixedOps, O); + break; + case Decoration::LinkageAttributes: + printStringImm(MI, NumFixedOps, O); + break; + default: + printRemainingVariableOps(MI, NumFixedOps, O, true); + break; + } + } } static void printExpr(const MCExpr *Expr, raw_ostream &O) { @@ -111,7 +251,34 @@ void SPIRVInstPrinter::printStringImm(const MCInst *MI, unsigned OpNo, raw_ostream &O) { - llvm_unreachable("Unimplemented printStringImm"); + const unsigned NumOps = MI->getNumOperands(); + unsigned StrStartIndex = OpNo; + while (StrStartIndex < NumOps) { + if (MI->getOperand(StrStartIndex).isReg()) + break; + + std::string Str = getSPIRVStringOperand(*MI, OpNo); + if (StrStartIndex != OpNo) + O << ' '; // Add a space if we're starting a new string/argument + O << '"'; + for (char c : Str) { + if (c == '"') + O.write('\\'); // Escape " characters (might break for complex UTF-8) + O.write(c); + } + O << '"'; + + unsigned numOpsInString = (Str.size() / 4) + 1; + StrStartIndex += numOpsInString; + + // Check for final Op of "OpDecorate %x %stringImm %linkageAttribute" + if (MI->getOpcode() == SPIRV::OpDecorate && + MI->getOperand(1).getImm() == Decoration::LinkageAttributes) { + O << ' '; + printLinkageType(MI, StrStartIndex, O); + break; + } + } } void SPIRVInstPrinter::printExtInst(const MCInst *MI, unsigned OpNo, @@ -119,12 +286,17 @@ llvm_unreachable("Unimplemented printExtInst"); } -// Methods for printing textual names of SPIR-V enums +// Implementation of SPIR-V Enum printing using definitions in SPIRVBaseInfo.h #define GEN_INSTR_PRINTER_IMPL(EnumName) \ void SPIRVInstPrinter::print##EnumName(const MCInst *MI, unsigned OpNo, \ raw_ostream &O) { \ - llvm_unreachable("Unimplemented print" #EnumName); \ + if (OpNo < MI->getNumOperands()) { \ + EnumName::EnumName e = \ + static_cast(MI->getOperand(OpNo).getImm()); \ + O << get##EnumName##Name(e); \ + } \ } + GEN_INSTR_PRINTER_IMPL(Capability) GEN_INSTR_PRINTER_IMPL(SourceLanguage) GEN_INSTR_PRINTER_IMPL(ExecutionModel) Index: llvm/lib/Target/SPIRV/SPIRV.h =================================================================== --- llvm/lib/Target/SPIRV/SPIRV.h +++ llvm/lib/Target/SPIRV/SPIRV.h @@ -19,10 +19,14 @@ class SPIRVSubtarget; class InstructionSelector; +ModulePass *createSPIRVGlobalTypesAndRegNumPass(); + InstructionSelector * createSPIRVInstructionSelector(const SPIRVTargetMachine &TM, const SPIRVSubtarget &Subtarget, const SPIRVRegisterBankInfo &RBI); + +void initializeSPIRVGlobalTypesAndRegNumPass(PassRegistry &); } // namespace llvm #endif // LLVM_LIB_TARGET_SPIRV_SPIRV_H Index: llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp =================================================================== --- llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp +++ llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp @@ -17,6 +17,7 @@ #include "SPIRVMCInstLower.h" #include "SPIRVTargetMachine.h" #include "TargetInfo/SPIRVTargetInfo.h" +#include "llvm/ADT/DenseMap.h" #include "llvm/CodeGen/AsmPrinter.h" #include "llvm/CodeGen/MachineConstantPool.h" #include "llvm/CodeGen/MachineFunctionPass.h" @@ -39,8 +40,14 @@ public: explicit SPIRVAsmPrinter(TargetMachine &TM, std::unique_ptr Streamer) - : AsmPrinter(TM, std::move(Streamer)) {} - + : AsmPrinter(TM, std::move(Streamer)) { + const auto *ST = + static_cast(TM).getSubtargetImpl(); + GR = ST->getSPIRVGlobalRegistry(); + TII = ST->getInstrInfo(); + } + SPIRVGlobalRegistry *GR; + const SPIRVInstrInfo *TII; StringRef getPassName() const override { return "SPIRV Assembly Printer"; } void printOperand(const MachineInstr *MI, int OpNum, raw_ostream &O); @@ -52,9 +59,11 @@ void emitFunctionEntryLabel() override {} void emitFunctionHeader() override; void emitFunctionBodyStart() override {} - void emitBasicBlockStart(const MachineBasicBlock &MBB) override {} + void emitFunctionBodyEnd() override; + void emitBasicBlockStart(const MachineBasicBlock &MBB) override; void emitBasicBlockEnd(const MachineBasicBlock &MBB) override {} void emitGlobalVariable(const GlobalVariable *GV) override {} + void emitOpLabel(const MachineBasicBlock &MBB); }; } // namespace @@ -70,6 +79,58 @@ MF->setSection(Section); } +// The table maps MMB number to SPIR-V unique ID register. +static DenseMap BBNumToRegMap; + +// Emit OpFunctionEnd at the end of MF and clear BBNumToRegMap. +void SPIRVAsmPrinter::emitFunctionBodyEnd() { + if (MF == GR->getMetaMF()) + return; + + MCInst FunctionEndInst; + FunctionEndInst.setOpcode(SPIRV::OpFunctionEnd); + EmitToStreamer(*OutStreamer, FunctionEndInst); + + BBNumToRegMap.clear(); +} + +static bool hasMBBRegister(const MachineBasicBlock &MBB) { + return BBNumToRegMap.find(MBB.getNumber()) != BBNumToRegMap.end(); +} + +// Convert MBB's number to correspnding ID register. +Register getOrCreateMBBRegister(const MachineBasicBlock &MBB, + SPIRVGlobalRegistry *GR) { + auto f = BBNumToRegMap.find(MBB.getNumber()); + if (f != BBNumToRegMap.end()) + return f->second; + Register NewReg = Register::index2VirtReg(GR->getNextID()); + BBNumToRegMap[MBB.getNumber()] = NewReg; + return NewReg; +} + +void SPIRVAsmPrinter::emitOpLabel(const MachineBasicBlock &MBB) { + MCInst LabelInst; + LabelInst.setOpcode(SPIRV::OpLabel); + LabelInst.addOperand(MCOperand::createReg(getOrCreateMBBRegister(MBB, GR))); + EmitToStreamer(*OutStreamer, LabelInst); +} + +void SPIRVAsmPrinter::emitBasicBlockStart(const MachineBasicBlock &MBB) { + if (MF == GR->getMetaMF()) + return; + + // If it's the first MBB in MF, it has OpFunction and OpFunctionParameter, so + // OpLabel should be output after them. + if (MBB.getNumber() == MF->front().getNumber()) { + for (const MachineInstr &MI : MBB) + if (MI.getOpcode() == SPIRV::OpFunction) + return; + llvm_unreachable("OpFunction is expected in the front MBB of MF"); + } + emitOpLabel(MBB); +} + void SPIRVAsmPrinter::printOperand(const MachineInstr *MI, int OpNum, raw_ostream &O) { const MachineOperand &MO = MI->getOperand(OpNum); @@ -121,12 +182,31 @@ return false; } +static bool isFuncOrHeaderInstr(const MachineInstr *MI, + const SPIRVInstrInfo *TII) { + return TII->isHeaderInstr(*MI) || MI->getOpcode() == SPIRV::OpFunction || + MI->getOpcode() == SPIRV::OpFunctionParameter; +} + void SPIRVAsmPrinter::emitInstruction(const MachineInstr *MI) { + if (!GR->getSkipEmission(MI)) { + SPIRVMCInstLower MCInstLowering; + MCInst TmpInst; + MCInstLowering.Lower(MI, TmpInst, GR); + EmitToStreamer(*OutStreamer, TmpInst); + } - SPIRVMCInstLower MCInstLowering; - MCInst TmpInst; - MCInstLowering.Lower(MI, TmpInst); - EmitToStreamer(*OutStreamer, TmpInst); + // Output OpLabel after OpFunction and OpFunctionParameter in the first MMB. + if (MF == GR->getMetaMF()) + return; + + const MachineInstr *NextMI = MI->getNextNode(); + if (!hasMBBRegister(*MI->getParent()) && isFuncOrHeaderInstr(MI, TII) && + (!NextMI || !isFuncOrHeaderInstr(NextMI, TII))) { + assert(MI->getParent()->getNumber() == MF->front().getNumber() && + "OpFunction is not in the front MBB of MF"); + emitOpLabel(*MI->getParent()); + } } // Force static initialization. Index: llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.h =================================================================== --- llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.h +++ llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.h @@ -43,7 +43,54 @@ AQ::AccessQualifier accessQual = AQ::ReadWrite, bool EmitIR = true); + const MachineFunction *MetaMF; + + // Maps a local register to the corresponding global aliases in meta-function. + using LocalToGlobalRegTable = std::map; + using RegisterAliasMapTy = + std::map; + + // The table contains global aliases of local registers for each machine + // function. They are collected when local instructions are moved to global + // level on SPIRVGlobalTypesAndRegNumPass. Then the aliases are used + // to substitute global aliases during code emission. + RegisterAliasMapTy RegisterAliasTable; + + // The set contains machine instructions which are necessary for correct MIR + // but will not be finally emitted. + DenseSet InstrsToDelete; + + // The counter holds the maximum ID we have in the module. + unsigned MaxID; + public: + void setMetaMF(const MachineFunction *MF) { MetaMF = MF; } + const MachineFunction *getMetaMF() { return MetaMF; } + + void setRegisterAlias(const MachineFunction *MF, Register Reg, + Register AliasReg) { + RegisterAliasTable[MF][Reg] = AliasReg; + } + + Register getRegisterAlias(const MachineFunction *MF, Register Reg) { + auto RI = RegisterAliasTable[MF].find(Reg); + if (RI == RegisterAliasTable[MF].end()) { + return Register(0); + } + return RegisterAliasTable[MF][Reg]; + } + + bool hasRegisterAlias(const MachineFunction *MF, Register Reg) { + // TODO: add assertion RegisterAliasTable.find(MF) after DT introduction. + return RegisterAliasTable[MF].find(Reg) != RegisterAliasTable[MF].end(); + } + + void setSkipEmission(MachineInstr *MI) { InstrsToDelete.insert(MI); } + + bool getSkipEmission(const MachineInstr *MI) { + return InstrsToDelete.contains(MI); + } + SPIRVGlobalRegistry(unsigned int PointerSize); MachineFunction *CurMF; @@ -90,6 +137,10 @@ return SpirvType->defs().begin()->getReg(); } + unsigned getMaxID() { return MaxID; } + unsigned getNextID() { return MaxID++; } + void setMaxID(unsigned N) { MaxID = N; } + void setCurrentFunc(MachineFunction &MF) { CurMF = &MF; } // Whether the given VReg has an OpTypeXXX instruction mapped to it with the Index: llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.cpp =================================================================== --- llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.cpp +++ llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.cpp @@ -22,7 +22,7 @@ using namespace llvm; SPIRVGlobalRegistry::SPIRVGlobalRegistry(unsigned int PointerSize) - : PointerSize(PointerSize) {} + : PointerSize(PointerSize), MaxID(0) {} SPIRVType * SPIRVGlobalRegistry::assignTypeToVReg(const Type *Type, Register VReg, Index: llvm/lib/Target/SPIRV/SPIRVGlobalTypesAndRegNumPass.cpp =================================================================== --- /dev/null +++ llvm/lib/Target/SPIRV/SPIRVGlobalTypesAndRegNumPass.cpp @@ -0,0 +1,493 @@ +//===-- 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 copies these as necessary to dummy +// function which represents the global scope. The original local instructions +// are left to keep MIR consistent. They will not be emitted to asm/object file. +// +// This pass also re-numbers the registers globally, and saves global aliases of +// local registers to the global registry. AsmPrinter uses these aliases to +// output instructions that refers global registers. +// +//===----------------------------------------------------------------------===// + +#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 + NUM_META_BLOCKS // Total number of sections requiring basic blocks +}; + +static MachineBasicBlock *CurrentMetaMBB = nullptr; +static MachineFunction *MetaMF = nullptr; + +static MachineFunction *getMetaMF() { return MetaMF; } + +static MachineBasicBlock *getMetaMBB(MetaBlockType block) { + return MetaMF->getBlockNumbered(block); +} + +// Set the MBB to one of the sections from the MetaBlockType enum. +static void setMetaBlock(MetaBlockType block) { + CurrentMetaMBB = getMetaMBB(block); +} + +static MachineBasicBlock *getMetaBlock() { return CurrentMetaMBB; } + +static MachineInstrBuilder buildInstrInCurrentMetaMBB(unsigned int Opcode) { + const auto &ST = static_cast(MetaMF->getSubtarget()); + const SPIRVInstrInfo *TII = ST.getInstrInfo(); + return BuildMI(getMetaBlock(), DebugLoc(), TII->get(Opcode)); +} + +// 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_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 */ + +// 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, const SPIRVSubtarget &ST) { + setMetaBlock(MB_MemoryModel); + unsigned PtrSize = ST.getPointerSize(); + + // Add OpMemoryModel + auto Addr = PtrSize == 32 ? AddressingModel::Physical32 + : PtrSize == 64 ? AddressingModel::Physical64 + : AddressingModel::Logical; + auto Mem = MemoryModel::OpenCL; + buildInstrInCurrentMetaMBB(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(MB_DebugSourceAndStrings); + + // Build the OpSource + auto SrcLang = SourceLanguage::OpenCL_C; + buildInstrInCurrentMetaMBB(SPIRV::OpSource) + .addImm(SrcLang) + .addImm(OpenCLVersion); +} + +// Create a new Function and MachineFunction 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 MachineFunction +// can hoist instructions into the right places easily. +static void initMetaFunction(Module &M, MachineModuleInfo &MMI) { + // 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. + LLVMContext &Ctx = M.getContext(); + BasicBlock *BB = BasicBlock::Create(Ctx); + ReturnInst::Create(Ctx, BB); + F->getBasicBlockList().push_back(BB); + M.getFunctionList().push_front(F); + + // Add the necessary basic blocks according to the MetaBlockTypes enum. + MetaMF = &MMI.getOrCreateMachineFunction(*F); + for (unsigned int i = 0; i < NUM_META_BLOCKS; ++i) { + MachineBasicBlock *MetaMBB = MetaMF->CreateMachineBasicBlock(); + MetaMF->push_back(MetaMBB); + } + + // Set initial basic block. + setMetaBlock(MB_TypeConstVars); +} + +// True if there is an instruction in BB with all the same operands as +// the given instruction has (after the given starting index). +// TODO: maybe it needs to check Opcodes too. +static bool findSameInstrInMBB(const MachineInstr &A, MachineBasicBlock *MBB, + SPIRVGlobalRegistry *GR, bool UpdateRegAliases, + unsigned int StartOpIndex = 0) { + for (const auto &B : *MBB) { + 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) { + if (A.getOperand(i).isReg() && B.getOperand(i).isReg()) { + Register RegA = A.getOperand(i).getReg(); + Register RegB = B.getOperand(i).getReg(); + Register AliasRegB = GR->getRegisterAlias(getMetaMF(), RegB); + AllOpsMatch = GR->getRegisterAlias(A.getMF(), RegA) == + (AliasRegB ? AliasRegB : RegB); + } else { + AllOpsMatch = A.getOperand(i).isIdenticalTo(B.getOperand(i)); + } + } + if (AllOpsMatch) { + if (UpdateRegAliases) { + assert(A.getOperand(0).isReg() && B.getOperand(0).isReg()); + Register LocalDefReg = A.getOperand(0).getReg(); + Register MetaDefReg = B.getOperand(0).getReg(); + Register AliasDefReg = GR->getRegisterAlias(getMetaMF(), MetaDefReg); + GR->setRegisterAlias(A.getMF(), LocalDefReg, + AliasDefReg.isValid() ? AliasDefReg + : MetaDefReg); + } + return true; + } + } + } + return false; +} + +// This table maps a globally numbered register to a local register in +// the meta function. +std::map GlobalToLocalMetaReg; +// Get or create a local register in the meta function. +static Register getOrCreateLocalRegInMetafunc(MachineInstr &MI, Register Reg, + bool IsType, + MachineRegisterInfo &MetaMRI, + SPIRVGlobalRegistry *GR) { + + Register GlobalAlias = GR->getRegisterAlias(MI.getMF(), Reg); + if (GlobalToLocalMetaReg.find(GlobalAlias) != GlobalToLocalMetaReg.end()) + return GlobalToLocalMetaReg[GlobalAlias]; + Register LocalReg = MetaMRI.createVirtualRegister( + IsType ? &SPIRV::TYPERegClass : &SPIRV::IDRegClass); + GlobalToLocalMetaReg[GlobalAlias] = LocalReg; + GR->setRegisterAlias(getMetaMF(), LocalReg, GlobalAlias); + return LocalReg; +} + +// Create a copy of the given instruction in the specified basic block of the +// global metadata function. +static void hoistMetaInstrWithGlobalRegs(MachineInstr &MI, + SPIRVGlobalRegistry *GR, + MetaBlockType MbType, + bool IsConst = false, + bool IsType = false) { + GR->setSkipEmission(&MI); + if (findSameInstrInMBB(MI, getMetaMBB(MbType), GR, IsType || IsConst, + (IsType || IsConst) ? 1 : 0)) + return; // Found a duplicate, so don't add it + + // No duplicates, so add it + setMetaBlock(MbType); + MachineRegisterInfo &MetaMRI = getMetaMF()->getRegInfo(); + const unsigned NumOperands = MI.getNumOperands(); + auto MIB = buildInstrInCurrentMetaMBB(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()) { + Register NewLocalReg = + getOrCreateLocalRegInMetafunc(MI, Op.getReg(), IsType, MetaMRI, GR); + if (Op.isDef()) + MIB.addDef(NewLocalReg); + else + MIB.addUse(NewLocalReg); + } else { + errs() << MI; + llvm_unreachable("Unexpected operand type when copying spirv meta instr"); + } + } +} + +static void extractInstructionsWithGlobalRegsToMetablockForMBB( + MachineBasicBlock &MBB, const SPIRVInstrInfo *TII, SPIRVGlobalRegistry *GR, + unsigned &FCounter) { + for (MachineInstr &MI : MBB) { + if (MI.getOpcode() == SPIRV::OpFunction) + FCounter++; + if (GR->getSkipEmission(&MI)) + continue; + const unsigned OpCode = MI.getOpcode(); + if (OpCode == SPIRV::OpName || OpCode == SPIRV::OpMemberName) + hoistMetaInstrWithGlobalRegs(MI, GR, MB_DebugNames); + else if (OpCode == SPIRV::OpEntryPoint) + hoistMetaInstrWithGlobalRegs(MI, GR, MB_EntryPoints); + else if (TII->isDecorationInstr(MI)) + hoistMetaInstrWithGlobalRegs(MI, GR, MB_Annotations); + else if (TII->isConstantInstr(MI) || TII->isTypeDeclInstr(MI) || + // The second and next OpFunctions are decls that should be moved + (FCounter > 1 && (OpCode == SPIRV::OpFunction || + OpCode == SPIRV::OpFunctionParameter))) + hoistMetaInstrWithGlobalRegs(MI, GR, MB_TypeConstVars, + TII->isConstantInstr(MI), + TII->isTypeDeclInstr(MI)); + } +} + +// 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. +static void +extractInstructionsWithGlobalRegsToMetablock(Module &M, MachineModuleInfo &MMI, + const SPIRVInstrInfo *TII, + SPIRVGlobalRegistry *GR) { + unsigned OpFuncCounter; + BEGIN_FOR_MF_IN_MODULE_EXCEPT_FIRST(M, MMI) + for (MachineBasicBlock &MBB : *MF) { + OpFuncCounter = 0; + extractInstructionsWithGlobalRegsToMetablockForMBB(MBB, TII, GR, + OpFuncCounter); + } + END_FOR_MF_IN_MODULE() +} + +static void numberRegistersInMBB(MachineBasicBlock &MBB, + unsigned int &RegBaseIndex, + MachineRegisterInfo &MRI, + SPIRVGlobalRegistry *GR) { + auto *MF = MBB.getParent(); + for (MachineInstr &MI : MBB) { + for (MachineOperand &Op : MI.operands()) { + if (Op.isReg()) { + Register Reg = Op.getReg(); + if (!GR->hasRegisterAlias(MF, Reg)) { + Register NewReg = Register::index2VirtReg(RegBaseIndex); + ++RegBaseIndex; + GR->setRegisterAlias(MF, Reg, NewReg); + } + } + } + } +} + +// Number all registers in all functions globally from 0 onwards and store +// the result in GR's register alias table. +static void numberRegistersGlobally(Module &M, MachineModuleInfo &MMI, + SPIRVGlobalRegistry *GR) { + unsigned int RegBaseIndex = 0; + BEGIN_FOR_MF_IN_MODULE_EXCEPT_FIRST(M, MMI) + for (MachineBasicBlock &MBB : *MF) + numberRegistersInMBB(MBB, RegBaseIndex, MF->getRegInfo(), GR); + END_FOR_MF_IN_MODULE() + GR->setMaxID(RegBaseIndex); +} + +using FuncNameToIDMap = std::map; + +// Iterate over all extracted OpDecorate instructions to look for IDs declared +// with Import linkage, and map the imported name string to the VReg defining +// that variable (which will usually be the result of an OpFunction). +// This lets us call externally imported functions using the correct VReg ID. +static void addExternalDeclarationsToIDMap(FuncNameToIDMap &FuncNameToOpID, + SPIRVGlobalRegistry *GR) { + // Only look for OpDecorates in the global meta function + const MachineBasicBlock *DecMBB = getMetaMBB(MB_Annotations); + for (const auto &MI : *DecMBB) { + if (MI.getOpcode() == SPIRV::OpDecorate) { + // If it's got Import linkage + auto Dec = MI.getOperand(1).getImm(); + if (Dec == Decoration::LinkageAttributes) { + auto Lnk = MI.getOperand(MI.getNumOperands() - 1).getImm(); + if (Lnk == LinkageType::Import) { + // Map imported function name to function ID VReg. + std::string Name = getStringImm(MI, 2); + Register Target = MI.getOperand(0).getReg(); + FuncNameToOpID[Name] = GR->getRegisterAlias(getMetaMF(), Target); + } + } + } + } +} + +// Replace global value for the Callee argument of OpFunctionCall with a +// register number for the function ID now that all results of OpFunction are +// globally numbered registers. +static void assignFunctionCallIDs(Module &M, MachineModuleInfo &MMI, + const SPIRVInstrInfo *TII, + SPIRVGlobalRegistry *GR) { + std::map FuncNameToID; + SmallVector FuncCalls; + + addExternalDeclarationsToIDMap(FuncNameToID, GR); + + // Record all OpFunctionCalls, and all internal OpFunction declarations. + BEGIN_FOR_MF_IN_MODULE_EXCEPT_FIRST(M, MMI) + for (MachineBasicBlock &MBB : *MF) { + for (MachineInstr &MI : MBB) { + if (MI.getOpcode() == SPIRV::OpFunction && !GR->getSkipEmission(&MI)) { + Register Reg = GR->getRegisterAlias(MF, MI.defs().begin()->getReg()); + assert(Reg.isValid()); + FuncNameToID[F->getGlobalIdentifier()] = Reg; + } else if (MI.getOpcode() == SPIRV::OpFunctionCall) { + FuncCalls.push_back(&MI); + } + } + } + END_FOR_MF_IN_MODULE() + + // Replace all OpFunctionCalls with new ones referring to FuncID vregs + for (const auto FuncCall : FuncCalls) { + auto FuncName = FuncCall->getOperand(2).getGlobal()->getGlobalIdentifier(); + auto FuncID = FuncNameToID.find(FuncName); + if (FuncID == FuncNameToID.end()) { + errs() << "Unknown function: " << FuncName << "\n"; + llvm_unreachable("Error: Could not find function id"); + } + const auto MF = FuncCall->getMF(); + + // Create a new copy of the OpFunctionCall but with the IDVReg for the + // callee rather than a GlobalValue, then delete the old instruction. + // Also insert dummy OpFunction which generates local register. + // TODO: move OpFunction creation to SPIRVCallLowering. + Register GlobaFuncReg = FuncID->second; + Register FuncTypeReg = FuncCall->getOperand(1).getReg(); + Register LocalFuncReg = + MF->getRegInfo().createVirtualRegister(&SPIRV::IDRegClass); + DebugLoc DL = FuncCall->getDebugLoc(); + auto MIB = BuildMI(*FuncCall->getParent(), FuncCall, DL, + TII->get(SPIRV::OpFunction)) + .addDef(LocalFuncReg) + .addUse(FuncCall->getOperand(1).getReg()) + .addImm(FunctionControl::None) + .addUse(FuncTypeReg); + GR->setSkipEmission(MIB); + + auto MIB2 = BuildMI(*FuncCall->getParent(), FuncCall, DL, + TII->get(SPIRV::OpFunctionCall)) + .addDef(FuncCall->getOperand(0).getReg()) + .addUse(FuncCall->getOperand(1).getReg()) + .addUse(LocalFuncReg); + const unsigned int NumOps = FuncCall->getNumOperands(); + for (unsigned int i = 3; i < NumOps; ++i) { + MIB2.addUse(FuncCall->getOperand(i).getReg()); + } + GR->setRegisterAlias(MF, LocalFuncReg, GlobaFuncReg); + FuncCall->removeFromParent(); + } +} + +// If a register has no definition in the meta function, insert a dummy def in +// the first MBB. +static void insertDummyDefsInMetafunc(SPIRVGlobalRegistry *GR) { + setMetaBlock(MB_Capabilities); + MachineRegisterInfo &MetaMRI = getMetaMF()->getRegInfo(); + unsigned NumRegs = MetaMRI.getNumVirtRegs(); + for (unsigned int i = 0; i < NumRegs; i++) { + Register MetaReg = Register::index2VirtReg(i); + if (MetaMRI.getVRegDef(MetaReg) == nullptr) { + auto MIB = buildInstrInCurrentMetaMBB(SPIRV::OpUndef) + .addDef(MetaReg) + .addUse(Register::index2VirtReg(0)); + GR->setSkipEmission(MIB); + } + } +} + +bool SPIRVGlobalTypesAndRegNum::runOnModule(Module &M) { + MachineModuleInfoWrapperPass &MMIWrapper = + getAnalysis(); + + initMetaFunction(M, MMIWrapper.getMMI()); + const auto &ST = static_cast(MetaMF->getSubtarget()); + const SPIRVInstrInfo *TII = ST.getInstrInfo(); + SPIRVGlobalRegistry *GR = ST.getSPIRVGlobalRegistry(); + GR->setMetaMF(getMetaMF()); + + addHeaderOps(M, ST); + + // Number registers from 0 onwards, store results in GR tables. + numberRegistersGlobally(M, MMIWrapper.getMMI(), GR); + + // Extract instructions like OpName, OpEntryPoint, OpDecorate etc. to global + // context (i.e. meta function). + extractInstructionsWithGlobalRegsToMetablock(M, MMIWrapper.getMMI(), TII, GR); + + // Set registers corresponding to callees' names in function calls. + assignFunctionCallIDs(M, MMIWrapper.getMMI(), TII, GR); + + // Insert dummy defs to the meta function for registers with no definition. + insertDummyDefsInMetafunc(GR); + + return true; +} + +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/SPIRVMCInstLower.h =================================================================== --- llvm/lib/Target/SPIRV/SPIRVMCInstLower.h +++ llvm/lib/Target/SPIRV/SPIRVMCInstLower.h @@ -14,11 +14,13 @@ namespace llvm { class MCInst; class MachineInstr; +class SPIRVGlobalRegistry; // This class is used to lower a MachineInstr into an MCInst. class LLVM_LIBRARY_VISIBILITY SPIRVMCInstLower { public: - void Lower(const MachineInstr *MI, MCInst &OutMI) const; + void Lower(const MachineInstr *MI, MCInst &OutMI, + SPIRVGlobalRegistry *GR) const; }; } // namespace llvm Index: llvm/lib/Target/SPIRV/SPIRVMCInstLower.cpp =================================================================== --- llvm/lib/Target/SPIRV/SPIRVMCInstLower.cpp +++ llvm/lib/Target/SPIRV/SPIRVMCInstLower.cpp @@ -12,26 +12,37 @@ //===----------------------------------------------------------------------===// #include "SPIRVMCInstLower.h" +#include "SPIRV.h" +#include "SPIRVSubtarget.h" #include "llvm/CodeGen/MachineInstr.h" #include "llvm/IR/Constants.h" using namespace llvm; -void SPIRVMCInstLower::Lower(const MachineInstr *MI, MCInst &OutMI) const { - OutMI.setOpcode(MI->getOpcode()); +// Defined in SPIRVAsmPrinter.cpp +extern Register getOrCreateMBBRegister(const MachineBasicBlock &MBB, + SPIRVGlobalRegistry *GR); +void SPIRVMCInstLower::Lower(const MachineInstr *MI, MCInst &OutMI, + SPIRVGlobalRegistry *GR) const { + OutMI.setOpcode(MI->getOpcode()); + const MachineFunction *MF = MI->getMF(); for (unsigned i = 0, e = MI->getNumOperands(); i != e; ++i) { const MachineOperand &MO = MI->getOperand(i); - // At this stage, SPIR-V should only have Register and Immediate operands MCOperand MCOp; switch (MO.getType()) { default: MI->print(errs()); llvm_unreachable("unknown operand type"); - case MachineOperand::MO_Register: - MCOp = MCOperand::createReg(MO.getReg()); + case MachineOperand::MO_MachineBasicBlock: + MCOp = MCOperand::createReg(getOrCreateMBBRegister(*MO.getMBB(), GR)); break; + case MachineOperand::MO_Register: { + Register NewReg = GR->getRegisterAlias(MF, MO.getReg()); + MCOp = MCOperand::createReg(NewReg.isValid() ? NewReg : MO.getReg()); + break; + } case MachineOperand::MO_Immediate: MCOp = MCOperand::createImm(MO.getImm()); break; Index: llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp =================================================================== --- llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp +++ llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp @@ -45,6 +45,7 @@ PassRegistry &PR = *PassRegistry::getPassRegistry(); initializeGlobalISel(PR); + initializeSPIRVGlobalTypesAndRegNumPass(PR); } static std::string computeDataLayout(const Triple &TT) { @@ -108,6 +109,8 @@ void addOptimizedRegAlloc() override {} void addPostRegAlloc() override; + + void addPreEmitPass2() override; }; } // namespace @@ -149,6 +152,13 @@ void SPIRVPassConfig::addISelPrepare() { TargetPassConfig::addISelPrepare(); } +// Add custom passes right before emitting asm/obj files. +void SPIRVPassConfig::addPreEmitPass2() { + // 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/lib/Target/SPIRV/SPIRVUtils.h =================================================================== --- llvm/lib/Target/SPIRV/SPIRVUtils.h +++ llvm/lib/Target/SPIRV/SPIRVUtils.h @@ -28,6 +28,10 @@ void addStringImm(const llvm::StringRef &Str, llvm::IRBuilder<> &B, std::vector &Args); +// Read the series of integer operands back as a null-terminated string using +// the reverse of the logic in addStringImm. +std::string getStringImm(const llvm::MachineInstr &MI, unsigned int StartIndex); + // Add the given numerical immediate to MIB. void addNumImm(const llvm::APInt &Imm, llvm::MachineInstrBuilder &MIB, bool IsFloat = false); Index: llvm/lib/Target/SPIRV/SPIRVUtils.cpp =================================================================== --- llvm/lib/Target/SPIRV/SPIRVUtils.cpp +++ llvm/lib/Target/SPIRV/SPIRVUtils.cpp @@ -11,6 +11,7 @@ //===----------------------------------------------------------------------===// #include "SPIRVUtils.h" +#include "MCTargetDesc/SPIRVBaseInfo.h" #include "SPIRV.h" using namespace llvm; @@ -55,6 +56,10 @@ } } +std::string getStringImm(const MachineInstr &MI, unsigned int StartIndex) { + return getSPIRVStringOperand(MI, StartIndex); +} + void addNumImm(const APInt &Imm, MachineInstrBuilder &MIB, bool IsFloat) { const auto Bitwidth = Imm.getBitWidth(); switch (Bitwidth) { Index: llvm/test/CodeGen/SPIRV/function/identity-function.ll =================================================================== --- /dev/null +++ llvm/test/CodeGen/SPIRV/function/identity-function.ll @@ -0,0 +1,19 @@ +; RUN: llc -O0 %s -o - | FileCheck %s + +target triple = "spirv32-unknown-unknown" + +; CHECK-DAG: OpName [[VALUE:%.+]] "value" +; CHECK-DAG: OpName [[IDENTITY:%.+]] "identity" + +; CHECK: [[INT:%.+]] = OpTypeInt 32 +; CHECK: [[FN:%.+]] = OpTypeFunction {{%.+}} {{%.+}} + +; CHECK: [[IDENTITY]] = OpFunction {{%.+}} None [[FN]] +; CHECK-NEXT: [[VALUE]] = OpFunctionParameter [[INT]] +; CHECK-NEXT: {{%.+}} = OpLabel +; CHECK-NEXT: OpReturnValue [[VALUE]] +; CHECK-NEXT: OpFunctionEnd + +define i32 @identity(i32 %value) { + ret i32 %value +} 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 %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 %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/function/trivial-function-with-call.ll =================================================================== --- /dev/null +++ llvm/test/CodeGen/SPIRV/function/trivial-function-with-call.ll @@ -0,0 +1,29 @@ +; RUN: llc -O0 %s -o - | FileCheck %s + +target triple = "spirv32-unknown-unknown" + +; Debug info: +; CHECK: OpName [[FOO:%.+]] "foo" +; CHECK: OpName [[BAR:%.+]] "bar" + +; Types: +; CHECK-DAG: [[I32:%.+]] = OpTypeInt 32 +; CHECK-DAG: [[VOID:%.+]] = OpTypeVoid +; CHECK-DAG: [[FNVOID:%.+]] = OpTypeFunction [[VOID]] [[I32]] +; CHECK-DAG: [[FNI32:%.+]] = OpTypeFunction [[I32]] [[I32]] +; Function decl: +; CHECK: [[BAR]] = OpFunction [[I32]] None [[FNI32]] +; CHECK-NEXT: OpFunctionParameter [[I32]] +declare i32 @bar(i32 %x) +; Function def: +; CHECK: [[FOO]] = OpFunction [[VOID]] None [[FNVOID]] +; CHECK: OpFunctionParameter +; CHECK: OpLabel +; CHECK: OpFunctionCall [[I32]] [[BAR]] +; CHECK: OpReturn +; CHECK-NOT: OpLabel +; CHECK: OpFunctionEnd +define spir_func void @foo(i32 %x) { + %call1 = call spir_func i32 @bar(i32 %x) + ret void +} 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-opencl.ll =================================================================== --- /dev/null +++ llvm/test/CodeGen/SPIRV/metadata-opencl.ll @@ -0,0 +1,29 @@ +; RUN: split-file %s %t +; RUN: llc -O0 %t/metadata-opencl12.ll -o - | FileCheck %t/metadata-opencl12.ll +; RUN: llc -O0 %t/metadata-opencl20.ll -o - | FileCheck %t/metadata-opencl20.ll +; RUN: llc -O0 %t/metadata-opencl22.ll -o - | FileCheck %t/metadata-opencl22.ll + +;--- metadata-opencl12.ll +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 +;--- metadata-opencl20.ll +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 +;--- metadata-opencl22.ll +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/transcoding/readonly.ll =================================================================== --- /dev/null +++ llvm/test/CodeGen/SPIRV/transcoding/readonly.ll @@ -0,0 +1,27 @@ +; RUN: llc -O0 %s -o - | FileCheck %s --check-prefix=CHECK-SPIRV + +; CHECK-SPIRV: OpDecorate %[[PARAM:[0-9]+]] FuncParamAttr NoWrite +; CHECK-SPIRV: %[[PARAM]] = OpFunctionParameter %{{.*}} + +; ModuleID = 'readonly.bc' +source_filename = "readonly.cpp" +target datalayout = "e-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-v512:512-v1024:1024-n8:16:32:64" +target triple = "spirv64-unknown-unknown" + +; Function Attrs: norecurse nounwind readonly willreturn +define dso_local spir_kernel void @_ZTSZ4mainE15kernel_function(i32 addrspace(1)* readonly %_arg_) local_unnamed_addr #0 { +entry: + ret void +} + +attributes #0 = { norecurse nounwind } + +!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 13.0.0 (https://github.com/intel/llvm.git)"}