diff --git a/include/LLVMSPIRVLib.h b/include/LLVMSPIRVLib.h index c6723b4fba..123e1a43fe 100644 --- a/include/LLVMSPIRVLib.h +++ b/include/LLVMSPIRVLib.h @@ -106,6 +106,36 @@ std::unique_ptr readSpirvModule(std::istream &IS, const SPIRV::TranslatorOpts &Opts, std::string &ErrMsg); +struct SPIRVModuleReport { + SPIRV::VersionNumber Version; + uint32_t MemoryModel; + uint32_t AddrModel; + std::vector Extensions; + std::vector ExtendedInstructionSets; + std::vector Capabilities; +}; +/// \brief Partially load SPIR-V from the stream and decode only selected +/// instructions that are needed to retrieve general information +/// about the module. If this call fails, readSPIRVModule is +/// expected to fail as well. +/// \returns nullopt on failure. +llvm::Optional getSpirvReport(std::istream &IS); +llvm::Optional getSpirvReport(std::istream &IS, + int &ErrCode); + +struct SPIRVModuleTextReport { + std::string Version; + std::string MemoryModel; + std::string AddrModel; + std::vector Extensions; + std::vector ExtendedInstructionSets; + std::vector Capabilities; +}; +/// \brief Create a human-readable form of the report returned by a call to +/// getSpirvReport by decoding its binary fields. +/// \returns String with the human-readable report. +SPIRVModuleTextReport formatSpirvReport(const SPIRVModuleReport &Report); + } // End namespace SPIRV namespace llvm { diff --git a/include/LLVMSPIRVOpts.h b/include/LLVMSPIRVOpts.h index b0604187f2..c956eb7ab4 100644 --- a/include/LLVMSPIRVOpts.h +++ b/include/LLVMSPIRVOpts.h @@ -69,6 +69,27 @@ enum class VersionNumber : uint32_t { MaximumVersion = SPIRV_1_6 }; +inline std::string formatVersionNumber(uint32_t Version) { + switch (Version) { + case static_cast(VersionNumber::SPIRV_1_0): + return "1.0"; + case static_cast(VersionNumber::SPIRV_1_1): + return "1.1"; + case static_cast(VersionNumber::SPIRV_1_2): + return "1.2"; + case static_cast(VersionNumber::SPIRV_1_3): + return "1.3"; + case static_cast(VersionNumber::SPIRV_1_4): + return "1.4"; + } + return "unknown"; +} + +inline bool isSPIRVVersionKnown(uint32_t Ver) { + return Ver >= static_cast(VersionNumber::MinimumVersion) && + Ver <= static_cast(VersionNumber::MaximumVersion); +} + enum class ExtensionID : uint32_t { First, #define EXT(X) X, diff --git a/lib/SPIRV/SPIRVReader.cpp b/lib/SPIRV/SPIRVReader.cpp index f52c25c2ec..239d0accb4 100644 --- a/lib/SPIRV/SPIRVReader.cpp +++ b/lib/SPIRV/SPIRVReader.cpp @@ -4866,6 +4866,137 @@ Instruction *SPIRVToLLVM::transRelational(SPIRVInstruction *I, BasicBlock *BB) { &BtnInfo, &Attrs, /*TakeFuncName=*/true))); } +llvm::Optional getSpirvReport(std::istream &IS) { + int IgnoreErrCode; + return getSpirvReport(IS, IgnoreErrCode); +} + +llvm::Optional getSpirvReport(std::istream &IS, + int &ErrCode) { + SPIRVWord Word; + std::string Name; + std::unique_ptr BM(SPIRVModule::createSPIRVModule()); + SPIRVDecoder D(IS, *BM); + D >> Word; + if (Word != MagicNumber) { + ErrCode = SPIRVEC_InvalidMagicNumber; + return {}; + } + D >> Word; + if (!isSPIRVVersionKnown(Word)) { + ErrCode = SPIRVEC_InvalidVersionNumber; + return {}; + } + SPIRVModuleReport Report; + Report.Version = static_cast(Word); + // Skip: Generator’s magic number, Bound and Reserved word + D.ignore(3); + + bool IsReportGenCompleted = false, IsMemoryModelDefined = false; + while (!IS.bad() && !IsReportGenCompleted && D.getWordCountAndOpCode()) { + switch (D.OpCode) { + case OpCapability: + D >> Word; + Report.Capabilities.push_back(Word); + break; + case OpExtension: + Name.clear(); + D >> Name; + Report.Extensions.push_back(Name); + break; + case OpExtInstImport: + Name.clear(); + D >> Word >> Name; + Report.ExtendedInstructionSets.push_back(Name); + break; + case OpMemoryModel: + if (IsMemoryModelDefined) { + ErrCode = SPIRVEC_RepeatedMemoryModel; + return {}; + } + SPIRVAddressingModelKind AddrModel; + SPIRVMemoryModelKind MemoryModel; + D >> AddrModel >> MemoryModel; + if (!isValid(AddrModel)) { + ErrCode = SPIRVEC_InvalidAddressingModel; + return {}; + } + if (!isValid(MemoryModel)) { + ErrCode = SPIRVEC_InvalidMemoryModel; + return {}; + } + Report.MemoryModel = MemoryModel; + Report.AddrModel = AddrModel; + IsMemoryModelDefined = true; + // In this report we don't analyze instructions after OpMemoryModel + IsReportGenCompleted = true; + break; + default: + // No more instructions to gather information about + IsReportGenCompleted = true; + } + } + if (IS.bad()) { + ErrCode = SPIRVEC_InvalidModule; + return {}; + } + if (!IsMemoryModelDefined) { + ErrCode = SPIRVEC_UnspecifiedMemoryModel; + return {}; + } + ErrCode = SPIRVEC_Success; + return llvm::Optional(std::move(Report)); +} + +std::string formatAddressingModel(uint32_t AddrModel) { + switch (AddrModel) { + case AddressingModelLogical: + return "Logical"; + case AddressingModelPhysical32: + return "Physical32"; + case AddressingModelPhysical64: + return "Physical64"; + case AddressingModelPhysicalStorageBuffer64: + return "PhysicalStorageBuffer64"; + default: + return "Unknown"; + } +} + +std::string formatMemoryModel(uint32_t MemoryModel) { + switch (MemoryModel) { + case MemoryModelSimple: + return "Simple"; + case MemoryModelGLSL450: + return "GLSL450"; + case MemoryModelOpenCL: + return "OpenCL"; + case MemoryModelVulkan: + return "Vulkan"; + default: + return "Unknown"; + } +} + +SPIRVModuleTextReport formatSpirvReport(const SPIRVModuleReport &Report) { + SPIRVModuleTextReport TextReport; + TextReport.Version = + formatVersionNumber(static_cast(Report.Version)); + TextReport.AddrModel = formatAddressingModel(Report.AddrModel); + TextReport.MemoryModel = formatMemoryModel(Report.MemoryModel); + // format capability codes as strings + std::string Name; + for (auto Capability : Report.Capabilities) { + const bool Found = SPIRVCapabilityNameMap::find( + static_cast(Capability), &Name); + TextReport.Capabilities.push_back(Found ? Name : "Unknown"); + } + // other fields with string content can be copied as is + TextReport.Extensions = Report.Extensions; + TextReport.ExtendedInstructionSets = Report.ExtendedInstructionSets; + return TextReport; +} + std::unique_ptr readSpirvModule(std::istream &IS, const SPIRV::TranslatorOpts &Opts, std::string &ErrMsg) { diff --git a/lib/SPIRV/libSPIRV/SPIRVErrorEnum.h b/lib/SPIRV/libSPIRV/SPIRVErrorEnum.h index 3316539b30..7e53f59f86 100644 --- a/lib/SPIRV/libSPIRV/SPIRVErrorEnum.h +++ b/lib/SPIRV/libSPIRV/SPIRVErrorEnum.h @@ -20,3 +20,9 @@ _SPIRV_OP(Requires1_1, "Feature requires SPIR-V 1.1 or greater:") _SPIRV_OP(RequiresVersion, "Cannot fulfill SPIR-V version restriction:\n") _SPIRV_OP(RequiresExtension, "Feature requires the following SPIR-V extension:\n") +_SPIRV_OP(InvalidMagicNumber, + "Invalid Magic Number.") +_SPIRV_OP(InvalidVersionNumber, + "Invalid Version Number.") +_SPIRV_OP(UnspecifiedMemoryModel, "Unspecified Memory Model.") +_SPIRV_OP(RepeatedMemoryModel, "Expects a single OpMemoryModel instruction.") diff --git a/lib/SPIRV/libSPIRV/SPIRVModule.cpp b/lib/SPIRV/libSPIRV/SPIRVModule.cpp index 3e208d1003..6829200d7c 100644 --- a/lib/SPIRV/libSPIRV/SPIRVModule.cpp +++ b/lib/SPIRV/libSPIRV/SPIRVModule.cpp @@ -2138,9 +2138,7 @@ std::istream &operator>>(std::istream &I, SPIRVModule &M) { } Decoder >> MI.SPIRVVersion; - bool SPIRVVersionIsKnown = - static_cast(VersionNumber::MinimumVersion) <= MI.SPIRVVersion && - MI.SPIRVVersion <= static_cast(VersionNumber::MaximumVersion); + const bool SPIRVVersionIsKnown = isSPIRVVersionKnown(MI.SPIRVVersion); if (!M.getErrorLog().checkError( SPIRVVersionIsKnown, SPIRVEC_InvalidModule, "unsupported SPIR-V version number '" + to_string(MI.SPIRVVersion) + diff --git a/test/negative/spirv_report_bad_input.spt b/test/negative/spirv_report_bad_input.spt new file mode 100644 index 0000000000..202cd702a6 --- /dev/null +++ b/test/negative/spirv_report_bad_input.spt @@ -0,0 +1,33 @@ +; RUN: llvm-spirv %s -to-binary -o %t.spv +; The next line is to corrupt the binary file by changing its Magic Number +; RUN: echo "0" > %t_corrupted.spv && cat %t.spv >> %t_corrupted.spv +; RUN: not llvm-spirv --spirv-print-report %t_corrupted.spv 2>&1 | FileCheck %s --check-prefix=CHECK-ERROR +; +; CHECK-ERROR: Invalid SPIR-V binary + +119734787 65536 393230 10 0 +2 Capability Addresses +2 Capability Kernel +2 Capability LoopFuseINTEL +2 Capability BitInstructions +6 Extension "SPV_INTEL_loop_fuse" +8 Extension "SPV_KHR_bit_instructions" +5 ExtInstImport 1 "OpenCL.std" +3 MemoryModel 1 2 +7 EntryPoint 6 5 "TestSatPacked" +3 Source 3 102000 + +5 Decorate 5 FuseLoopsInFunctionINTEL 3 1 +4 TypeInt 3 32 0 +2 TypeVoid 2 +5 TypeFunction 4 2 3 3 + +5 Function 2 5 0 4 +3 FunctionParameter 3 6 +3 FunctionParameter 3 7 + +2 Label 8 +4 BitReverse 3 9 6 +1 Return + +1 FunctionEnd diff --git a/test/spirv_report.spt b/test/spirv_report.spt new file mode 100644 index 0000000000..a8d89abee3 --- /dev/null +++ b/test/spirv_report.spt @@ -0,0 +1,43 @@ +; RUN: llvm-spirv %s -to-binary -o %t.spv +; RUN: llvm-spirv --spirv-print-report %t.spv | FileCheck %s --check-prefix=CHECK-DAG + +; CHECK-DAG: Version: 1.0 +; CHECK-DAG: Memory model: OpenCL +; CHECK-DAG: Addressing model: Physical32 +; CHECK-DAG: Number of capabilities: 4 +; CHECK-DAG: Capability: Addresses +; CHECK-DAG: Capability: Kernel +; CHECK-DAG: Capability: LoopFuseINTEL +; CHECK-DAG: Capability: BitInstructions +; CHECK-DAG: Number of extensions: 2 +; CHECK-DAG: Extension: SPV_INTEL_loop_fuse +; CHECK-DAG: Extension: SPV_KHR_bit_instructions +; CHECK-DAG: Number of extended instruction sets: 1 +; CHECK-DAG: Extended Instruction Set: OpenCL.std + +119734787 65536 393230 10 0 +2 Capability Addresses +2 Capability Kernel +2 Capability LoopFuseINTEL +2 Capability BitInstructions +6 Extension "SPV_INTEL_loop_fuse" +8 Extension "SPV_KHR_bit_instructions" +5 ExtInstImport 1 "OpenCL.std" +3 MemoryModel 1 2 +7 EntryPoint 6 5 "TestSatPacked" +3 Source 3 102000 + +5 Decorate 5 FuseLoopsInFunctionINTEL 3 1 +4 TypeInt 3 32 0 +2 TypeVoid 2 +5 TypeFunction 4 2 3 3 + +5 Function 2 5 0 4 +3 FunctionParameter 3 6 +3 FunctionParameter 3 7 + +2 Label 8 +4 BitReverse 3 9 6 +1 Return + +1 FunctionEnd diff --git a/tools/llvm-spirv/llvm-spirv.cpp b/tools/llvm-spirv/llvm-spirv.cpp index ce98731cff..7a28f5f2c5 100644 --- a/tools/llvm-spirv/llvm-spirv.cpp +++ b/tools/llvm-spirv/llvm-spirv.cpp @@ -194,6 +194,12 @@ static cl::opt SpecConstInfo( cl::desc("Display id of constants available for specializaion and their " "size in bytes")); +static cl::opt + SPIRVPrintReport("spirv-print-report", cl::init(false), + cl::desc("Display general information about the module " + "(capabilities, extensions, version, memory model" + " and addressing model)")); + static cl::opt FPCMode( "spirv-fp-contract", cl::desc("Set FP Contraction mode:"), cl::init(SPIRV::FPContractMode::On), @@ -767,7 +773,7 @@ int main(int Ac, char **Av) { return convertSPIRV(); #endif - if (!IsReverse && !IsRegularization && !SpecConstInfo) + if (!IsReverse && !IsRegularization && !SpecConstInfo && !SPIRVPrintReport) return convertLLVMToSPIRV(Opts); if (IsReverse && IsRegularization) { @@ -793,5 +799,39 @@ int main(int Ac, char **Av) { std::cout << "Spec const id = " << SpecConst.first << ", size in bytes = " << SpecConst.second << "\n"; } + + if (SPIRVPrintReport) { + std::ifstream IFS(InputFile, std::ios::binary); + int ErrCode = 0; + llvm::Optional BinReport = + SPIRV::getSpirvReport(IFS, ErrCode); + if (!BinReport) { + std::cerr << "Invalid SPIR-V binary, error code is " << ErrCode << "\n"; + return -1; + } + + SPIRV::SPIRVModuleTextReport TextReport = + SPIRV::formatSpirvReport(*BinReport); + + std::cout << "SPIR-V module report:" + << "\n Version: " << TextReport.Version + << "\n Memory model: " << TextReport.MemoryModel + << "\n Addressing model: " << TextReport.AddrModel << "\n"; + + std::cout << " Number of capabilities: " << TextReport.Capabilities.size() + << "\n"; + for (auto &Capability : TextReport.Capabilities) + std::cout << " Capability: " << Capability << "\n"; + + std::cout << " Number of extensions: " << TextReport.Extensions.size() + << "\n"; + for (auto &Extension : TextReport.Extensions) + std::cout << " Extension: " << Extension << "\n"; + + std::cout << " Number of extended instruction sets: " + << TextReport.ExtendedInstructionSets.size() << "\n"; + for (auto &ExtendedInstructionSet : TextReport.ExtendedInstructionSets) + std::cout << " Extended Instruction Set: " << ExtendedInstructionSet << "\n"; + } return 0; }