diff --git a/llvm/lib/Target/SPIRV/CMakeLists.txt b/llvm/lib/Target/SPIRV/CMakeLists.txt --- a/llvm/lib/Target/SPIRV/CMakeLists.txt +++ b/llvm/lib/Target/SPIRV/CMakeLists.txt @@ -21,6 +21,7 @@ SPIRVISelLowering.cpp SPIRVLegalizerInfo.cpp SPIRVMCInstLower.cpp + SPIRVModuleAnalysis.cpp SPIRVRegisterBankInfo.cpp SPIRVRegisterInfo.cpp SPIRVSubtarget.cpp diff --git a/llvm/lib/Target/SPIRV/MCTargetDesc/SPIRVInstPrinter.cpp b/llvm/lib/Target/SPIRV/MCTargetDesc/SPIRVInstPrinter.cpp --- a/llvm/lib/Target/SPIRV/MCTargetDesc/SPIRVInstPrinter.cpp +++ b/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" @@ -63,7 +65,113 @@ void SPIRVInstPrinter::printInst(const MCInst *MI, uint64_t Address, StringRef Annot, const MCSubtargetInfo &STI, raw_ostream &OS) { + const unsigned 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 NumOps = MI->getNumOperands(); + for (unsigned i = NumFixedOps; i < NumOps; ++i) { + OS << ' '; + printMemoryOperand(MI, i, OS); + if (MI->getOperand(i).getImm() & + static_cast(SPIRV::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); } @@ -72,7 +180,42 @@ } 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 NumFixedOps = MCDesc.getNumOperands(); + + if (NumFixedOps != MI->getNumOperands()) { + auto DecOp = MI->getOperand(NumFixedOps - 1); + auto Dec = static_cast(DecOp.getImm()); + + O << ' '; + + switch (Dec) { + case SPIRV::Decoration::BuiltIn: + printBuiltIn(MI, NumFixedOps, O); + break; + case SPIRV::Decoration::UniformId: + printScope(MI, NumFixedOps, O); + break; + case SPIRV::Decoration::FuncParamAttr: + printFunctionParameterAttribute(MI, NumFixedOps, O); + break; + case SPIRV::Decoration::FPRoundingMode: + printFPRoundingMode(MI, NumFixedOps, O); + break; + case SPIRV::Decoration::FPFastMathMode: + printFPFastMathMode(MI, NumFixedOps, O); + break; + case SPIRV::Decoration::LinkageAttributes: + case SPIRV::Decoration::UserSemantic: + printStringImm(MI, NumFixedOps, O); + break; + default: + printRemainingVariableOps(MI, NumFixedOps, O, true); + break; + } + } } static void printExpr(const MCExpr *Expr, raw_ostream &O) { @@ -111,7 +254,35 @@ 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() == + static_cast(SPIRV::Decoration::LinkageAttributes)) { + O << ' '; + printLinkageType(MI, StrStartIndex, O); + break; + } + } } void SPIRVInstPrinter::printExtInst(const MCInst *MI, unsigned OpNo, @@ -119,39 +290,267 @@ llvm_unreachable("Unimplemented printExtInst"); } -// Methods for printing textual names of SPIR-V enums. -#define GEN_INSTR_PRINTER_IMPL(EnumName) \ - void SPIRVInstPrinter::print##EnumName(const MCInst *MI, unsigned OpNo, \ - raw_ostream &O) { \ - llvm_unreachable("Unimplemented print" #EnumName); \ - } -GEN_INSTR_PRINTER_IMPL(Capability) -GEN_INSTR_PRINTER_IMPL(SourceLanguage) -GEN_INSTR_PRINTER_IMPL(ExecutionModel) -GEN_INSTR_PRINTER_IMPL(AddressingModel) -GEN_INSTR_PRINTER_IMPL(MemoryModel) -GEN_INSTR_PRINTER_IMPL(ExecutionMode) -GEN_INSTR_PRINTER_IMPL(StorageClass) -GEN_INSTR_PRINTER_IMPL(Dim) -GEN_INSTR_PRINTER_IMPL(SamplerAddressingMode) -GEN_INSTR_PRINTER_IMPL(SamplerFilterMode) -GEN_INSTR_PRINTER_IMPL(ImageFormat) -GEN_INSTR_PRINTER_IMPL(ImageChannelOrder) -GEN_INSTR_PRINTER_IMPL(ImageChannelDataType) -GEN_INSTR_PRINTER_IMPL(ImageOperand) -GEN_INSTR_PRINTER_IMPL(FPFastMathMode) -GEN_INSTR_PRINTER_IMPL(FPRoundingMode) -GEN_INSTR_PRINTER_IMPL(LinkageType) -GEN_INSTR_PRINTER_IMPL(AccessQualifier) -GEN_INSTR_PRINTER_IMPL(FunctionParameterAttribute) -GEN_INSTR_PRINTER_IMPL(Decoration) -GEN_INSTR_PRINTER_IMPL(BuiltIn) -GEN_INSTR_PRINTER_IMPL(SelectionControl) -GEN_INSTR_PRINTER_IMPL(LoopControl) -GEN_INSTR_PRINTER_IMPL(FunctionControl) -GEN_INSTR_PRINTER_IMPL(MemorySemantics) -GEN_INSTR_PRINTER_IMPL(MemoryOperand) -GEN_INSTR_PRINTER_IMPL(Scope) -GEN_INSTR_PRINTER_IMPL(GroupOperation) -GEN_INSTR_PRINTER_IMPL(KernelEnqueueFlags) -GEN_INSTR_PRINTER_IMPL(KernelProfilingInfo) +void SPIRVInstPrinter::printCapability(const MCInst *MI, unsigned OpNo, + raw_ostream &O) { + if (OpNo < MI->getNumOperands()) { + SPIRV::Capability e = + static_cast(MI->getOperand(OpNo).getImm()); + O << SPIRV::getCapabilityName(e); + } +} + +void SPIRVInstPrinter::printSourceLanguage(const MCInst *MI, unsigned OpNo, + raw_ostream &O) { + if (OpNo < MI->getNumOperands()) { + SPIRV::SourceLanguage e = + static_cast(MI->getOperand(OpNo).getImm()); + O << SPIRV::getSourceLanguageName(e); + } +} + +void SPIRVInstPrinter::printExecutionModel(const MCInst *MI, unsigned OpNo, + raw_ostream &O) { + if (OpNo < MI->getNumOperands()) { + SPIRV::ExecutionModel e = + static_cast(MI->getOperand(OpNo).getImm()); + O << SPIRV::getExecutionModelName(e); + } +} + +void SPIRVInstPrinter::printAddressingModel(const MCInst *MI, unsigned OpNo, + raw_ostream &O) { + if (OpNo < MI->getNumOperands()) { + SPIRV::AddressingModel e = + static_cast(MI->getOperand(OpNo).getImm()); + O << SPIRV::getAddressingModelName(e); + } +} + +void SPIRVInstPrinter::printMemoryModel(const MCInst *MI, unsigned OpNo, + raw_ostream &O) { + if (OpNo < MI->getNumOperands()) { + SPIRV::MemoryModel e = + static_cast(MI->getOperand(OpNo).getImm()); + O << SPIRV::getMemoryModelName(e); + } +} + +void SPIRVInstPrinter::printExecutionMode(const MCInst *MI, unsigned OpNo, + raw_ostream &O) { + if (OpNo < MI->getNumOperands()) { + SPIRV::ExecutionMode e = + static_cast(MI->getOperand(OpNo).getImm()); + O << SPIRV::getExecutionModeName(e); + } +} + +void SPIRVInstPrinter::printStorageClass(const MCInst *MI, unsigned OpNo, + raw_ostream &O) { + if (OpNo < MI->getNumOperands()) { + SPIRV::StorageClass e = + static_cast(MI->getOperand(OpNo).getImm()); + O << SPIRV::getStorageClassName(e); + } +} + +void SPIRVInstPrinter::printDim(const MCInst *MI, unsigned OpNo, + raw_ostream &O) { + if (OpNo < MI->getNumOperands()) { + SPIRV::Dim e = static_cast(MI->getOperand(OpNo).getImm()); + O << SPIRV::getDimName(e); + } +} + +void SPIRVInstPrinter::printSamplerAddressingMode(const MCInst *MI, + unsigned OpNo, + raw_ostream &O) { + if (OpNo < MI->getNumOperands()) { + SPIRV::SamplerAddressingMode e = static_cast( + MI->getOperand(OpNo).getImm()); + O << SPIRV::getSamplerAddressingModeName(e); + } +} + +void SPIRVInstPrinter::printSamplerFilterMode(const MCInst *MI, unsigned OpNo, + raw_ostream &O) { + if (OpNo < MI->getNumOperands()) { + SPIRV::SamplerFilterMode e = + static_cast(MI->getOperand(OpNo).getImm()); + O << SPIRV::getSamplerFilterModeName(e); + } +} + +void SPIRVInstPrinter::printImageFormat(const MCInst *MI, unsigned OpNo, + raw_ostream &O) { + if (OpNo < MI->getNumOperands()) { + SPIRV::ImageFormat e = + static_cast(MI->getOperand(OpNo).getImm()); + O << SPIRV::getImageFormatName(e); + } +} + +void SPIRVInstPrinter::printImageChannelOrder(const MCInst *MI, unsigned OpNo, + raw_ostream &O) { + if (OpNo < MI->getNumOperands()) { + SPIRV::ImageChannelOrder e = + static_cast(MI->getOperand(OpNo).getImm()); + O << SPIRV::getImageChannelOrderName(e); + } +} + +void SPIRVInstPrinter::printImageChannelDataType(const MCInst *MI, + unsigned OpNo, + raw_ostream &O) { + if (OpNo < MI->getNumOperands()) { + SPIRV::ImageChannelDataType e = + static_cast(MI->getOperand(OpNo).getImm()); + O << SPIRV::getImageChannelDataTypeName(e); + } +} + +void SPIRVInstPrinter::printImageOperand(const MCInst *MI, unsigned OpNo, + raw_ostream &O) { + if (OpNo < MI->getNumOperands()) { + unsigned e = static_cast(MI->getOperand(OpNo).getImm()); + O << SPIRV::getImageOperandName(e); + } +} + +void SPIRVInstPrinter::printFPFastMathMode(const MCInst *MI, unsigned OpNo, + raw_ostream &O) { + if (OpNo < MI->getNumOperands()) { + unsigned e = static_cast(MI->getOperand(OpNo).getImm()); + O << SPIRV::getFPFastMathModeName(e); + } +} + +void SPIRVInstPrinter::printFPRoundingMode(const MCInst *MI, unsigned OpNo, + raw_ostream &O) { + if (OpNo < MI->getNumOperands()) { + SPIRV::FPRoundingMode e = + static_cast(MI->getOperand(OpNo).getImm()); + O << SPIRV::getFPRoundingModeName(e); + } +} + +void SPIRVInstPrinter::printLinkageType(const MCInst *MI, unsigned OpNo, + raw_ostream &O) { + if (OpNo < MI->getNumOperands()) { + SPIRV::LinkageType e = + static_cast(MI->getOperand(OpNo).getImm()); + O << SPIRV::getLinkageTypeName(e); + } +} + +void SPIRVInstPrinter::printAccessQualifier(const MCInst *MI, unsigned OpNo, + raw_ostream &O) { + if (OpNo < MI->getNumOperands()) { + SPIRV::AccessQualifier e = + static_cast(MI->getOperand(OpNo).getImm()); + O << SPIRV::getAccessQualifierName(e); + } +} + +void SPIRVInstPrinter::printFunctionParameterAttribute(const MCInst *MI, + unsigned OpNo, + raw_ostream &O) { + if (OpNo < MI->getNumOperands()) { + SPIRV::FunctionParameterAttribute e = + static_cast( + MI->getOperand(OpNo).getImm()); + O << SPIRV::getFunctionParameterAttributeName(e); + } +} + +void SPIRVInstPrinter::printDecoration(const MCInst *MI, unsigned OpNo, + raw_ostream &O) { + if (OpNo < MI->getNumOperands()) { + SPIRV::Decoration e = + static_cast(MI->getOperand(OpNo).getImm()); + O << SPIRV::getDecorationName(e); + } +} + +void SPIRVInstPrinter::printBuiltIn(const MCInst *MI, unsigned OpNo, + raw_ostream &O) { + if (OpNo < MI->getNumOperands()) { + SPIRV::BuiltIn e = + static_cast(MI->getOperand(OpNo).getImm()); + O << SPIRV::getBuiltInName(e); + } +} + +void SPIRVInstPrinter::printSelectionControl(const MCInst *MI, unsigned OpNo, + raw_ostream &O) { + if (OpNo < MI->getNumOperands()) { + unsigned e = static_cast(MI->getOperand(OpNo).getImm()); + O << SPIRV::getSelectionControlName(e); + } +} + +void SPIRVInstPrinter::printLoopControl(const MCInst *MI, unsigned OpNo, + raw_ostream &O) { + if (OpNo < MI->getNumOperands()) { + unsigned e = static_cast(MI->getOperand(OpNo).getImm()); + O << SPIRV::getLoopControlName(e); + } +} + +void SPIRVInstPrinter::printFunctionControl(const MCInst *MI, unsigned OpNo, + raw_ostream &O) { + if (OpNo < MI->getNumOperands()) { + unsigned e = static_cast(MI->getOperand(OpNo).getImm()); + O << SPIRV::getFunctionControlName(e); + } +} + +void SPIRVInstPrinter::printMemorySemantics(const MCInst *MI, unsigned OpNo, + raw_ostream &O) { + if (OpNo < MI->getNumOperands()) { + unsigned e = static_cast(MI->getOperand(OpNo).getImm()); + O << SPIRV::getMemorySemanticsName(e); + } +} + +void SPIRVInstPrinter::printMemoryOperand(const MCInst *MI, unsigned OpNo, + raw_ostream &O) { + if (OpNo < MI->getNumOperands()) { + unsigned e = static_cast(MI->getOperand(OpNo).getImm()); + O << SPIRV::getMemoryOperandName(e); + } +} + +void SPIRVInstPrinter::printScope(const MCInst *MI, unsigned OpNo, + raw_ostream &O) { + if (OpNo < MI->getNumOperands()) { + SPIRV::Scope e = static_cast(MI->getOperand(OpNo).getImm()); + O << SPIRV::getScopeName(e); + } +} + +void SPIRVInstPrinter::printGroupOperation(const MCInst *MI, unsigned OpNo, + raw_ostream &O) { + if (OpNo < MI->getNumOperands()) { + SPIRV::GroupOperation e = + static_cast(MI->getOperand(OpNo).getImm()); + O << SPIRV::getGroupOperationName(e); + } +} + +void SPIRVInstPrinter::printKernelEnqueueFlags(const MCInst *MI, unsigned OpNo, + raw_ostream &O) { + if (OpNo < MI->getNumOperands()) { + SPIRV::KernelEnqueueFlags e = + static_cast(MI->getOperand(OpNo).getImm()); + O << SPIRV::getKernelEnqueueFlagsName(e); + } +} + +void SPIRVInstPrinter::printKernelProfilingInfo(const MCInst *MI, unsigned OpNo, + raw_ostream &O) { + if (OpNo < MI->getNumOperands()) { + SPIRV::KernelProfilingInfo e = + static_cast(MI->getOperand(OpNo).getImm()); + O << SPIRV::getKernelProfilingInfoName(e); + } +} diff --git a/llvm/lib/Target/SPIRV/SPIRV.h b/llvm/lib/Target/SPIRV/SPIRV.h --- a/llvm/lib/Target/SPIRV/SPIRV.h +++ b/llvm/lib/Target/SPIRV/SPIRV.h @@ -23,6 +23,8 @@ createSPIRVInstructionSelector(const SPIRVTargetMachine &TM, const SPIRVSubtarget &Subtarget, const RegisterBankInfo &RBI); + +void initializeSPIRVModuleAnalysisPass(PassRegistry &); } // namespace llvm #endif // LLVM_LIB_TARGET_SPIRV_SPIRV_H 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 @@ -15,8 +15,12 @@ #include "SPIRV.h" #include "SPIRVInstrInfo.h" #include "SPIRVMCInstLower.h" +#include "SPIRVModuleAnalysis.h" +#include "SPIRVSubtarget.h" #include "SPIRVTargetMachine.h" +#include "SPIRVUtils.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" @@ -29,36 +33,75 @@ #include "llvm/MC/MCSymbol.h" #include "llvm/MC/TargetRegistry.h" #include "llvm/Support/raw_ostream.h" + using namespace llvm; #define DEBUG_TYPE "asm-printer" namespace { class SPIRVAsmPrinter : public AsmPrinter { - public: explicit SPIRVAsmPrinter(TargetMachine &TM, std::unique_ptr Streamer) - : AsmPrinter(TM, std::move(Streamer)) {} + : AsmPrinter(TM, std::move(Streamer)), ST(nullptr), TII(nullptr) {} + bool ModuleSectionsEmitted; + const SPIRVSubtarget *ST; + const SPIRVInstrInfo *TII; StringRef getPassName() const override { return "SPIRV Assembly Printer"; } void printOperand(const MachineInstr *MI, int OpNum, raw_ostream &O); - bool PrintAsmOperand(const MachineInstr *MI, unsigned OpNo, const char *ExtraCode, raw_ostream &O) override; - void emitInstruction(const MachineInstr *MI) override; + void outputMCInst(MCInst &Inst); + void outputInstruction(const MachineInstr *MI); + void outputModuleSection(SPIRV::ModuleSectionType MSType); + void outputEntryPoints(); + void outputDebugSourceAndStrings(const Module &M); + void outputOpMemoryModel(); + void outputOpFunctionEnd(); + void outputExtFuncDecls(); + void outputModuleSections(); + void emitInstruction(const MachineInstr *MI) override; 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); + void emitEndOfAsmFile(Module &M) override; + bool doInitialization(Module &M) override; + + void getAnalysisUsage(AnalysisUsage &AU) const override; + SPIRV::ModuleAnalysisInfo *MAI; }; } // namespace +void SPIRVAsmPrinter::getAnalysisUsage(AnalysisUsage &AU) const { + AU.addRequired(); + AU.addPreserved(); + AsmPrinter::getAnalysisUsage(AU); +} + +// If the module has no functions, we need output global info anyway. +void SPIRVAsmPrinter::emitEndOfAsmFile(Module &M) { + if (ModuleSectionsEmitted == false) { + outputModuleSections(); + ModuleSectionsEmitted = true; + } +} + void SPIRVAsmPrinter::emitFunctionHeader() { + if (ModuleSectionsEmitted == false) { + outputModuleSections(); + ModuleSectionsEmitted = true; + } + // Get the subtarget from the current MachineFunction. + ST = &MF->getSubtarget(); + TII = ST->getInstrInfo(); const Function &F = MF->getFunction(); if (isVerbose()) { @@ -71,6 +114,38 @@ MF->setSection(Section); } +void SPIRVAsmPrinter::outputOpFunctionEnd() { + MCInst FunctionEndInst; + FunctionEndInst.setOpcode(SPIRV::OpFunctionEnd); + outputMCInst(FunctionEndInst); +} + +// Emit OpFunctionEnd at the end of MF and clear BBNumToRegMap. +void SPIRVAsmPrinter::emitFunctionBodyEnd() { + outputOpFunctionEnd(); + MAI->BBNumToRegMap.clear(); +} + +void SPIRVAsmPrinter::emitOpLabel(const MachineBasicBlock &MBB) { + MCInst LabelInst; + LabelInst.setOpcode(SPIRV::OpLabel); + LabelInst.addOperand(MCOperand::createReg(MAI->getOrCreateMBBRegister(MBB))); + outputMCInst(LabelInst); +} + +void SPIRVAsmPrinter::emitBasicBlockStart(const MachineBasicBlock &MBB) { + // 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; + // TODO: this case should be checked by the verifier. + report_fatal_error("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); @@ -122,12 +197,148 @@ return false; } -void SPIRVAsmPrinter::emitInstruction(const MachineInstr *MI) { +static bool isFuncOrHeaderInstr(const MachineInstr *MI, + const SPIRVInstrInfo *TII) { + return TII->isHeaderInstr(*MI) || MI->getOpcode() == SPIRV::OpFunction || + MI->getOpcode() == SPIRV::OpFunctionParameter; +} + +void SPIRVAsmPrinter::outputMCInst(MCInst &Inst) { + OutStreamer->emitInstruction(Inst, *OutContext.getSubtargetInfo()); +} +void SPIRVAsmPrinter::outputInstruction(const MachineInstr *MI) { SPIRVMCInstLower MCInstLowering; MCInst TmpInst; - MCInstLowering.lower(MI, TmpInst); - EmitToStreamer(*OutStreamer, TmpInst); + MCInstLowering.lower(MI, TmpInst, MAI); + outputMCInst(TmpInst); +} + +void SPIRVAsmPrinter::emitInstruction(const MachineInstr *MI) { + if (!MAI->getSkipEmission(MI)) + outputInstruction(MI); + + // Output OpLabel after OpFunction and OpFunctionParameter in the first MBB. + const MachineInstr *NextMI = MI->getNextNode(); + if (!MAI->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()); + } +} + +void SPIRVAsmPrinter::outputModuleSection(SPIRV::ModuleSectionType MSType) { + for (MachineInstr *MI : MAI->getMSInstrs(MSType)) + outputInstruction(MI); +} + +void SPIRVAsmPrinter::outputDebugSourceAndStrings(const Module &M) { + // Output OpSource. + MCInst Inst; + Inst.setOpcode(SPIRV::OpSource); + Inst.addOperand(MCOperand::createImm(static_cast(MAI->SrcLang))); + Inst.addOperand( + MCOperand::createImm(static_cast(MAI->SrcLangVersion))); + outputMCInst(Inst); +} + +void SPIRVAsmPrinter::outputOpMemoryModel() { + MCInst Inst; + Inst.setOpcode(SPIRV::OpMemoryModel); + Inst.addOperand(MCOperand::createImm(static_cast(MAI->Addr))); + Inst.addOperand(MCOperand::createImm(static_cast(MAI->Mem))); + outputMCInst(Inst); +} + +// Before the OpEntryPoints' output, we need to add the entry point's +// interfaces. The interface is a list of IDs of global OpVariable instructions. +// These declare the set of global variables from a module that form +// the interface of this entry point. +void SPIRVAsmPrinter::outputEntryPoints() { + // Find all OpVariable IDs with required StorageClass. + DenseSet InterfaceIDs; + for (MachineInstr *MI : MAI->GlobalVarList) { + assert(MI->getOpcode() == SPIRV::OpVariable); + auto SC = static_cast(MI->getOperand(2).getImm()); + // Before version 1.4, the interface's storage classes are limited to + // the Input and Output storage classes. Starting with version 1.4, + // the interface's storage classes are all storage classes used in + // declaring all global variables referenced by the entry point call tree. + if (ST->getSPIRVVersion() >= 14 || SC == SPIRV::StorageClass::Input || + SC == SPIRV::StorageClass::Output) { + MachineFunction *MF = MI->getMF(); + Register Reg = MAI->getRegisterAlias(MF, MI->getOperand(0).getReg()); + InterfaceIDs.insert(Reg); + } + } + + // Output OpEntryPoints adding interface args to all of them. + for (MachineInstr *MI : MAI->getMSInstrs(SPIRV::MB_EntryPoints)) { + SPIRVMCInstLower MCInstLowering; + MCInst TmpInst; + MCInstLowering.lower(MI, TmpInst, MAI); + for (Register Reg : InterfaceIDs) { + assert(Reg.isValid()); + TmpInst.addOperand(MCOperand::createReg(Reg)); + } + outputMCInst(TmpInst); + } +} + +void SPIRVAsmPrinter::outputExtFuncDecls() { + // Insert OpFunctionEnd after each declaration. + SmallVectorImpl::iterator + I = MAI->getMSInstrs(SPIRV::MB_ExtFuncDecls).begin(), + E = MAI->getMSInstrs(SPIRV::MB_ExtFuncDecls).end(); + for (; I != E; ++I) { + outputInstruction(*I); + if ((I + 1) == E || (*(I + 1))->getOpcode() == SPIRV::OpFunction) + outputOpFunctionEnd(); + } +} + +void SPIRVAsmPrinter::outputModuleSections() { + const Module *M = MMI->getModule(); + // Get the global subtarget to output module-level info. + ST = static_cast(TM).getSubtargetImpl(); + TII = ST->getInstrInfo(); + MAI = &SPIRVModuleAnalysis::MAI; + assert(ST && TII && MAI && M && "Module analysis is required"); + // Output instructions according to the Logical Layout of a Module: + // TODO: 1,2. All OpCapability instructions, then optional OpExtension + // instructions. + // TODO: 3. Optional OpExtInstImport instructions. + // 4. The single required OpMemoryModel instruction. + outputOpMemoryModel(); + // 5. All entry point declarations, using OpEntryPoint. + outputEntryPoints(); + // 6. Execution-mode declarations, using OpExecutionMode or OpExecutionModeId. + // TODO: + // 7a. Debug: all OpString, OpSourceExtension, OpSource, and + // OpSourceContinued, without forward references. + outputDebugSourceAndStrings(*M); + // 7b. Debug: all OpName and all OpMemberName. + outputModuleSection(SPIRV::MB_DebugNames); + // 7c. Debug: all OpModuleProcessed instructions. + outputModuleSection(SPIRV::MB_DebugModuleProcessed); + // 8. All annotation instructions (all decorations). + outputModuleSection(SPIRV::MB_Annotations); + // 9. All type declarations (OpTypeXXX instructions), all constant + // instructions, and all global variable declarations. This section is + // the first section to allow use of: OpLine and OpNoLine debug information; + // non-semantic instructions with OpExtInst. + outputModuleSection(SPIRV::MB_TypeConstVars); + // 10. All function declarations (functions without a body). + outputExtFuncDecls(); + // 11. All function definitions (functions with a body). + // This is done in regular function output. +} + +bool SPIRVAsmPrinter::doInitialization(Module &M) { + ModuleSectionsEmitted = false; + // We need to call the parent's one explicitly. + return AsmPrinter::doInitialization(M); } // Force static initialization. diff --git a/llvm/lib/Target/SPIRV/SPIRVMCInstLower.h b/llvm/lib/Target/SPIRV/SPIRVMCInstLower.h --- a/llvm/lib/Target/SPIRV/SPIRVMCInstLower.h +++ b/llvm/lib/Target/SPIRV/SPIRVMCInstLower.h @@ -14,11 +14,15 @@ namespace llvm { class MCInst; class MachineInstr; +namespace SPIRV { +class ModuleAnalysisInfo; +} // namespace SPIRV // 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, + SPIRV::ModuleAnalysisInfo *MAI) const; }; } // namespace llvm diff --git a/llvm/lib/Target/SPIRV/SPIRVMCInstLower.cpp b/llvm/lib/Target/SPIRV/SPIRVMCInstLower.cpp --- a/llvm/lib/Target/SPIRV/SPIRVMCInstLower.cpp +++ b/llvm/lib/Target/SPIRV/SPIRVMCInstLower.cpp @@ -12,24 +12,38 @@ //===----------------------------------------------------------------------===// #include "SPIRVMCInstLower.h" +#include "SPIRV.h" +#include "SPIRVModuleAnalysis.h" +#include "SPIRVUtils.h" #include "llvm/CodeGen/MachineInstr.h" #include "llvm/IR/Constants.h" using namespace llvm; -void SPIRVMCInstLower::lower(const MachineInstr *MI, MCInst &OutMI) const { +void SPIRVMCInstLower::lower(const MachineInstr *MI, MCInst &OutMI, + SPIRV::ModuleAnalysisInfo *MAI) 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); - MCOperand MCOp; switch (MO.getType()) { default: llvm_unreachable("unknown operand type"); - case MachineOperand::MO_Register: - MCOp = MCOperand::createReg(MO.getReg()); + case MachineOperand::MO_GlobalAddress: { + Register FuncReg = MAI->getFuncReg(MO.getGlobal()->getGlobalIdentifier()); + assert(FuncReg.isValid() && "Cannot find function Id"); + MCOp = MCOperand::createReg(FuncReg); + break; + } + case MachineOperand::MO_MachineBasicBlock: + MCOp = MCOperand::createReg(MAI->getOrCreateMBBRegister(*MO.getMBB())); + break; + case MachineOperand::MO_Register: { + Register NewReg = MAI->getRegisterAlias(MF, MO.getReg()); + MCOp = MCOperand::createReg(NewReg.isValid() ? NewReg : MO.getReg()); break; + } case MachineOperand::MO_Immediate: MCOp = MCOperand::createImm(MO.getImm()); break; diff --git a/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.h b/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.h new file mode 100644 --- /dev/null +++ b/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.h @@ -0,0 +1,137 @@ +//===- SPIRVModuleAnalysis.h - analysis of global instrs & regs -*- 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 +// +//===----------------------------------------------------------------------===// +// +// The analysis collects instructions that should be output at the module level +// and performs the global register numbering. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIB_TARGET_SPIRV_SPIRVMODULEANALYSIS_H +#define LLVM_LIB_TARGET_SPIRV_SPIRVMODULEANALYSIS_H + +#include "MCTargetDesc/SPIRVBaseInfo.h" +#include "SPIRVSubtarget.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringMap.h" + +namespace llvm { +class MachineFunction; +class MachineModuleInfo; + +namespace SPIRV { +// The enum contains logical module sections for the instruction collection. +enum ModuleSectionType { + // MB_Capabilities, MB_Extensions, MB_ExtInstImports, MB_MemoryModel, + MB_EntryPoints, // All OpEntryPoint instructions (if any). + // MB_ExecutionModes, MB_DebugSourceAndStrings, + MB_DebugNames, // All OpName and OpMemberName 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_MODULE_SECTIONS // Total number of sections requiring basic blocks. +}; + +using InstrList = SmallVector; +// Maps a local register to the corresponding global alias. +using LocalToGlobalRegTable = std::map; +using RegisterAliasMapTy = + std::map; + +// The struct contains results of the module analysis and methods +// to access them. +struct ModuleAnalysisInfo { + SPIRV::MemoryModel Mem; + SPIRV::AddressingModel Addr; + SPIRV::SourceLanguage SrcLang; + unsigned SrcLangVersion; + // Contains the list of all global OpVariables in the module. + SmallVector GlobalVarList; + // Maps function names to coresponding function ID registers. + StringMap FuncNameMap; + // The set contains machine instructions which are necessary + // for correct MIR but will not be emitted in function bodies. + DenseSet InstrsToDelete; + // The table contains global aliases of local registers for each machine + // function. The aliases are used to substitute local registers during + // code emission. + RegisterAliasMapTy RegisterAliasTable; + // The counter holds the maximum ID we have in the module. + unsigned MaxID; + // The array contains lists of MIs for each module section. + InstrList MS[NUM_MODULE_SECTIONS]; + // The table maps MBB number to SPIR-V unique ID register. + DenseMap BBNumToRegMap; + + Register getFuncReg(std::string FuncName) { + auto FuncReg = FuncNameMap.find(FuncName); + assert(FuncReg != FuncNameMap.end() && "Cannot find function Id"); + return FuncReg->second; + } + InstrList &getMSInstrs(unsigned MSType) { return MS[MSType]; } + void setSkipEmission(MachineInstr *MI) { InstrsToDelete.insert(MI); } + bool getSkipEmission(const MachineInstr *MI) { + return InstrsToDelete.contains(MI); + } + 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) { + return RegisterAliasTable.find(MF) != RegisterAliasTable.end() && + RegisterAliasTable[MF].find(Reg) != RegisterAliasTable[MF].end(); + } + unsigned getNextID() { return MaxID++; } + bool hasMBBRegister(const MachineBasicBlock &MBB) { + return BBNumToRegMap.find(MBB.getNumber()) != BBNumToRegMap.end(); + } + // Convert MBB's number to corresponding ID register. + Register getOrCreateMBBRegister(const MachineBasicBlock &MBB) { + auto f = BBNumToRegMap.find(MBB.getNumber()); + if (f != BBNumToRegMap.end()) + return f->second; + Register NewReg = Register::index2VirtReg(getNextID()); + BBNumToRegMap[MBB.getNumber()] = NewReg; + return NewReg; + } +}; +} // namespace SPIRV + +struct SPIRVModuleAnalysis : public ModulePass { + static char ID; + +public: + SPIRVModuleAnalysis() : ModulePass(ID) {} + + bool runOnModule(Module &M) override; + void getAnalysisUsage(AnalysisUsage &AU) const override; + static struct SPIRV::ModuleAnalysisInfo MAI; + +private: + void setBaseInfo(const Module &M); + template void collectTypesConstsVars(); + void processDefInstrs(const Module &M); + void collectFuncNames(MachineInstr &MI, const Function &F); + void processOtherInstrs(const Module &M); + void numberRegistersGlobally(const Module &M); + + const SPIRVSubtarget *ST; + SPIRVGlobalRegistry *GR; + const SPIRVInstrInfo *TII; + MachineModuleInfo *MMI; +}; +} // namespace llvm +#endif // LLVM_LIB_TARGET_SPIRV_SPIRVMODULEANALYSIS_H diff --git a/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp b/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp new file mode 100644 --- /dev/null +++ b/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp @@ -0,0 +1,250 @@ +//===- SPIRVModuleAnalysis.cpp - analysis of global instrs & regs - 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 +// +//===----------------------------------------------------------------------===// +// +// The analysis collects instructions that should be output at the module level +// and performs the global register numbering. +// +// The results of this analysis are used in AsmPrinter to rename registers +// globally and to output required instructions at the module level. +// +//===----------------------------------------------------------------------===// + +#include "SPIRVModuleAnalysis.h" +#include "SPIRV.h" +#include "SPIRVGlobalRegistry.h" +#include "SPIRVSubtarget.h" +#include "SPIRVTargetMachine.h" +#include "SPIRVUtils.h" +#include "TargetInfo/SPIRVTargetInfo.h" +#include "llvm/CodeGen/MachineModuleInfo.h" +#include "llvm/CodeGen/TargetPassConfig.h" + +using namespace llvm; + +#define DEBUG_TYPE "spirv-module-analysis" + +char llvm::SPIRVModuleAnalysis::ID = 0; + +namespace llvm { +void initializeSPIRVModuleAnalysisPass(PassRegistry &); +} // namespace llvm + +INITIALIZE_PASS(SPIRVModuleAnalysis, DEBUG_TYPE, "SPIRV module analysis", true, + true) + +// Retrieve an unsigned from an MDNode with a list of them as operands. +static unsigned getMetadataUInt(MDNode *MdNode, unsigned OpIndex, + unsigned DefaultVal = 0) { + if (MdNode && OpIndex < MdNode->getNumOperands()) { + const auto &Op = MdNode->getOperand(OpIndex); + return mdconst::extract(Op)->getZExtValue(); + } + return DefaultVal; +} + +void SPIRVModuleAnalysis::setBaseInfo(const Module &M) { + MAI.MaxID = 0; + for (int i = 0; i < SPIRV::NUM_MODULE_SECTIONS; i++) + MAI.MS[i].clear(); + MAI.RegisterAliasTable.clear(); + MAI.InstrsToDelete.clear(); + MAI.FuncNameMap.clear(); + MAI.GlobalVarList.clear(); + + // TODO: determine memory model and source language from the configuratoin. + MAI.Mem = SPIRV::MemoryModel::OpenCL; + MAI.SrcLang = SPIRV::SourceLanguage::OpenCL_C; + unsigned PtrSize = ST->getPointerSize(); + MAI.Addr = PtrSize == 32 ? SPIRV::AddressingModel::Physical32 + : PtrSize == 64 ? SPIRV::AddressingModel::Physical64 + : SPIRV::AddressingModel::Logical; + // Get the OpenCL version number from metadata. + // TODO: support other source languages. + MAI.SrcLangVersion = 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); + MAI.SrcLangVersion = 0 | (MajorNum << 16) | (MinorNum << 8) | RevNum; + } +} + +// True if there is an instruction in the MS list 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 findSameInstrInMS(const MachineInstr &A, + SPIRV::ModuleSectionType MSType, + SPIRV::ModuleAnalysisInfo &MAI, + bool UpdateRegAliases, + unsigned StartOpIndex = 0) { + for (const auto *B : MAI.MS[MSType]) { + const unsigned 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(); + AllOpsMatch = MAI.getRegisterAlias(A.getMF(), RegA) == + MAI.getRegisterAlias(B->getMF(), RegB); + } else { + AllOpsMatch = A.getOperand(i).isIdenticalTo(B->getOperand(i)); + } + } + if (AllOpsMatch) { + if (UpdateRegAliases) { + assert(A.getOperand(0).isReg() && B->getOperand(0).isReg()); + Register LocalReg = A.getOperand(0).getReg(); + Register GlobalReg = + MAI.getRegisterAlias(B->getMF(), B->getOperand(0).getReg()); + MAI.setRegisterAlias(A.getMF(), LocalReg, GlobalReg); + } + return true; + } + } + } + return false; +} + +// Look for IDs declared with Import linkage, and map the imported name string +// to the register defining that variable (which will usually be the result of +// an OpFunction). This lets us call externally imported functions using +// the correct ID registers. +void SPIRVModuleAnalysis::collectFuncNames(MachineInstr &MI, + const Function &F) { + if (MI.getOpcode() == SPIRV::OpDecorate) { + // If it's got Import linkage. + auto Dec = MI.getOperand(1).getImm(); + if (Dec == static_cast(SPIRV::Decoration::LinkageAttributes)) { + auto Lnk = MI.getOperand(MI.getNumOperands() - 1).getImm(); + if (Lnk == static_cast(SPIRV::LinkageType::Import)) { + // Map imported function name to function ID register. + std::string Name = getStringImm(MI, 2); + Register Target = MI.getOperand(0).getReg(); + // TODO: check defs from different MFs. + MAI.FuncNameMap[Name] = MAI.getRegisterAlias(MI.getMF(), Target); + } + } + } else if (MI.getOpcode() == SPIRV::OpFunction) { + // Record all internal OpFunction declarations. + Register Reg = MI.defs().begin()->getReg(); + Register GlobalReg = MAI.getRegisterAlias(MI.getMF(), Reg); + assert(GlobalReg.isValid()); + // TODO: check that it does not conflict with existing entries. + MAI.FuncNameMap[F.getGlobalIdentifier()] = GlobalReg; + } +} + +// Collect the given instruction in the specified MS. We assume global register +// numbering has already occurred by this point. We can directly compare reg +// arguments when detecting duplicates. +static void collectOtherInstr(MachineInstr &MI, SPIRV::ModuleAnalysisInfo &MAI, + SPIRV::ModuleSectionType MSType, + bool IsConstOrType = false) { + MAI.setSkipEmission(&MI); + if (findSameInstrInMS(MI, MSType, MAI, IsConstOrType, IsConstOrType ? 1 : 0)) + return; // Found a duplicate, so don't add it. + // No duplicates, so add it. + MAI.MS[MSType].push_back(&MI); +} + +// Some global instructions make reference to function-local ID regs, so cannot +// be correctly collected until these registers are globally numbered. +void SPIRVModuleAnalysis::processOtherInstrs(const Module &M) { + for (auto F = M.begin(), E = M.end(); F != E; ++F) { + if ((*F).isDeclaration()) + continue; + MachineFunction *MF = MMI->getMachineFunction(*F); + assert(MF); + unsigned FCounter = 0; + for (MachineBasicBlock &MBB : *MF) + for (MachineInstr &MI : MBB) { + if (MI.getOpcode() == SPIRV::OpFunction) + FCounter++; + if (MAI.getSkipEmission(&MI)) + continue; + const unsigned OpCode = MI.getOpcode(); + const bool IsFuncOrParm = + OpCode == SPIRV::OpFunction || OpCode == SPIRV::OpFunctionParameter; + const bool IsConstOrType = + TII->isConstantInstr(MI) || TII->isTypeDeclInstr(MI); + if (OpCode == SPIRV::OpName || OpCode == SPIRV::OpMemberName) { + collectOtherInstr(MI, MAI, SPIRV::MB_DebugNames); + } else if (OpCode == SPIRV::OpEntryPoint) { + collectOtherInstr(MI, MAI, SPIRV::MB_EntryPoints); + } else if (TII->isDecorationInstr(MI)) { + collectOtherInstr(MI, MAI, SPIRV::MB_Annotations); + collectFuncNames(MI, *F); + } else if (IsConstOrType || (FCounter > 1 && IsFuncOrParm)) { + // Now OpSpecConstant*s are not in DT, + // but they need to be collected anyway. + enum SPIRV::ModuleSectionType Type = + IsFuncOrParm ? SPIRV::MB_ExtFuncDecls : SPIRV::MB_TypeConstVars; + collectOtherInstr(MI, MAI, Type, IsConstOrType); + } else if (OpCode == SPIRV::OpFunction) { + collectFuncNames(MI, *F); + } + } + } +} + +// Number registers in all functions globally from 0 onwards and store +// the result in global register alias table. +void SPIRVModuleAnalysis::numberRegistersGlobally(const Module &M) { + for (auto F = M.begin(), E = M.end(); F != E; ++F) { + if ((*F).isDeclaration()) + continue; + MachineFunction *MF = MMI->getMachineFunction(*F); + assert(MF); + for (MachineBasicBlock &MBB : *MF) { + for (MachineInstr &MI : MBB) { + for (MachineOperand &Op : MI.operands()) { + if (!Op.isReg()) + continue; + Register Reg = Op.getReg(); + if (MAI.hasRegisterAlias(MF, Reg)) + continue; + Register NewReg = Register::index2VirtReg(MAI.getNextID()); + MAI.setRegisterAlias(MF, Reg, NewReg); + } + } + } + } +} + +struct SPIRV::ModuleAnalysisInfo SPIRVModuleAnalysis::MAI; + +void SPIRVModuleAnalysis::getAnalysisUsage(AnalysisUsage &AU) const { + AU.addRequired(); + AU.addRequired(); +} + +bool SPIRVModuleAnalysis::runOnModule(Module &M) { + SPIRVTargetMachine &TM = + getAnalysis().getTM(); + ST = TM.getSubtargetImpl(); + GR = ST->getSPIRVGlobalRegistry(); + TII = ST->getInstrInfo(); + + MMI = &getAnalysis().getMMI(); + + setBaseInfo(M); + + // TODO: Process type/const/global var/func decl instructions, number their + // destination registers from 0 to N, collect Extensions and Capabilities. + + // Number rest of registers from N+1 onwards. + numberRegistersGlobally(M); + + // Collect OpName, OpEntryPoint, OpDecorate etc, process other instructions. + processOtherInstrs(M); + + return false; +} 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 @@ -40,6 +40,7 @@ PassRegistry &PR = *PassRegistry::getPassRegistry(); initializeGlobalISel(PR); + initializeSPIRVModuleAnalysisPass(PR); } static std::string computeDataLayout(const Triple &TT) { diff --git a/llvm/test/CodeGen/SPIRV/function/identity-function.ll b/llvm/test/CodeGen/SPIRV/function/identity-function.ll new file mode 100644 --- /dev/null +++ b/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 [[INT]] [[INT]] + +; CHECK: [[IDENTITY]] = OpFunction [[INT]] 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 +} diff --git a/llvm/test/CodeGen/SPIRV/function/trivial-function-definition.ll b/llvm/test/CodeGen/SPIRV/function/trivial-function-definition.ll new file mode 100644 --- /dev/null +++ b/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 +} diff --git a/llvm/test/CodeGen/SPIRV/function/trivial-function-with-attributes.ll b/llvm/test/CodeGen/SPIRV/function/trivial-function-with-attributes.ll new file mode 100644 --- /dev/null +++ b/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 diff --git a/llvm/test/CodeGen/SPIRV/function/trivial-function-with-call.ll b/llvm/test/CodeGen/SPIRV/function/trivial-function-with-call.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/SPIRV/function/trivial-function-with-call.ll @@ -0,0 +1,30 @@ +; 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]] +; CHECK-NEXT: OpFunctionEnd +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 +} diff --git a/llvm/test/CodeGen/SPIRV/lit.local.cfg b/llvm/test/CodeGen/SPIRV/lit.local.cfg new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/SPIRV/lit.local.cfg @@ -0,0 +1,2 @@ +if not 'SPIRV' in config.root.targets: + config.unsupported = True diff --git a/llvm/test/CodeGen/SPIRV/metadata-opencl.ll b/llvm/test/CodeGen/SPIRV/metadata-opencl.ll new file mode 100644 --- /dev/null +++ b/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 diff --git a/llvm/test/CodeGen/SPIRV/transcoding/readonly.ll b/llvm/test/CodeGen/SPIRV/transcoding/readonly.ll new file mode 100644 --- /dev/null +++ b/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)"}