mirror of
https://github.com/KhronosGroup/SPIRV-Tools
synced 2024-10-18 11:10:05 +00:00
Refactor the disassembler code for reuse (#4616)
The upcoming spirv-diff tool also outputs disassembly, although in a per-instruction basis. This change refactors the disassembler code to support such a use case.
This commit is contained in:
parent
f0351b7bc6
commit
4322c4b5a7
@ -17,6 +17,8 @@
|
||||
// This file contains a disassembler: It converts a SPIR-V binary
|
||||
// to text.
|
||||
|
||||
#include "source/disassemble.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
@ -28,9 +30,7 @@
|
||||
#include "source/assembly_grammar.h"
|
||||
#include "source/binary.h"
|
||||
#include "source/diagnostic.h"
|
||||
#include "source/disassemble.h"
|
||||
#include "source/ext_inst.h"
|
||||
#include "source/name_mapper.h"
|
||||
#include "source/opcode.h"
|
||||
#include "source/parsed_operand.h"
|
||||
#include "source/print.h"
|
||||
@ -40,29 +40,21 @@
|
||||
#include "source/util/make_unique.h"
|
||||
#include "spirv-tools/libspirv.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace {
|
||||
|
||||
// A Disassembler instance converts a SPIR-V binary to its assembly
|
||||
// representation.
|
||||
class Disassembler {
|
||||
public:
|
||||
Disassembler(const spvtools::AssemblyGrammar& grammar, uint32_t options,
|
||||
spvtools::NameMapper name_mapper)
|
||||
: grammar_(grammar),
|
||||
print_(spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_PRINT, options)),
|
||||
color_(spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_COLOR, options)),
|
||||
indent_(spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_INDENT, options)
|
||||
? kStandardIndent
|
||||
: 0),
|
||||
comment_(spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_COMMENT, options)),
|
||||
Disassembler(const AssemblyGrammar& grammar, uint32_t options,
|
||||
NameMapper name_mapper)
|
||||
: print_(spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_PRINT, options)),
|
||||
text_(),
|
||||
out_(print_ ? out_stream() : out_stream(text_)),
|
||||
stream_(out_.get()),
|
||||
instruction_disassembler_(grammar, out_.get(), options, name_mapper),
|
||||
header_(!spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER, options)),
|
||||
show_byte_offset_(spvIsInBitfield(
|
||||
SPV_BINARY_TO_TEXT_OPTION_SHOW_BYTE_OFFSET, options)),
|
||||
byte_offset_(0),
|
||||
name_mapper_(std::move(name_mapper)) {}
|
||||
byte_offset_(0) {}
|
||||
|
||||
// Emits the assembly header for the module, and sets up internal state
|
||||
// so subsequent callbacks can handle the cases where the entire module
|
||||
@ -78,56 +70,13 @@ class Disassembler {
|
||||
spv_result_t SaveTextResult(spv_text* text_result) const;
|
||||
|
||||
private:
|
||||
enum { kStandardIndent = 15 };
|
||||
|
||||
using out_stream = spvtools::out_stream;
|
||||
|
||||
// Emits an operand for the given instruction, where the instruction
|
||||
// is at offset words from the start of the binary.
|
||||
void EmitOperand(const spv_parsed_instruction_t& inst,
|
||||
const uint16_t operand_index);
|
||||
|
||||
// Emits a mask expression for the given mask word of the specified type.
|
||||
void EmitMaskOperand(const spv_operand_type_t type, const uint32_t word);
|
||||
|
||||
// Resets the output color, if color is turned on.
|
||||
void ResetColor() {
|
||||
if (color_) out_.get() << spvtools::clr::reset{print_};
|
||||
}
|
||||
// Sets the output to grey, if color is turned on.
|
||||
void SetGrey() {
|
||||
if (color_) out_.get() << spvtools::clr::grey{print_};
|
||||
}
|
||||
// Sets the output to blue, if color is turned on.
|
||||
void SetBlue() {
|
||||
if (color_) out_.get() << spvtools::clr::blue{print_};
|
||||
}
|
||||
// Sets the output to yellow, if color is turned on.
|
||||
void SetYellow() {
|
||||
if (color_) out_.get() << spvtools::clr::yellow{print_};
|
||||
}
|
||||
// Sets the output to red, if color is turned on.
|
||||
void SetRed() {
|
||||
if (color_) out_.get() << spvtools::clr::red{print_};
|
||||
}
|
||||
// Sets the output to green, if color is turned on.
|
||||
void SetGreen() {
|
||||
if (color_) out_.get() << spvtools::clr::green{print_};
|
||||
}
|
||||
|
||||
const spvtools::AssemblyGrammar& grammar_;
|
||||
const bool print_; // Should we also print to the standard output stream?
|
||||
const bool color_; // Should we print in colour?
|
||||
const int indent_; // How much to indent. 0 means don't indent
|
||||
const int comment_; // Should we comment the source
|
||||
spv_endianness_t endian_; // The detected endianness of the binary.
|
||||
std::stringstream text_; // Captures the text, if not printing.
|
||||
out_stream out_; // The Output stream. Either to text_ or standard output.
|
||||
std::ostream& stream_; // The output std::stream.
|
||||
disassemble::InstructionDisassembler instruction_disassembler_;
|
||||
const bool header_; // Should we output header as the leading comment?
|
||||
const bool show_byte_offset_; // Should we print byte offset, in hex?
|
||||
size_t byte_offset_; // The number of bytes processed so far.
|
||||
spvtools::NameMapper name_mapper_;
|
||||
bool inserted_decoration_space_ = false;
|
||||
bool inserted_debug_space_ = false;
|
||||
bool inserted_type_space_ = false;
|
||||
@ -139,21 +88,11 @@ spv_result_t Disassembler::HandleHeader(spv_endianness_t endian,
|
||||
endian_ = endian;
|
||||
|
||||
if (header_) {
|
||||
const char* generator_tool =
|
||||
spvGeneratorStr(SPV_GENERATOR_TOOL_PART(generator));
|
||||
stream_ << "; SPIR-V\n"
|
||||
<< "; Version: " << SPV_SPIRV_VERSION_MAJOR_PART(version) << "."
|
||||
<< SPV_SPIRV_VERSION_MINOR_PART(version) << "\n"
|
||||
<< "; Generator: " << generator_tool;
|
||||
// For unknown tools, print the numeric tool value.
|
||||
if (0 == strcmp("Unknown", generator_tool)) {
|
||||
stream_ << "(" << SPV_GENERATOR_TOOL_PART(generator) << ")";
|
||||
}
|
||||
// Print the miscellaneous part of the generator word on the same
|
||||
// line as the tool name.
|
||||
stream_ << "; " << SPV_GENERATOR_MISC_PART(generator) << "\n"
|
||||
<< "; Bound: " << id_bound << "\n"
|
||||
<< "; Schema: " << schema << "\n";
|
||||
instruction_disassembler_.EmitHeaderSpirv();
|
||||
instruction_disassembler_.EmitHeaderVersion(version);
|
||||
instruction_disassembler_.EmitHeaderGenerator(generator);
|
||||
instruction_disassembler_.EmitHeaderIdBound(id_bound);
|
||||
instruction_disassembler_.EmitHeaderSchema(schema);
|
||||
}
|
||||
|
||||
byte_offset_ = SPV_INDEX_INSTRUCTION * sizeof(uint32_t);
|
||||
@ -163,230 +102,17 @@ spv_result_t Disassembler::HandleHeader(spv_endianness_t endian,
|
||||
|
||||
spv_result_t Disassembler::HandleInstruction(
|
||||
const spv_parsed_instruction_t& inst) {
|
||||
auto opcode = static_cast<SpvOp>(inst.opcode);
|
||||
if (comment_ && opcode == SpvOpFunction) {
|
||||
stream_ << std::endl;
|
||||
stream_ << std::string(indent_, ' ');
|
||||
stream_ << "; Function " << name_mapper_(inst.result_id) << std::endl;
|
||||
}
|
||||
if (comment_ && !inserted_decoration_space_ &&
|
||||
spvOpcodeIsDecoration(opcode)) {
|
||||
inserted_decoration_space_ = true;
|
||||
stream_ << std::endl;
|
||||
stream_ << std::string(indent_, ' ');
|
||||
stream_ << "; Annotations" << std::endl;
|
||||
}
|
||||
if (comment_ && !inserted_debug_space_ && spvOpcodeIsDebug(opcode)) {
|
||||
inserted_debug_space_ = true;
|
||||
stream_ << std::endl;
|
||||
stream_ << std::string(indent_, ' ');
|
||||
stream_ << "; Debug Information" << std::endl;
|
||||
}
|
||||
if (comment_ && !inserted_type_space_ && spvOpcodeGeneratesType(opcode)) {
|
||||
inserted_type_space_ = true;
|
||||
stream_ << std::endl;
|
||||
stream_ << std::string(indent_, ' ');
|
||||
stream_ << "; Types, variables and constants" << std::endl;
|
||||
}
|
||||
instruction_disassembler_.EmitSectionComment(inst, inserted_decoration_space_,
|
||||
inserted_debug_space_,
|
||||
inserted_type_space_);
|
||||
|
||||
if (inst.result_id) {
|
||||
SetBlue();
|
||||
const std::string id_name = name_mapper_(inst.result_id);
|
||||
if (indent_)
|
||||
stream_ << std::setw(std::max(0, indent_ - 3 - int(id_name.size())));
|
||||
stream_ << "%" << id_name;
|
||||
ResetColor();
|
||||
stream_ << " = ";
|
||||
} else {
|
||||
stream_ << std::string(indent_, ' ');
|
||||
}
|
||||
|
||||
stream_ << "Op" << spvOpcodeString(opcode);
|
||||
|
||||
for (uint16_t i = 0; i < inst.num_operands; i++) {
|
||||
const spv_operand_type_t type = inst.operands[i].type;
|
||||
assert(type != SPV_OPERAND_TYPE_NONE);
|
||||
if (type == SPV_OPERAND_TYPE_RESULT_ID) continue;
|
||||
stream_ << " ";
|
||||
EmitOperand(inst, i);
|
||||
}
|
||||
|
||||
if (comment_ && opcode == SpvOpName) {
|
||||
const spv_parsed_operand_t& operand = inst.operands[0];
|
||||
const uint32_t word = inst.words[operand.offset];
|
||||
stream_ << " ; id %" << word;
|
||||
}
|
||||
|
||||
if (show_byte_offset_) {
|
||||
SetGrey();
|
||||
auto saved_flags = stream_.flags();
|
||||
auto saved_fill = stream_.fill();
|
||||
stream_ << " ; 0x" << std::setw(8) << std::hex << std::setfill('0')
|
||||
<< byte_offset_;
|
||||
stream_.flags(saved_flags);
|
||||
stream_.fill(saved_fill);
|
||||
ResetColor();
|
||||
}
|
||||
instruction_disassembler_.EmitInstruction(inst, byte_offset_);
|
||||
|
||||
byte_offset_ += inst.num_words * sizeof(uint32_t);
|
||||
|
||||
stream_ << "\n";
|
||||
return SPV_SUCCESS;
|
||||
}
|
||||
|
||||
void Disassembler::EmitOperand(const spv_parsed_instruction_t& inst,
|
||||
const uint16_t operand_index) {
|
||||
assert(operand_index < inst.num_operands);
|
||||
const spv_parsed_operand_t& operand = inst.operands[operand_index];
|
||||
const uint32_t word = inst.words[operand.offset];
|
||||
switch (operand.type) {
|
||||
case SPV_OPERAND_TYPE_RESULT_ID:
|
||||
assert(false && "<result-id> is not supposed to be handled here");
|
||||
SetBlue();
|
||||
stream_ << "%" << name_mapper_(word);
|
||||
break;
|
||||
case SPV_OPERAND_TYPE_ID:
|
||||
case SPV_OPERAND_TYPE_TYPE_ID:
|
||||
case SPV_OPERAND_TYPE_SCOPE_ID:
|
||||
case SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID:
|
||||
SetYellow();
|
||||
stream_ << "%" << name_mapper_(word);
|
||||
break;
|
||||
case SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER: {
|
||||
spv_ext_inst_desc ext_inst;
|
||||
SetRed();
|
||||
if (grammar_.lookupExtInst(inst.ext_inst_type, word, &ext_inst) ==
|
||||
SPV_SUCCESS) {
|
||||
stream_ << ext_inst->name;
|
||||
} else {
|
||||
if (!spvExtInstIsNonSemantic(inst.ext_inst_type)) {
|
||||
assert(false && "should have caught this earlier");
|
||||
} else {
|
||||
// for non-semantic instruction sets we can just print the number
|
||||
stream_ << word;
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case SPV_OPERAND_TYPE_SPEC_CONSTANT_OP_NUMBER: {
|
||||
spv_opcode_desc opcode_desc;
|
||||
if (grammar_.lookupOpcode(SpvOp(word), &opcode_desc))
|
||||
assert(false && "should have caught this earlier");
|
||||
SetRed();
|
||||
stream_ << opcode_desc->name;
|
||||
} break;
|
||||
case SPV_OPERAND_TYPE_LITERAL_INTEGER:
|
||||
case SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER: {
|
||||
SetRed();
|
||||
spvtools::EmitNumericLiteral(&stream_, inst, operand);
|
||||
ResetColor();
|
||||
} break;
|
||||
case SPV_OPERAND_TYPE_LITERAL_STRING: {
|
||||
stream_ << "\"";
|
||||
SetGreen();
|
||||
|
||||
std::string str = spvDecodeLiteralStringOperand(inst, operand_index);
|
||||
for (char const& c : str) {
|
||||
if (c == '"' || c == '\\') stream_ << '\\';
|
||||
stream_ << c;
|
||||
}
|
||||
ResetColor();
|
||||
stream_ << '"';
|
||||
} break;
|
||||
case SPV_OPERAND_TYPE_CAPABILITY:
|
||||
case SPV_OPERAND_TYPE_SOURCE_LANGUAGE:
|
||||
case SPV_OPERAND_TYPE_EXECUTION_MODEL:
|
||||
case SPV_OPERAND_TYPE_ADDRESSING_MODEL:
|
||||
case SPV_OPERAND_TYPE_MEMORY_MODEL:
|
||||
case SPV_OPERAND_TYPE_EXECUTION_MODE:
|
||||
case SPV_OPERAND_TYPE_STORAGE_CLASS:
|
||||
case SPV_OPERAND_TYPE_DIMENSIONALITY:
|
||||
case SPV_OPERAND_TYPE_SAMPLER_ADDRESSING_MODE:
|
||||
case SPV_OPERAND_TYPE_SAMPLER_FILTER_MODE:
|
||||
case SPV_OPERAND_TYPE_SAMPLER_IMAGE_FORMAT:
|
||||
case SPV_OPERAND_TYPE_FP_ROUNDING_MODE:
|
||||
case SPV_OPERAND_TYPE_LINKAGE_TYPE:
|
||||
case SPV_OPERAND_TYPE_ACCESS_QUALIFIER:
|
||||
case SPV_OPERAND_TYPE_FUNCTION_PARAMETER_ATTRIBUTE:
|
||||
case SPV_OPERAND_TYPE_DECORATION:
|
||||
case SPV_OPERAND_TYPE_BUILT_IN:
|
||||
case SPV_OPERAND_TYPE_GROUP_OPERATION:
|
||||
case SPV_OPERAND_TYPE_KERNEL_ENQ_FLAGS:
|
||||
case SPV_OPERAND_TYPE_KERNEL_PROFILING_INFO:
|
||||
case SPV_OPERAND_TYPE_RAY_FLAGS:
|
||||
case SPV_OPERAND_TYPE_RAY_QUERY_INTERSECTION:
|
||||
case SPV_OPERAND_TYPE_RAY_QUERY_COMMITTED_INTERSECTION_TYPE:
|
||||
case SPV_OPERAND_TYPE_RAY_QUERY_CANDIDATE_INTERSECTION_TYPE:
|
||||
case SPV_OPERAND_TYPE_DEBUG_BASE_TYPE_ATTRIBUTE_ENCODING:
|
||||
case SPV_OPERAND_TYPE_DEBUG_COMPOSITE_TYPE:
|
||||
case SPV_OPERAND_TYPE_DEBUG_TYPE_QUALIFIER:
|
||||
case SPV_OPERAND_TYPE_DEBUG_OPERATION:
|
||||
case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_BASE_TYPE_ATTRIBUTE_ENCODING:
|
||||
case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_COMPOSITE_TYPE:
|
||||
case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_TYPE_QUALIFIER:
|
||||
case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_OPERATION:
|
||||
case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_IMPORTED_ENTITY:
|
||||
case SPV_OPERAND_TYPE_FPDENORM_MODE:
|
||||
case SPV_OPERAND_TYPE_FPOPERATION_MODE:
|
||||
case SPV_OPERAND_TYPE_QUANTIZATION_MODES:
|
||||
case SPV_OPERAND_TYPE_OVERFLOW_MODES: {
|
||||
spv_operand_desc entry;
|
||||
if (grammar_.lookupOperand(operand.type, word, &entry))
|
||||
assert(false && "should have caught this earlier");
|
||||
stream_ << entry->name;
|
||||
} break;
|
||||
case SPV_OPERAND_TYPE_FP_FAST_MATH_MODE:
|
||||
case SPV_OPERAND_TYPE_FUNCTION_CONTROL:
|
||||
case SPV_OPERAND_TYPE_LOOP_CONTROL:
|
||||
case SPV_OPERAND_TYPE_IMAGE:
|
||||
case SPV_OPERAND_TYPE_MEMORY_ACCESS:
|
||||
case SPV_OPERAND_TYPE_SELECTION_CONTROL:
|
||||
case SPV_OPERAND_TYPE_DEBUG_INFO_FLAGS:
|
||||
case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_INFO_FLAGS:
|
||||
EmitMaskOperand(operand.type, word);
|
||||
break;
|
||||
default:
|
||||
if (spvOperandIsConcreteMask(operand.type)) {
|
||||
EmitMaskOperand(operand.type, word);
|
||||
} else if (spvOperandIsConcrete(operand.type)) {
|
||||
spv_operand_desc entry;
|
||||
if (grammar_.lookupOperand(operand.type, word, &entry))
|
||||
assert(false && "should have caught this earlier");
|
||||
stream_ << entry->name;
|
||||
} else {
|
||||
assert(false && "unhandled or invalid case");
|
||||
}
|
||||
break;
|
||||
}
|
||||
ResetColor();
|
||||
}
|
||||
|
||||
void Disassembler::EmitMaskOperand(const spv_operand_type_t type,
|
||||
const uint32_t word) {
|
||||
// Scan the mask from least significant bit to most significant bit. For each
|
||||
// set bit, emit the name of that bit. Separate multiple names with '|'.
|
||||
uint32_t remaining_word = word;
|
||||
uint32_t mask;
|
||||
int num_emitted = 0;
|
||||
for (mask = 1; remaining_word; mask <<= 1) {
|
||||
if (remaining_word & mask) {
|
||||
remaining_word ^= mask;
|
||||
spv_operand_desc entry;
|
||||
if (grammar_.lookupOperand(type, mask, &entry))
|
||||
assert(false && "should have caught this earlier");
|
||||
if (num_emitted) stream_ << "|";
|
||||
stream_ << entry->name;
|
||||
num_emitted++;
|
||||
}
|
||||
}
|
||||
if (!num_emitted) {
|
||||
// An operand value of 0 was provided, so represent it by the name
|
||||
// of the 0 value. In many cases, that's "None".
|
||||
spv_operand_desc entry;
|
||||
if (SPV_SUCCESS == grammar_.lookupOperand(type, 0, &entry))
|
||||
stream_ << entry->name;
|
||||
}
|
||||
}
|
||||
|
||||
spv_result_t Disassembler::SaveTextResult(spv_text* text_result) const {
|
||||
if (!print_) {
|
||||
size_t length = text_.str().size();
|
||||
@ -468,8 +194,342 @@ spv_result_t DisassembleTargetInstruction(
|
||||
return SPV_SUCCESS;
|
||||
}
|
||||
|
||||
constexpr int kStandardIndent = 15;
|
||||
} // namespace
|
||||
|
||||
namespace disassemble {
|
||||
InstructionDisassembler::InstructionDisassembler(const AssemblyGrammar& grammar,
|
||||
std::ostream& stream,
|
||||
uint32_t options,
|
||||
NameMapper name_mapper)
|
||||
: grammar_(grammar),
|
||||
stream_(stream),
|
||||
print_(spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_PRINT, options)),
|
||||
color_(spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_COLOR, options)),
|
||||
indent_(spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_INDENT, options)
|
||||
? kStandardIndent
|
||||
: 0),
|
||||
comment_(spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_COMMENT, options)),
|
||||
show_byte_offset_(
|
||||
spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_SHOW_BYTE_OFFSET, options)),
|
||||
name_mapper_(std::move(name_mapper)) {}
|
||||
|
||||
void InstructionDisassembler::EmitHeaderSpirv() { stream_ << "; SPIR-V\n"; }
|
||||
|
||||
void InstructionDisassembler::EmitHeaderVersion(uint32_t version) {
|
||||
stream_ << "; Version: " << SPV_SPIRV_VERSION_MAJOR_PART(version) << "."
|
||||
<< SPV_SPIRV_VERSION_MINOR_PART(version) << "\n";
|
||||
}
|
||||
|
||||
void InstructionDisassembler::EmitHeaderGenerator(uint32_t generator) {
|
||||
const char* generator_tool =
|
||||
spvGeneratorStr(SPV_GENERATOR_TOOL_PART(generator));
|
||||
stream_ << "; Generator: " << generator_tool;
|
||||
// For unknown tools, print the numeric tool value.
|
||||
if (0 == strcmp("Unknown", generator_tool)) {
|
||||
stream_ << "(" << SPV_GENERATOR_TOOL_PART(generator) << ")";
|
||||
}
|
||||
// Print the miscellaneous part of the generator word on the same
|
||||
// line as the tool name.
|
||||
stream_ << "; " << SPV_GENERATOR_MISC_PART(generator) << "\n";
|
||||
}
|
||||
|
||||
void InstructionDisassembler::EmitHeaderIdBound(uint32_t id_bound) {
|
||||
stream_ << "; Bound: " << id_bound << "\n";
|
||||
}
|
||||
|
||||
void InstructionDisassembler::EmitHeaderSchema(uint32_t schema) {
|
||||
stream_ << "; Schema: " << schema << "\n";
|
||||
}
|
||||
|
||||
void InstructionDisassembler::EmitInstruction(
|
||||
const spv_parsed_instruction_t& inst, size_t inst_byte_offset) {
|
||||
auto opcode = static_cast<SpvOp>(inst.opcode);
|
||||
|
||||
if (inst.result_id) {
|
||||
SetBlue();
|
||||
const std::string id_name = name_mapper_(inst.result_id);
|
||||
if (indent_)
|
||||
stream_ << std::setw(std::max(0, indent_ - 3 - int(id_name.size())));
|
||||
stream_ << "%" << id_name;
|
||||
ResetColor();
|
||||
stream_ << " = ";
|
||||
} else {
|
||||
stream_ << std::string(indent_, ' ');
|
||||
}
|
||||
|
||||
stream_ << "Op" << spvOpcodeString(opcode);
|
||||
|
||||
for (uint16_t i = 0; i < inst.num_operands; i++) {
|
||||
const spv_operand_type_t type = inst.operands[i].type;
|
||||
assert(type != SPV_OPERAND_TYPE_NONE);
|
||||
if (type == SPV_OPERAND_TYPE_RESULT_ID) continue;
|
||||
stream_ << " ";
|
||||
EmitOperand(inst, i);
|
||||
}
|
||||
|
||||
if (comment_ && opcode == SpvOpName) {
|
||||
const spv_parsed_operand_t& operand = inst.operands[0];
|
||||
const uint32_t word = inst.words[operand.offset];
|
||||
stream_ << " ; id %" << word;
|
||||
}
|
||||
|
||||
if (show_byte_offset_) {
|
||||
SetGrey();
|
||||
auto saved_flags = stream_.flags();
|
||||
auto saved_fill = stream_.fill();
|
||||
stream_ << " ; 0x" << std::setw(8) << std::hex << std::setfill('0')
|
||||
<< inst_byte_offset;
|
||||
stream_.flags(saved_flags);
|
||||
stream_.fill(saved_fill);
|
||||
ResetColor();
|
||||
}
|
||||
stream_ << "\n";
|
||||
}
|
||||
|
||||
void InstructionDisassembler::EmitSectionComment(
|
||||
const spv_parsed_instruction_t& inst, bool& inserted_decoration_space,
|
||||
bool& inserted_debug_space, bool& inserted_type_space) {
|
||||
auto opcode = static_cast<SpvOp>(inst.opcode);
|
||||
if (comment_ && opcode == SpvOpFunction) {
|
||||
stream_ << std::endl;
|
||||
stream_ << std::string(indent_, ' ');
|
||||
stream_ << "; Function " << name_mapper_(inst.result_id) << std::endl;
|
||||
}
|
||||
if (comment_ && !inserted_decoration_space && spvOpcodeIsDecoration(opcode)) {
|
||||
inserted_decoration_space = true;
|
||||
stream_ << std::endl;
|
||||
stream_ << std::string(indent_, ' ');
|
||||
stream_ << "; Annotations" << std::endl;
|
||||
}
|
||||
if (comment_ && !inserted_debug_space && spvOpcodeIsDebug(opcode)) {
|
||||
inserted_debug_space = true;
|
||||
stream_ << std::endl;
|
||||
stream_ << std::string(indent_, ' ');
|
||||
stream_ << "; Debug Information" << std::endl;
|
||||
}
|
||||
if (comment_ && !inserted_type_space && spvOpcodeGeneratesType(opcode)) {
|
||||
inserted_type_space = true;
|
||||
stream_ << std::endl;
|
||||
stream_ << std::string(indent_, ' ');
|
||||
stream_ << "; Types, variables and constants" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void InstructionDisassembler::EmitOperand(const spv_parsed_instruction_t& inst,
|
||||
const uint16_t operand_index) {
|
||||
assert(operand_index < inst.num_operands);
|
||||
const spv_parsed_operand_t& operand = inst.operands[operand_index];
|
||||
const uint32_t word = inst.words[operand.offset];
|
||||
switch (operand.type) {
|
||||
case SPV_OPERAND_TYPE_RESULT_ID:
|
||||
assert(false && "<result-id> is not supposed to be handled here");
|
||||
SetBlue();
|
||||
stream_ << "%" << name_mapper_(word);
|
||||
break;
|
||||
case SPV_OPERAND_TYPE_ID:
|
||||
case SPV_OPERAND_TYPE_TYPE_ID:
|
||||
case SPV_OPERAND_TYPE_SCOPE_ID:
|
||||
case SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID:
|
||||
SetYellow();
|
||||
stream_ << "%" << name_mapper_(word);
|
||||
break;
|
||||
case SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER: {
|
||||
spv_ext_inst_desc ext_inst;
|
||||
SetRed();
|
||||
if (grammar_.lookupExtInst(inst.ext_inst_type, word, &ext_inst) ==
|
||||
SPV_SUCCESS) {
|
||||
stream_ << ext_inst->name;
|
||||
} else {
|
||||
if (!spvExtInstIsNonSemantic(inst.ext_inst_type)) {
|
||||
assert(false && "should have caught this earlier");
|
||||
} else {
|
||||
// for non-semantic instruction sets we can just print the number
|
||||
stream_ << word;
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case SPV_OPERAND_TYPE_SPEC_CONSTANT_OP_NUMBER: {
|
||||
spv_opcode_desc opcode_desc;
|
||||
if (grammar_.lookupOpcode(SpvOp(word), &opcode_desc))
|
||||
assert(false && "should have caught this earlier");
|
||||
SetRed();
|
||||
stream_ << opcode_desc->name;
|
||||
} break;
|
||||
case SPV_OPERAND_TYPE_LITERAL_INTEGER:
|
||||
case SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER: {
|
||||
SetRed();
|
||||
EmitNumericLiteral(&stream_, inst, operand);
|
||||
ResetColor();
|
||||
} break;
|
||||
case SPV_OPERAND_TYPE_LITERAL_STRING: {
|
||||
stream_ << "\"";
|
||||
SetGreen();
|
||||
|
||||
std::string str = spvDecodeLiteralStringOperand(inst, operand_index);
|
||||
for (char const& c : str) {
|
||||
if (c == '"' || c == '\\') stream_ << '\\';
|
||||
stream_ << c;
|
||||
}
|
||||
ResetColor();
|
||||
stream_ << '"';
|
||||
} break;
|
||||
case SPV_OPERAND_TYPE_CAPABILITY:
|
||||
case SPV_OPERAND_TYPE_SOURCE_LANGUAGE:
|
||||
case SPV_OPERAND_TYPE_EXECUTION_MODEL:
|
||||
case SPV_OPERAND_TYPE_ADDRESSING_MODEL:
|
||||
case SPV_OPERAND_TYPE_MEMORY_MODEL:
|
||||
case SPV_OPERAND_TYPE_EXECUTION_MODE:
|
||||
case SPV_OPERAND_TYPE_STORAGE_CLASS:
|
||||
case SPV_OPERAND_TYPE_DIMENSIONALITY:
|
||||
case SPV_OPERAND_TYPE_SAMPLER_ADDRESSING_MODE:
|
||||
case SPV_OPERAND_TYPE_SAMPLER_FILTER_MODE:
|
||||
case SPV_OPERAND_TYPE_SAMPLER_IMAGE_FORMAT:
|
||||
case SPV_OPERAND_TYPE_FP_ROUNDING_MODE:
|
||||
case SPV_OPERAND_TYPE_LINKAGE_TYPE:
|
||||
case SPV_OPERAND_TYPE_ACCESS_QUALIFIER:
|
||||
case SPV_OPERAND_TYPE_FUNCTION_PARAMETER_ATTRIBUTE:
|
||||
case SPV_OPERAND_TYPE_DECORATION:
|
||||
case SPV_OPERAND_TYPE_BUILT_IN:
|
||||
case SPV_OPERAND_TYPE_GROUP_OPERATION:
|
||||
case SPV_OPERAND_TYPE_KERNEL_ENQ_FLAGS:
|
||||
case SPV_OPERAND_TYPE_KERNEL_PROFILING_INFO:
|
||||
case SPV_OPERAND_TYPE_RAY_FLAGS:
|
||||
case SPV_OPERAND_TYPE_RAY_QUERY_INTERSECTION:
|
||||
case SPV_OPERAND_TYPE_RAY_QUERY_COMMITTED_INTERSECTION_TYPE:
|
||||
case SPV_OPERAND_TYPE_RAY_QUERY_CANDIDATE_INTERSECTION_TYPE:
|
||||
case SPV_OPERAND_TYPE_DEBUG_BASE_TYPE_ATTRIBUTE_ENCODING:
|
||||
case SPV_OPERAND_TYPE_DEBUG_COMPOSITE_TYPE:
|
||||
case SPV_OPERAND_TYPE_DEBUG_TYPE_QUALIFIER:
|
||||
case SPV_OPERAND_TYPE_DEBUG_OPERATION:
|
||||
case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_BASE_TYPE_ATTRIBUTE_ENCODING:
|
||||
case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_COMPOSITE_TYPE:
|
||||
case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_TYPE_QUALIFIER:
|
||||
case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_OPERATION:
|
||||
case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_IMPORTED_ENTITY:
|
||||
case SPV_OPERAND_TYPE_FPDENORM_MODE:
|
||||
case SPV_OPERAND_TYPE_FPOPERATION_MODE:
|
||||
case SPV_OPERAND_TYPE_QUANTIZATION_MODES:
|
||||
case SPV_OPERAND_TYPE_OVERFLOW_MODES: {
|
||||
spv_operand_desc entry;
|
||||
if (grammar_.lookupOperand(operand.type, word, &entry))
|
||||
assert(false && "should have caught this earlier");
|
||||
stream_ << entry->name;
|
||||
} break;
|
||||
case SPV_OPERAND_TYPE_FP_FAST_MATH_MODE:
|
||||
case SPV_OPERAND_TYPE_FUNCTION_CONTROL:
|
||||
case SPV_OPERAND_TYPE_LOOP_CONTROL:
|
||||
case SPV_OPERAND_TYPE_IMAGE:
|
||||
case SPV_OPERAND_TYPE_MEMORY_ACCESS:
|
||||
case SPV_OPERAND_TYPE_SELECTION_CONTROL:
|
||||
case SPV_OPERAND_TYPE_DEBUG_INFO_FLAGS:
|
||||
case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_INFO_FLAGS:
|
||||
EmitMaskOperand(operand.type, word);
|
||||
break;
|
||||
default:
|
||||
if (spvOperandIsConcreteMask(operand.type)) {
|
||||
EmitMaskOperand(operand.type, word);
|
||||
} else if (spvOperandIsConcrete(operand.type)) {
|
||||
spv_operand_desc entry;
|
||||
if (grammar_.lookupOperand(operand.type, word, &entry))
|
||||
assert(false && "should have caught this earlier");
|
||||
stream_ << entry->name;
|
||||
} else {
|
||||
assert(false && "unhandled or invalid case");
|
||||
}
|
||||
break;
|
||||
}
|
||||
ResetColor();
|
||||
}
|
||||
|
||||
void InstructionDisassembler::EmitMaskOperand(const spv_operand_type_t type,
|
||||
const uint32_t word) {
|
||||
// Scan the mask from least significant bit to most significant bit. For each
|
||||
// set bit, emit the name of that bit. Separate multiple names with '|'.
|
||||
uint32_t remaining_word = word;
|
||||
uint32_t mask;
|
||||
int num_emitted = 0;
|
||||
for (mask = 1; remaining_word; mask <<= 1) {
|
||||
if (remaining_word & mask) {
|
||||
remaining_word ^= mask;
|
||||
spv_operand_desc entry;
|
||||
if (grammar_.lookupOperand(type, mask, &entry))
|
||||
assert(false && "should have caught this earlier");
|
||||
if (num_emitted) stream_ << "|";
|
||||
stream_ << entry->name;
|
||||
num_emitted++;
|
||||
}
|
||||
}
|
||||
if (!num_emitted) {
|
||||
// An operand value of 0 was provided, so represent it by the name
|
||||
// of the 0 value. In many cases, that's "None".
|
||||
spv_operand_desc entry;
|
||||
if (SPV_SUCCESS == grammar_.lookupOperand(type, 0, &entry))
|
||||
stream_ << entry->name;
|
||||
}
|
||||
}
|
||||
|
||||
void InstructionDisassembler::ResetColor() {
|
||||
if (color_) stream_ << spvtools::clr::reset{print_};
|
||||
}
|
||||
void InstructionDisassembler::SetGrey() {
|
||||
if (color_) stream_ << spvtools::clr::grey{print_};
|
||||
}
|
||||
void InstructionDisassembler::SetBlue() {
|
||||
if (color_) stream_ << spvtools::clr::blue{print_};
|
||||
}
|
||||
void InstructionDisassembler::SetYellow() {
|
||||
if (color_) stream_ << spvtools::clr::yellow{print_};
|
||||
}
|
||||
void InstructionDisassembler::SetRed() {
|
||||
if (color_) stream_ << spvtools::clr::red{print_};
|
||||
}
|
||||
void InstructionDisassembler::SetGreen() {
|
||||
if (color_) stream_ << spvtools::clr::green{print_};
|
||||
}
|
||||
} // namespace disassemble
|
||||
|
||||
std::string spvInstructionBinaryToText(const spv_target_env env,
|
||||
const uint32_t* instCode,
|
||||
const size_t instWordCount,
|
||||
const uint32_t* code,
|
||||
const size_t wordCount,
|
||||
const uint32_t options) {
|
||||
spv_context context = spvContextCreate(env);
|
||||
const AssemblyGrammar grammar(context);
|
||||
if (!grammar.isValid()) {
|
||||
spvContextDestroy(context);
|
||||
return "";
|
||||
}
|
||||
|
||||
// Generate friendly names for Ids if requested.
|
||||
std::unique_ptr<FriendlyNameMapper> friendly_mapper;
|
||||
NameMapper name_mapper = GetTrivialNameMapper();
|
||||
if (options & SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES) {
|
||||
friendly_mapper = MakeUnique<FriendlyNameMapper>(context, code, wordCount);
|
||||
name_mapper = friendly_mapper->GetNameMapper();
|
||||
}
|
||||
|
||||
// Now disassemble!
|
||||
Disassembler disassembler(grammar, options, name_mapper);
|
||||
WrappedDisassembler wrapped(&disassembler, instCode, instWordCount);
|
||||
spvBinaryParse(context, &wrapped, code, wordCount, DisassembleTargetHeader,
|
||||
DisassembleTargetInstruction, nullptr);
|
||||
|
||||
spv_text text = nullptr;
|
||||
std::string output;
|
||||
if (disassembler.SaveTextResult(&text) == SPV_SUCCESS) {
|
||||
output.assign(text->str, text->str + text->length);
|
||||
// Drop trailing newline characters.
|
||||
while (!output.empty() && output.back() == '\n') output.pop_back();
|
||||
}
|
||||
spvTextDestroy(text);
|
||||
spvContextDestroy(context);
|
||||
|
||||
return output;
|
||||
}
|
||||
} // namespace spvtools
|
||||
|
||||
spv_result_t spvBinaryToText(const spv_const_context context,
|
||||
const uint32_t* code, const size_t wordCount,
|
||||
const uint32_t options, spv_text* pText,
|
||||
@ -493,53 +553,13 @@ spv_result_t spvBinaryToText(const spv_const_context context,
|
||||
}
|
||||
|
||||
// Now disassemble!
|
||||
Disassembler disassembler(grammar, options, name_mapper);
|
||||
if (auto error = spvBinaryParse(&hijack_context, &disassembler, code,
|
||||
wordCount, DisassembleHeader,
|
||||
DisassembleInstruction, pDiagnostic)) {
|
||||
spvtools::Disassembler disassembler(grammar, options, name_mapper);
|
||||
if (auto error =
|
||||
spvBinaryParse(&hijack_context, &disassembler, code, wordCount,
|
||||
spvtools::DisassembleHeader,
|
||||
spvtools::DisassembleInstruction, pDiagnostic)) {
|
||||
return error;
|
||||
}
|
||||
|
||||
return disassembler.SaveTextResult(pText);
|
||||
}
|
||||
|
||||
std::string spvtools::spvInstructionBinaryToText(const spv_target_env env,
|
||||
const uint32_t* instCode,
|
||||
const size_t instWordCount,
|
||||
const uint32_t* code,
|
||||
const size_t wordCount,
|
||||
const uint32_t options) {
|
||||
spv_context context = spvContextCreate(env);
|
||||
const spvtools::AssemblyGrammar grammar(context);
|
||||
if (!grammar.isValid()) {
|
||||
spvContextDestroy(context);
|
||||
return "";
|
||||
}
|
||||
|
||||
// Generate friendly names for Ids if requested.
|
||||
std::unique_ptr<spvtools::FriendlyNameMapper> friendly_mapper;
|
||||
spvtools::NameMapper name_mapper = spvtools::GetTrivialNameMapper();
|
||||
if (options & SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES) {
|
||||
friendly_mapper = spvtools::MakeUnique<spvtools::FriendlyNameMapper>(
|
||||
context, code, wordCount);
|
||||
name_mapper = friendly_mapper->GetNameMapper();
|
||||
}
|
||||
|
||||
// Now disassemble!
|
||||
Disassembler disassembler(grammar, options, name_mapper);
|
||||
WrappedDisassembler wrapped(&disassembler, instCode, instWordCount);
|
||||
spvBinaryParse(context, &wrapped, code, wordCount, DisassembleTargetHeader,
|
||||
DisassembleTargetInstruction, nullptr);
|
||||
|
||||
spv_text text = nullptr;
|
||||
std::string output;
|
||||
if (disassembler.SaveTextResult(&text) == SPV_SUCCESS) {
|
||||
output.assign(text->str, text->str + text->length);
|
||||
// Drop trailing newline characters.
|
||||
while (!output.empty() && output.back() == '\n') output.pop_back();
|
||||
}
|
||||
spvTextDestroy(text);
|
||||
spvContextDestroy(context);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
@ -15,8 +15,10 @@
|
||||
#ifndef SOURCE_DISASSEMBLE_H_
|
||||
#define SOURCE_DISASSEMBLE_H_
|
||||
|
||||
#include <iosfwd>
|
||||
#include <string>
|
||||
|
||||
#include "source/name_mapper.h"
|
||||
#include "spirv-tools/libspirv.h"
|
||||
|
||||
namespace spvtools {
|
||||
@ -33,6 +35,63 @@ std::string spvInstructionBinaryToText(const spv_target_env env,
|
||||
const size_t word_count,
|
||||
const uint32_t options);
|
||||
|
||||
class AssemblyGrammar;
|
||||
namespace disassemble {
|
||||
|
||||
// Shared code with other tools (than the disassembler) that might need to
|
||||
// output disassembly. An InstructionDisassembler instance converts SPIR-V
|
||||
// binary for an instruction to its assembly representation.
|
||||
class InstructionDisassembler {
|
||||
public:
|
||||
InstructionDisassembler(const AssemblyGrammar& grammar, std::ostream& stream,
|
||||
uint32_t options, NameMapper name_mapper);
|
||||
|
||||
// Emits the assembly header for the module.
|
||||
void EmitHeaderSpirv();
|
||||
void EmitHeaderVersion(uint32_t version);
|
||||
void EmitHeaderGenerator(uint32_t generator);
|
||||
void EmitHeaderIdBound(uint32_t id_bound);
|
||||
void EmitHeaderSchema(uint32_t schema);
|
||||
|
||||
// Emits the assembly text for the given instruction.
|
||||
void EmitInstruction(const spv_parsed_instruction_t& inst,
|
||||
size_t inst_byte_offset);
|
||||
|
||||
// Emits a comment between different sections of the module.
|
||||
void EmitSectionComment(const spv_parsed_instruction_t& inst,
|
||||
bool& inserted_decoration_space,
|
||||
bool& inserted_debug_space,
|
||||
bool& inserted_type_space);
|
||||
|
||||
// Resets the output color, if color is turned on.
|
||||
void ResetColor();
|
||||
// Set the output color, if color is turned on.
|
||||
void SetGrey();
|
||||
void SetBlue();
|
||||
void SetYellow();
|
||||
void SetRed();
|
||||
void SetGreen();
|
||||
|
||||
private:
|
||||
// Emits an operand for the given instruction, where the instruction
|
||||
// is at offset words from the start of the binary.
|
||||
void EmitOperand(const spv_parsed_instruction_t& inst,
|
||||
const uint16_t operand_index);
|
||||
|
||||
// Emits a mask expression for the given mask word of the specified type.
|
||||
void EmitMaskOperand(const spv_operand_type_t type, const uint32_t word);
|
||||
|
||||
const spvtools::AssemblyGrammar& grammar_;
|
||||
std::ostream& stream_;
|
||||
const bool print_; // Should we also print to the standard output stream?
|
||||
const bool color_; // Should we print in colour?
|
||||
const int indent_; // How much to indent. 0 means don't indent
|
||||
const int comment_; // Should we comment the source
|
||||
const bool show_byte_offset_; // Should we print byte offset, in hex?
|
||||
spvtools::NameMapper name_mapper_;
|
||||
};
|
||||
|
||||
} // namespace disassemble
|
||||
} // namespace spvtools
|
||||
|
||||
#endif // SOURCE_DISASSEMBLE_H_
|
||||
|
Loading…
Reference in New Issue
Block a user