Skip to content

Commit

Permalink
[Backport to 17] add API call to display general information about th…
Browse files Browse the repository at this point in the history
…e module (#2298) (#2694)

Partially load SPIR-V from the stream and decode only selected for the report instructions, needed to retrieve general information about the module: capabilities, extensions, version, memory model and addressing model.

In addition to immediately helpful for back-ends lists of capabilities and extensions declared in SPIR-V module, a general intent also is to extend report details in future by feedbacks about further potentially useful analysis, statistics, etc.

Co-authored-by: Vyacheslav Levytskyy <[email protected]>
  • Loading branch information
ustachow and VyacheslavLevytskyy authored Sep 2, 2024
1 parent 3aa5bcd commit b4006b9
Show file tree
Hide file tree
Showing 8 changed files with 305 additions and 4 deletions.
29 changes: 29 additions & 0 deletions include/LLVMSPIRVLib.h
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,35 @@ std::unique_ptr<SPIRVModule> readSpirvModule(std::istream &IS,
const SPIRV::TranslatorOpts &Opts,
std::string &ErrMsg);

struct SPIRVModuleReport {
SPIRV::VersionNumber Version;
uint32_t MemoryModel;
uint32_t AddrModel;
std::vector<std::string> Extensions;
std::vector<std::string> ExtendedInstructionSets;
std::vector<uint32_t> 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.
std::optional<SPIRVModuleReport> getSpirvReport(std::istream &IS);
std::optional<SPIRVModuleReport> getSpirvReport(std::istream &IS, int &ErrCode);

struct SPIRVModuleTextReport {
std::string Version;
std::string MemoryModel;
std::string AddrModel;
std::vector<std::string> Extensions;
std::vector<std::string> ExtendedInstructionSets;
std::vector<std::string> 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 {
Expand Down
21 changes: 21 additions & 0 deletions include/LLVMSPIRVOpts.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,27 @@ enum class VersionNumber : uint32_t {
MaximumVersion = SPIRV_1_6
};

inline constexpr std::string_view formatVersionNumber(uint32_t Version) {
switch (Version) {
case static_cast<uint32_t>(VersionNumber::SPIRV_1_0):
return "1.0";
case static_cast<uint32_t>(VersionNumber::SPIRV_1_1):
return "1.1";
case static_cast<uint32_t>(VersionNumber::SPIRV_1_2):
return "1.2";
case static_cast<uint32_t>(VersionNumber::SPIRV_1_3):
return "1.3";
case static_cast<uint32_t>(VersionNumber::SPIRV_1_4):
return "1.4";
}
return "unknown";
}

inline bool isSPIRVVersionKnown(uint32_t Ver) {
return Ver >= static_cast<uint32_t>(VersionNumber::MinimumVersion) &&
Ver <= static_cast<uint32_t>(VersionNumber::MaximumVersion);
}

enum class ExtensionID : uint32_t {
First,
#define EXT(X) X,
Expand Down
131 changes: 131 additions & 0 deletions lib/SPIRV/SPIRVReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4868,6 +4868,137 @@ Instruction *SPIRVToLLVM::transRelational(SPIRVInstruction *I, BasicBlock *BB) {
return cast<Instruction>(Mutator.getMutated());
}

std::optional<SPIRVModuleReport> getSpirvReport(std::istream &IS) {
int IgnoreErrCode;
return getSpirvReport(IS, IgnoreErrCode);
}

std::optional<SPIRVModuleReport> getSpirvReport(std::istream &IS,
int &ErrCode) {
SPIRVWord Word;
std::string Name;
std::unique_ptr<SPIRVModule> 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<SPIRV::VersionNumber>(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 std::make_optional(std::move(Report));
}

constexpr std::string_view 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";
}
}

constexpr std::string_view 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<uint32_t>(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) {
bool Found = SPIRVCapabilityNameMap::find(
static_cast<SPIRVCapabilityKind>(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<SPIRVModule> readSpirvModule(std::istream &IS,
const SPIRV::TranslatorOpts &Opts,
std::string &ErrMsg) {
Expand Down
6 changes: 6 additions & 0 deletions lib/SPIRV/libSPIRV/SPIRVErrorEnum.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,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.")
4 changes: 1 addition & 3 deletions lib/SPIRV/libSPIRV/SPIRVModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2134,9 +2134,7 @@ std::istream &operator>>(std::istream &I, SPIRVModule &M) {
}

Decoder >> MI.SPIRVVersion;
bool SPIRVVersionIsKnown =
static_cast<uint32_t>(VersionNumber::MinimumVersion) <= MI.SPIRVVersion &&
MI.SPIRVVersion <= static_cast<uint32_t>(VersionNumber::MaximumVersion);
bool SPIRVVersionIsKnown = isSPIRVVersionKnown(MI.SPIRVVersion);
if (!M.getErrorLog().checkError(
SPIRVVersionIsKnown, SPIRVEC_InvalidModule,
"unsupported SPIR-V version number '" + to_string(MI.SPIRVVersion) +
Expand Down
33 changes: 33 additions & 0 deletions test/negative/spirv_report_bad_input.spt
Original file line number Diff line number Diff line change
@@ -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
43 changes: 43 additions & 0 deletions test/spirv_report.spt
Original file line number Diff line number Diff line change
@@ -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
42 changes: 41 additions & 1 deletion tools/llvm-spirv/llvm-spirv.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,12 @@ static cl::opt<bool> SpecConstInfo(
cl::desc("Display id of constants available for specializaion and their "
"size in bytes"));

static cl::opt<bool>
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<SPIRV::FPContractMode> FPCMode(
"spirv-fp-contract", cl::desc("Set FP Contraction mode:"),
cl::init(SPIRV::FPContractMode::On),
Expand Down Expand Up @@ -805,7 +811,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) {
Expand All @@ -832,5 +838,39 @@ int main(int Ac, char **Av) {
<< ", size in bytes = " << SpecConst.Size
<< ", type = " << SpecConst.Type << "\n";
}

if (SPIRVPrintReport) {
std::ifstream IFS(InputFile, std::ios::binary);
int ErrCode = 0;
std::optional<SPIRV::SPIRVModuleReport> 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.value());

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;
}

0 comments on commit b4006b9

Please sign in to comment.