mirror of
https://github.com/KhronosGroup/SPIRV-Tools
synced 2024-11-21 19:20:07 +00:00
spirv-dis: Add --nested-indent and --reorder-blocks (#5671)
With --nested-indent, the SPIR-V blocks are nested according to the structured control flow. Each OpLabel is nested that much with the contents of the block nested a little more. The blocks are separated by a blank line for better visualization. With --reorder-blocks, the SPIR-V blocks are reordered according to the structured control flow. This is particularly useful with --nested-indent. Note that with --nested-indent, the disassembly does not exactly show the binary as-is, and the instructions may be reordered.
This commit is contained in:
parent
bc28ac7c19
commit
7564e142d6
@ -382,6 +382,11 @@ typedef enum spv_binary_to_text_options_t {
|
||||
SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES = SPV_BIT(6),
|
||||
// Add some comments to the generated assembly
|
||||
SPV_BINARY_TO_TEXT_OPTION_COMMENT = SPV_BIT(7),
|
||||
// Use nested indentation for more readable SPIR-V
|
||||
SPV_BINARY_TO_TEXT_OPTION_NESTED_INDENT = SPV_BIT(8),
|
||||
// Reorder blocks to match the structured control flow of SPIR-V to increase
|
||||
// readability.
|
||||
SPV_BINARY_TO_TEXT_OPTION_REORDER_BLOCKS = SPV_BIT(9),
|
||||
SPV_FORCE_32_BIT_ENUM(spv_binary_to_text_options_t)
|
||||
} spv_binary_to_text_options_t;
|
||||
|
||||
|
@ -24,6 +24,8 @@
|
||||
#include <cstring>
|
||||
#include <iomanip>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <stack>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
@ -43,6 +45,70 @@
|
||||
namespace spvtools {
|
||||
namespace {
|
||||
|
||||
// Indices to ControlFlowGraph's list of blocks from one block to its successors
|
||||
struct BlockSuccessors {
|
||||
// Merge block in OpLoopMerge and OpSelectionMerge
|
||||
uint32_t merge_block_id = 0;
|
||||
// The continue block in OpLoopMerge
|
||||
uint32_t continue_block_id = 0;
|
||||
// The true and false blocks in OpBranchConditional
|
||||
uint32_t true_block_id = 0;
|
||||
uint32_t false_block_id = 0;
|
||||
// The body block of a loop, as specified by OpBranch after a merge
|
||||
// instruction
|
||||
uint32_t body_block_id = 0;
|
||||
// The same-nesting-level block that follows this one, indicated by an
|
||||
// OpBranch with no merge instruction.
|
||||
uint32_t next_block_id = 0;
|
||||
// The cases (including default) of an OpSwitch
|
||||
std::vector<uint32_t> case_block_ids;
|
||||
};
|
||||
|
||||
class ParsedInstruction {
|
||||
public:
|
||||
ParsedInstruction(const spv_parsed_instruction_t* instruction) {
|
||||
// Make a copy of the parsed instruction, including stable memory for its
|
||||
// operands.
|
||||
instruction_ = *instruction;
|
||||
operands_ =
|
||||
std::make_unique<spv_parsed_operand_t[]>(instruction->num_operands);
|
||||
memcpy(operands_.get(), instruction->operands,
|
||||
instruction->num_operands * sizeof(*instruction->operands));
|
||||
instruction_.operands = operands_.get();
|
||||
}
|
||||
|
||||
const spv_parsed_instruction_t* get() const { return &instruction_; }
|
||||
|
||||
private:
|
||||
spv_parsed_instruction_t instruction_;
|
||||
std::unique_ptr<spv_parsed_operand_t[]> operands_;
|
||||
};
|
||||
|
||||
// One block in the CFG
|
||||
struct SingleBlock {
|
||||
// The byte offset in the SPIR-V where the block starts. Used for printing in
|
||||
// a comment.
|
||||
size_t byte_offset;
|
||||
|
||||
// Block instructions
|
||||
std::vector<ParsedInstruction> instructions;
|
||||
|
||||
// Successors of this block
|
||||
BlockSuccessors successors;
|
||||
|
||||
// The nesting level for this block.
|
||||
uint32_t nest_level = 0;
|
||||
bool nest_level_assigned = false;
|
||||
|
||||
// Whether the block was reachable
|
||||
bool reachable = false;
|
||||
};
|
||||
|
||||
// CFG for one function
|
||||
struct ControlFlowGraph {
|
||||
std::vector<SingleBlock> blocks;
|
||||
};
|
||||
|
||||
// A Disassembler instance converts a SPIR-V binary to its assembly
|
||||
// representation.
|
||||
class Disassembler {
|
||||
@ -50,6 +116,10 @@ class Disassembler {
|
||||
Disassembler(const AssemblyGrammar& grammar, uint32_t options,
|
||||
NameMapper name_mapper)
|
||||
: print_(spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_PRINT, options)),
|
||||
nested_indent_(
|
||||
spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_NESTED_INDENT, options)),
|
||||
reorder_blocks_(
|
||||
spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_REORDER_BLOCKS, options)),
|
||||
text_(),
|
||||
out_(print_ ? out_stream() : out_stream(text_)),
|
||||
instruction_disassembler_(grammar, out_.get(), options, name_mapper),
|
||||
@ -70,7 +140,13 @@ class Disassembler {
|
||||
spv_result_t SaveTextResult(spv_text* text_result) const;
|
||||
|
||||
private:
|
||||
void EmitCFG();
|
||||
|
||||
const bool print_; // Should we also print to the standard output stream?
|
||||
const bool nested_indent_; // Should the blocks be indented according to the
|
||||
// control flow structure?
|
||||
const bool
|
||||
reorder_blocks_; // Should the blocks be reordered for readability?
|
||||
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.
|
||||
@ -80,6 +156,9 @@ class Disassembler {
|
||||
bool inserted_decoration_space_ = false;
|
||||
bool inserted_debug_space_ = false;
|
||||
bool inserted_type_space_ = false;
|
||||
|
||||
// The CFG for the current function
|
||||
ControlFlowGraph current_function_cfg_;
|
||||
};
|
||||
|
||||
spv_result_t Disassembler::HandleHeader(spv_endianness_t endian,
|
||||
@ -106,13 +185,336 @@ spv_result_t Disassembler::HandleInstruction(
|
||||
inserted_debug_space_,
|
||||
inserted_type_space_);
|
||||
|
||||
instruction_disassembler_.EmitInstruction(inst, byte_offset_);
|
||||
// When nesting needs to be calculated or when the blocks are reordered, we
|
||||
// have to have the full picture of the CFG first. Defer processing of the
|
||||
// instructions until the entire function is visited. This is not done
|
||||
// without those options (even if simpler) to improve debuggability; for
|
||||
// example to be able to see whatever is parsed so far even if there is a
|
||||
// parse error.
|
||||
if (nested_indent_ || reorder_blocks_) {
|
||||
switch (static_cast<spv::Op>(inst.opcode)) {
|
||||
case spv::Op::OpLabel: {
|
||||
// Add a new block to the CFG
|
||||
SingleBlock new_block;
|
||||
new_block.byte_offset = byte_offset_;
|
||||
new_block.instructions.emplace_back(&inst);
|
||||
current_function_cfg_.blocks.push_back(std::move(new_block));
|
||||
break;
|
||||
}
|
||||
case spv::Op::OpFunctionEnd:
|
||||
// Process the CFG and output the instructions
|
||||
EmitCFG();
|
||||
// Output OpFunctionEnd itself too
|
||||
[[fallthrough]];
|
||||
default:
|
||||
if (!current_function_cfg_.blocks.empty()) {
|
||||
// If in a function, stash the instruction for later.
|
||||
current_function_cfg_.blocks.back().instructions.emplace_back(&inst);
|
||||
} else {
|
||||
// Otherwise emit the instruction right away.
|
||||
instruction_disassembler_.EmitInstruction(inst, byte_offset_);
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
instruction_disassembler_.EmitInstruction(inst, byte_offset_);
|
||||
}
|
||||
|
||||
byte_offset_ += inst.num_words * sizeof(uint32_t);
|
||||
|
||||
return SPV_SUCCESS;
|
||||
}
|
||||
|
||||
// Helper to get the operand of an instruction as an id.
|
||||
uint32_t GetOperand(const spv_parsed_instruction_t* instruction,
|
||||
uint32_t operand) {
|
||||
return instruction->words[instruction->operands[operand].offset];
|
||||
}
|
||||
|
||||
std::unordered_map<uint32_t, uint32_t> BuildControlFlowGraph(
|
||||
ControlFlowGraph& cfg) {
|
||||
std::unordered_map<uint32_t, uint32_t> id_to_index;
|
||||
|
||||
for (size_t index = 0; index < cfg.blocks.size(); ++index) {
|
||||
SingleBlock& block = cfg.blocks[index];
|
||||
|
||||
// For future use, build the ID->index map
|
||||
assert(static_cast<spv::Op>(block.instructions[0].get()->opcode) ==
|
||||
spv::Op::OpLabel);
|
||||
const uint32_t id = block.instructions[0].get()->result_id;
|
||||
|
||||
id_to_index[id] = static_cast<uint32_t>(index);
|
||||
|
||||
// Look for a merge instruction first. The function of OpBranch depends on
|
||||
// that.
|
||||
if (block.instructions.size() >= 3) {
|
||||
const spv_parsed_instruction_t* maybe_merge =
|
||||
block.instructions[block.instructions.size() - 2].get();
|
||||
|
||||
switch (static_cast<spv::Op>(maybe_merge->opcode)) {
|
||||
case spv::Op::OpLoopMerge:
|
||||
block.successors.merge_block_id = GetOperand(maybe_merge, 0);
|
||||
block.successors.continue_block_id = GetOperand(maybe_merge, 1);
|
||||
break;
|
||||
|
||||
case spv::Op::OpSelectionMerge:
|
||||
block.successors.merge_block_id = GetOperand(maybe_merge, 0);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Then look at the last instruction; it must be a branch
|
||||
assert(block.instructions.size() >= 2);
|
||||
|
||||
const spv_parsed_instruction_t* branch = block.instructions.back().get();
|
||||
switch (static_cast<spv::Op>(branch->opcode)) {
|
||||
case spv::Op::OpBranch:
|
||||
if (block.successors.merge_block_id != 0) {
|
||||
block.successors.body_block_id = GetOperand(branch, 0);
|
||||
} else {
|
||||
block.successors.next_block_id = GetOperand(branch, 0);
|
||||
}
|
||||
break;
|
||||
|
||||
case spv::Op::OpBranchConditional:
|
||||
block.successors.true_block_id = GetOperand(branch, 1);
|
||||
block.successors.false_block_id = GetOperand(branch, 2);
|
||||
break;
|
||||
|
||||
case spv::Op::OpSwitch:
|
||||
for (uint32_t case_index = 1; case_index < branch->num_operands;
|
||||
case_index += 2) {
|
||||
block.successors.case_block_ids.push_back(
|
||||
GetOperand(branch, case_index));
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return id_to_index;
|
||||
}
|
||||
|
||||
// Helper to deal with nesting and non-existing ids / previously-assigned
|
||||
// levels. It assigns a given nesting level `level` to the block identified by
|
||||
// `id` (unless that block already has a nesting level assigned).
|
||||
void Nest(ControlFlowGraph& cfg,
|
||||
const std::unordered_map<uint32_t, uint32_t>& id_to_index,
|
||||
uint32_t id, uint32_t level) {
|
||||
if (id == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uint32_t block_index = id_to_index.at(id);
|
||||
SingleBlock& block = cfg.blocks[block_index];
|
||||
|
||||
if (!block.nest_level_assigned) {
|
||||
block.nest_level = level;
|
||||
block.nest_level_assigned = true;
|
||||
}
|
||||
}
|
||||
|
||||
// For a given block, assign nesting level to its successors.
|
||||
void NestSuccessors(ControlFlowGraph& cfg, const SingleBlock& block,
|
||||
const std::unordered_map<uint32_t, uint32_t>& id_to_index) {
|
||||
assert(block.nest_level_assigned);
|
||||
|
||||
// Nest loops as such:
|
||||
//
|
||||
// %loop = OpLabel
|
||||
// OpLoopMerge %merge %cont ...
|
||||
// OpBranch %body
|
||||
// %body = OpLabel
|
||||
// Op...
|
||||
// %cont = OpLabel
|
||||
// Op...
|
||||
// %merge = OpLabel
|
||||
// Op...
|
||||
//
|
||||
// Nest conditional branches as such:
|
||||
//
|
||||
// %header = OpLabel
|
||||
// OpSelectionMerge %merge ...
|
||||
// OpBranchConditional ... %true %false
|
||||
// %true = OpLabel
|
||||
// Op...
|
||||
// %false = OpLabel
|
||||
// Op...
|
||||
// %merge = OpLabel
|
||||
// Op...
|
||||
//
|
||||
// Nest switch/case as such:
|
||||
//
|
||||
// %header = OpLabel
|
||||
// OpSelectionMerge %merge ...
|
||||
// OpSwitch ... %default ... %case0 ... %case1 ...
|
||||
// %default = OpLabel
|
||||
// Op...
|
||||
// %case0 = OpLabel
|
||||
// Op...
|
||||
// %case1 = OpLabel
|
||||
// Op...
|
||||
// ...
|
||||
// %merge = OpLabel
|
||||
// Op...
|
||||
//
|
||||
// The following can be observed:
|
||||
//
|
||||
// - In all cases, the merge block has the same nesting as this block
|
||||
// - The continue block of loops is nested 1 level deeper
|
||||
// - The body/branches/cases are nested 2 levels deeper
|
||||
//
|
||||
// Back branches to the header block, branches to the merge block, etc
|
||||
// are correctly handled by processing the header block first (that is
|
||||
// _this_ block, already processed), then following the above rules
|
||||
// (in the same order) for any block that is not already processed.
|
||||
Nest(cfg, id_to_index, block.successors.merge_block_id, block.nest_level);
|
||||
Nest(cfg, id_to_index, block.successors.continue_block_id,
|
||||
block.nest_level + 1);
|
||||
Nest(cfg, id_to_index, block.successors.true_block_id, block.nest_level + 2);
|
||||
Nest(cfg, id_to_index, block.successors.false_block_id, block.nest_level + 2);
|
||||
Nest(cfg, id_to_index, block.successors.body_block_id, block.nest_level + 2);
|
||||
Nest(cfg, id_to_index, block.successors.next_block_id, block.nest_level);
|
||||
for (uint32_t case_block_id : block.successors.case_block_ids) {
|
||||
Nest(cfg, id_to_index, case_block_id, block.nest_level + 2);
|
||||
}
|
||||
}
|
||||
|
||||
struct StackEntry {
|
||||
// The index of the block (in ControlFlowGraph::blocks) to process.
|
||||
uint32_t block_index;
|
||||
// Whether this is the pre or post visit of the block. Because a post-visit
|
||||
// traversal is needed, the same block is pushed back on the stack on
|
||||
// pre-visit so it can be visited again on post-visit.
|
||||
bool post_visit = false;
|
||||
};
|
||||
|
||||
// Helper to deal with DFS traversal and non-existing ids
|
||||
void VisitSuccesor(std::stack<StackEntry>* dfs_stack,
|
||||
const std::unordered_map<uint32_t, uint32_t>& id_to_index,
|
||||
uint32_t id) {
|
||||
if (id != 0) {
|
||||
dfs_stack->push({id_to_index.at(id), false});
|
||||
}
|
||||
}
|
||||
|
||||
// Given the control flow graph, calculates and returns the reverse post-order
|
||||
// ordering of the blocks. The blocks are then disassembled in that order for
|
||||
// readability.
|
||||
std::vector<uint32_t> OrderBlocks(
|
||||
ControlFlowGraph& cfg,
|
||||
const std::unordered_map<uint32_t, uint32_t>& id_to_index) {
|
||||
std::vector<uint32_t> post_order;
|
||||
|
||||
// Nest level of a function's first block is 0.
|
||||
cfg.blocks[0].nest_level = 0;
|
||||
cfg.blocks[0].nest_level_assigned = true;
|
||||
|
||||
// Stack of block indices as they are visited.
|
||||
std::stack<StackEntry> dfs_stack;
|
||||
dfs_stack.push({0, false});
|
||||
|
||||
std::set<uint32_t> visited;
|
||||
|
||||
while (!dfs_stack.empty()) {
|
||||
const uint32_t block_index = dfs_stack.top().block_index;
|
||||
const bool post_visit = dfs_stack.top().post_visit;
|
||||
dfs_stack.pop();
|
||||
|
||||
// If this is the second time the block is visited, that's the post-order
|
||||
// visit.
|
||||
if (post_visit) {
|
||||
post_order.push_back(block_index);
|
||||
continue;
|
||||
}
|
||||
|
||||
// If already visited, another path got to it first (like a case
|
||||
// fallthrough), avoid reprocessing it.
|
||||
if (visited.count(block_index) > 0) {
|
||||
continue;
|
||||
}
|
||||
visited.insert(block_index);
|
||||
|
||||
// Push it back in the stack for post-order visit
|
||||
dfs_stack.push({block_index, true});
|
||||
|
||||
SingleBlock& block = cfg.blocks[block_index];
|
||||
|
||||
// Assign nest levels of successors right away. The successors are either
|
||||
// nested under this block, or are back or forward edges to blocks outside
|
||||
// this nesting level (no farther than the merge block), whose nesting
|
||||
// levels are already assigned before this block is visited.
|
||||
NestSuccessors(cfg, block, id_to_index);
|
||||
block.reachable = true;
|
||||
|
||||
// The post-order visit yields the order in which the blocks are naturally
|
||||
// ordered _backwards_. So blocks to be ordered last should be visited
|
||||
// first. In other words, they should be pushed to the DFS stack last.
|
||||
VisitSuccesor(&dfs_stack, id_to_index, block.successors.true_block_id);
|
||||
VisitSuccesor(&dfs_stack, id_to_index, block.successors.false_block_id);
|
||||
VisitSuccesor(&dfs_stack, id_to_index, block.successors.body_block_id);
|
||||
VisitSuccesor(&dfs_stack, id_to_index, block.successors.next_block_id);
|
||||
for (uint32_t case_block_id : block.successors.case_block_ids) {
|
||||
VisitSuccesor(&dfs_stack, id_to_index, case_block_id);
|
||||
}
|
||||
VisitSuccesor(&dfs_stack, id_to_index, block.successors.continue_block_id);
|
||||
VisitSuccesor(&dfs_stack, id_to_index, block.successors.merge_block_id);
|
||||
}
|
||||
|
||||
std::vector<uint32_t> order(post_order.rbegin(), post_order.rend());
|
||||
|
||||
// Finally, dump all unreachable blocks at the end
|
||||
for (size_t index = 0; index < cfg.blocks.size(); ++index) {
|
||||
SingleBlock& block = cfg.blocks[index];
|
||||
|
||||
if (!block.reachable) {
|
||||
order.push_back(static_cast<uint32_t>(index));
|
||||
block.nest_level = 0;
|
||||
block.nest_level_assigned = true;
|
||||
}
|
||||
}
|
||||
|
||||
return order;
|
||||
}
|
||||
|
||||
void Disassembler::EmitCFG() {
|
||||
// Build the CFG edges. At the same time, build an ID->block index map to
|
||||
// simplify building the CFG edges.
|
||||
const std::unordered_map<uint32_t, uint32_t> id_to_index =
|
||||
BuildControlFlowGraph(current_function_cfg_);
|
||||
|
||||
// Walk the CFG in reverse post-order to find the best ordering of blocks for
|
||||
// presentation
|
||||
std::vector<uint32_t> block_order =
|
||||
OrderBlocks(current_function_cfg_, id_to_index);
|
||||
assert(block_order.size() == current_function_cfg_.blocks.size());
|
||||
|
||||
// Walk the CFG either in block order or input order based on whether the
|
||||
// reorder_blocks_ option is given.
|
||||
for (uint32_t index = 0; index < current_function_cfg_.blocks.size();
|
||||
++index) {
|
||||
const uint32_t block_index = reorder_blocks_ ? block_order[index] : index;
|
||||
const SingleBlock& block = current_function_cfg_.blocks[block_index];
|
||||
|
||||
// Emit instructions for this block
|
||||
size_t byte_offset = block.byte_offset;
|
||||
assert(block.nest_level_assigned);
|
||||
|
||||
for (const ParsedInstruction& inst : block.instructions) {
|
||||
instruction_disassembler_.EmitInstructionInBlock(*inst.get(), byte_offset,
|
||||
block.nest_level);
|
||||
byte_offset += inst.get()->num_words * sizeof(uint32_t);
|
||||
}
|
||||
}
|
||||
|
||||
current_function_cfg_.blocks.clear();
|
||||
}
|
||||
|
||||
spv_result_t Disassembler::SaveTextResult(spv_text* text_result) const {
|
||||
if (!print_) {
|
||||
size_t length = text_.str().size();
|
||||
@ -214,6 +616,8 @@ uint32_t GetLineLengthWithoutColor(const std::string line) {
|
||||
}
|
||||
|
||||
constexpr int kStandardIndent = 15;
|
||||
constexpr int kBlockNestIndent = 2;
|
||||
constexpr int kBlockBodyIndentOffset = 2;
|
||||
constexpr uint32_t kCommentColumn = 50;
|
||||
} // namespace
|
||||
|
||||
@ -229,6 +633,8 @@ InstructionDisassembler::InstructionDisassembler(const AssemblyGrammar& grammar,
|
||||
indent_(spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_INDENT, options)
|
||||
? kStandardIndent
|
||||
: 0),
|
||||
nested_indent_(
|
||||
spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_NESTED_INDENT, options)),
|
||||
comment_(spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_COMMENT, options)),
|
||||
show_byte_offset_(
|
||||
spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_SHOW_BYTE_OFFSET, options)),
|
||||
@ -265,12 +671,29 @@ void InstructionDisassembler::EmitHeaderSchema(uint32_t schema) {
|
||||
|
||||
void InstructionDisassembler::EmitInstruction(
|
||||
const spv_parsed_instruction_t& inst, size_t inst_byte_offset) {
|
||||
EmitInstructionImpl(inst, inst_byte_offset, 0, false);
|
||||
}
|
||||
|
||||
void InstructionDisassembler::EmitInstructionInBlock(
|
||||
const spv_parsed_instruction_t& inst, size_t inst_byte_offset,
|
||||
uint32_t block_indent) {
|
||||
EmitInstructionImpl(inst, inst_byte_offset, block_indent, true);
|
||||
}
|
||||
|
||||
void InstructionDisassembler::EmitInstructionImpl(
|
||||
const spv_parsed_instruction_t& inst, size_t inst_byte_offset,
|
||||
uint32_t block_indent, bool is_in_block) {
|
||||
auto opcode = static_cast<spv::Op>(inst.opcode);
|
||||
|
||||
// To better align the comments (if any), write the instruction to a line
|
||||
// first so its length can be readily available.
|
||||
std::ostringstream line;
|
||||
|
||||
if (nested_indent_ && opcode == spv::Op::OpLabel) {
|
||||
// Separate the blocks by an empty line to make them easier to separate
|
||||
stream_ << std::endl;
|
||||
}
|
||||
|
||||
if (inst.result_id) {
|
||||
SetBlue();
|
||||
const std::string id_name = name_mapper_(inst.result_id);
|
||||
@ -283,6 +706,17 @@ void InstructionDisassembler::EmitInstruction(
|
||||
line << std::string(indent_, ' ');
|
||||
}
|
||||
|
||||
if (nested_indent_ && is_in_block) {
|
||||
// Output OpLabel at the specified nest level, and instructions inside
|
||||
// blocks nested a little more.
|
||||
uint32_t indent = block_indent;
|
||||
bool body_indent = opcode != spv::Op::OpLabel;
|
||||
|
||||
line << std::string(
|
||||
indent * kBlockNestIndent + (body_indent ? kBlockBodyIndentOffset : 0),
|
||||
' ');
|
||||
}
|
||||
|
||||
line << "Op" << spvOpcodeString(opcode);
|
||||
|
||||
for (uint16_t i = 0; i < inst.num_operands; i++) {
|
||||
@ -386,6 +820,11 @@ void InstructionDisassembler::EmitSectionComment(
|
||||
auto opcode = static_cast<spv::Op>(inst.opcode);
|
||||
if (comment_ && opcode == spv::Op::OpFunction) {
|
||||
stream_ << std::endl;
|
||||
if (nested_indent_) {
|
||||
// Double the empty lines between Function sections since nested_indent_
|
||||
// also separates blocks by a blank.
|
||||
stream_ << std::endl;
|
||||
}
|
||||
stream_ << std::string(indent_, ' ');
|
||||
stream_ << "; Function " << name_mapper_(inst.result_id) << std::endl;
|
||||
}
|
||||
|
@ -58,6 +58,11 @@ class InstructionDisassembler {
|
||||
// Emits the assembly text for the given instruction.
|
||||
void EmitInstruction(const spv_parsed_instruction_t& inst,
|
||||
size_t inst_byte_offset);
|
||||
// Same as EmitInstruction, but only for block instructions (including
|
||||
// OpLabel) and useful for nested indentation. If nested indentation is not
|
||||
// desired, EmitInstruction can still be used for block instructions.
|
||||
void EmitInstructionInBlock(const spv_parsed_instruction_t& inst,
|
||||
size_t inst_byte_offset, uint32_t block_indent);
|
||||
|
||||
// Emits a comment between different sections of the module.
|
||||
void EmitSectionComment(const spv_parsed_instruction_t& inst,
|
||||
@ -82,6 +87,10 @@ class InstructionDisassembler {
|
||||
void SetRed(std::ostream& stream) const;
|
||||
void SetGreen(std::ostream& stream) const;
|
||||
|
||||
void EmitInstructionImpl(const spv_parsed_instruction_t& inst,
|
||||
size_t inst_byte_offset, uint32_t block_indent,
|
||||
bool is_in_block);
|
||||
|
||||
// Emits an operand for the given instruction, where the instruction
|
||||
// is at offset words from the start of the binary.
|
||||
void EmitOperand(std::ostream& stream, const spv_parsed_instruction_t& inst,
|
||||
@ -97,10 +106,11 @@ class InstructionDisassembler {
|
||||
|
||||
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 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 bool nested_indent_; // Whether indentation should indicate nesting
|
||||
const int comment_; // Should we comment the source
|
||||
const bool show_byte_offset_; // Should we print byte offset, in hex?
|
||||
spvtools::NameMapper name_mapper_;
|
||||
|
||||
|
@ -24,7 +24,7 @@
|
||||
|
||||
namespace spvtools {
|
||||
|
||||
// Builds an Module returns the owning IRContext from the given SPIR-V
|
||||
// Builds a Module and returns the owning IRContext from the given SPIR-V
|
||||
// |binary|. |size| specifies number of words in |binary|. The |binary| will be
|
||||
// decoded according to the given target |env|. Returns nullptr if errors occur
|
||||
// and sends the errors to |consumer|. When |extra_line_tracking| is true,
|
||||
@ -41,7 +41,7 @@ std::unique_ptr<opt::IRContext> BuildModule(spv_target_env env,
|
||||
const uint32_t* binary,
|
||||
size_t size);
|
||||
|
||||
// Builds an Module and returns the owning IRContext from the given
|
||||
// Builds a Module and returns the owning IRContext from the given
|
||||
// SPIR-V assembly |text|. The |text| will be encoded according to the given
|
||||
// target |env|. Returns nullptr if errors occur and sends the errors to
|
||||
// |consumer|.
|
||||
|
@ -33,6 +33,7 @@ TEST_P(RoundTripLiteralsTest, Sample) {
|
||||
for (bool endian_swap : kSwapEndians) {
|
||||
EXPECT_THAT(
|
||||
EncodeAndDecodeSuccessfully(GetParam(), SPV_BINARY_TO_TEXT_OPTION_NONE,
|
||||
SPV_TEXT_TO_BINARY_OPTION_NONE,
|
||||
SPV_ENV_UNIVERSAL_1_0, endian_swap),
|
||||
Eq(GetParam()));
|
||||
}
|
||||
@ -68,6 +69,7 @@ TEST_P(RoundTripSpecialCaseLiteralsTest, Sample) {
|
||||
for (bool endian_swap : kSwapEndians) {
|
||||
EXPECT_THAT(EncodeAndDecodeSuccessfully(std::get<0>(GetParam()),
|
||||
SPV_BINARY_TO_TEXT_OPTION_NONE,
|
||||
SPV_TEXT_TO_BINARY_OPTION_NONE,
|
||||
SPV_ENV_UNIVERSAL_1_0, endian_swap),
|
||||
Eq(std::get<1>(GetParam())));
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -41,8 +41,7 @@ TEST_F(NonSemanticRoundTripTest, NonSemanticInsts) {
|
||||
%8 = OpExtInstImport "NonSemantic.Testing.AnotherUnknownExtInstSet"
|
||||
%9 = OpExtInst %4 %8 613874321 %7 %5 %6
|
||||
)";
|
||||
std::string disassembly = EncodeAndDecodeSuccessfully(
|
||||
spirv, SPV_BINARY_TO_TEXT_OPTION_NONE, SPV_ENV_UNIVERSAL_1_0);
|
||||
std::string disassembly = EncodeAndDecodeSuccessfully(spirv);
|
||||
EXPECT_THAT(disassembly, Eq(spirv));
|
||||
}
|
||||
|
||||
|
@ -111,13 +111,15 @@ class TextToBinaryTestBase : public T {
|
||||
std::string EncodeAndDecodeSuccessfully(
|
||||
const std::string& txt,
|
||||
uint32_t disassemble_options = SPV_BINARY_TO_TEXT_OPTION_NONE,
|
||||
uint32_t assemble_options = SPV_TEXT_TO_BINARY_OPTION_NONE,
|
||||
spv_target_env env = SPV_ENV_UNIVERSAL_1_0, bool flip_words = false) {
|
||||
DestroyBinary();
|
||||
DestroyDiagnostic();
|
||||
ScopedContext context(env);
|
||||
disassemble_options |= SPV_BINARY_TO_TEXT_OPTION_NO_HEADER;
|
||||
spv_result_t error = spvTextToBinary(context.context, txt.c_str(),
|
||||
txt.size(), &binary, &diagnostic);
|
||||
spv_result_t error =
|
||||
spvTextToBinaryWithOptions(context.context, txt.c_str(), txt.size(),
|
||||
assemble_options, &binary, &diagnostic);
|
||||
if (error) {
|
||||
spvDiagnosticPrint(diagnostic);
|
||||
spvDiagnosticDestroy(diagnostic);
|
||||
|
@ -55,10 +55,10 @@ TEST_P(OpDecorateSimpleTest, AnySimpleDecoration) {
|
||||
{1, uint32_t(std::get<1>(GetParam()).value())},
|
||||
std::get<1>(GetParam()).operands())));
|
||||
// Also check disassembly.
|
||||
EXPECT_THAT(
|
||||
EncodeAndDecodeSuccessfully(input.str(), SPV_BINARY_TO_TEXT_OPTION_NONE,
|
||||
std::get<0>(GetParam())),
|
||||
Eq(input.str()));
|
||||
EXPECT_THAT(EncodeAndDecodeSuccessfully(
|
||||
input.str(), SPV_BINARY_TO_TEXT_OPTION_NONE,
|
||||
SPV_TEXT_TO_BINARY_OPTION_NONE, std::get<0>(GetParam())),
|
||||
Eq(input.str()));
|
||||
}
|
||||
|
||||
// Like above, but parameters to the decoration are IDs.
|
||||
@ -78,10 +78,10 @@ TEST_P(OpDecorateSimpleIdTest, AnySimpleDecoration) {
|
||||
{1, uint32_t(std::get<1>(GetParam()).value())},
|
||||
std::get<1>(GetParam()).operands())));
|
||||
// Also check disassembly.
|
||||
EXPECT_THAT(
|
||||
EncodeAndDecodeSuccessfully(input.str(), SPV_BINARY_TO_TEXT_OPTION_NONE,
|
||||
std::get<0>(GetParam())),
|
||||
Eq(input.str()));
|
||||
EXPECT_THAT(EncodeAndDecodeSuccessfully(
|
||||
input.str(), SPV_BINARY_TO_TEXT_OPTION_NONE,
|
||||
SPV_TEXT_TO_BINARY_OPTION_NONE, std::get<0>(GetParam())),
|
||||
Eq(input.str()));
|
||||
}
|
||||
|
||||
#define CASE(NAME) spv::Decoration::NAME, #NAME
|
||||
@ -460,10 +460,10 @@ TEST_P(OpMemberDecorateSimpleTest, AnySimpleDecoration) {
|
||||
{1, 42, uint32_t(std::get<1>(GetParam()).value())},
|
||||
std::get<1>(GetParam()).operands())));
|
||||
// Also check disassembly.
|
||||
EXPECT_THAT(
|
||||
EncodeAndDecodeSuccessfully(input.str(), SPV_BINARY_TO_TEXT_OPTION_NONE,
|
||||
std::get<0>(GetParam())),
|
||||
Eq(input.str()));
|
||||
EXPECT_THAT(EncodeAndDecodeSuccessfully(
|
||||
input.str(), SPV_BINARY_TO_TEXT_OPTION_NONE,
|
||||
SPV_TEXT_TO_BINARY_OPTION_NONE, std::get<0>(GetParam())),
|
||||
Eq(input.str()));
|
||||
}
|
||||
|
||||
#define CASE(NAME) spv::Decoration::NAME, #NAME
|
||||
|
@ -35,7 +35,8 @@ using CompositeRoundTripTest = RoundTripTest;
|
||||
TEST_F(CompositeRoundTripTest, Good) {
|
||||
std::string spirv = "%2 = OpCopyLogical %1 %3\n";
|
||||
std::string disassembly = EncodeAndDecodeSuccessfully(
|
||||
spirv, SPV_BINARY_TO_TEXT_OPTION_NONE, SPV_ENV_UNIVERSAL_1_4);
|
||||
spirv, SPV_BINARY_TO_TEXT_OPTION_NONE, SPV_TEXT_TO_BINARY_OPTION_NONE,
|
||||
SPV_ENV_UNIVERSAL_1_4);
|
||||
EXPECT_THAT(disassembly, Eq(spirv));
|
||||
}
|
||||
|
||||
|
@ -130,9 +130,10 @@ TEST_P(ExtensionRoundTripTest, Samples) {
|
||||
EXPECT_THAT(CompiledInstructions(ac.input, env), Eq(ac.expected));
|
||||
|
||||
// Check round trip through the disassembler.
|
||||
EXPECT_THAT(EncodeAndDecodeSuccessfully(ac.input,
|
||||
SPV_BINARY_TO_TEXT_OPTION_NONE, env),
|
||||
Eq(ac.input))
|
||||
EXPECT_THAT(
|
||||
EncodeAndDecodeSuccessfully(ac.input, SPV_BINARY_TO_TEXT_OPTION_NONE,
|
||||
SPV_TEXT_TO_BINARY_OPTION_NONE, env),
|
||||
Eq(ac.input))
|
||||
<< "target env: " << spvTargetEnvDescription(env) << "\n";
|
||||
}
|
||||
|
||||
|
@ -107,7 +107,8 @@ TEST_F(MemoryRoundTripTest, OpPtrEqualGood) {
|
||||
EXPECT_THAT(CompiledInstructions(spirv, SPV_ENV_UNIVERSAL_1_4),
|
||||
Eq(MakeInstruction(spv::Op::OpPtrEqual, {1, 2, 3, 4})));
|
||||
std::string disassembly = EncodeAndDecodeSuccessfully(
|
||||
spirv, SPV_BINARY_TO_TEXT_OPTION_NONE, SPV_ENV_UNIVERSAL_1_4);
|
||||
spirv, SPV_BINARY_TO_TEXT_OPTION_NONE, SPV_TEXT_TO_BINARY_OPTION_NONE,
|
||||
SPV_ENV_UNIVERSAL_1_4);
|
||||
EXPECT_THAT(disassembly, Eq(spirv));
|
||||
}
|
||||
|
||||
@ -124,7 +125,8 @@ TEST_F(MemoryRoundTripTest, OpPtrNotEqualGood) {
|
||||
EXPECT_THAT(CompiledInstructions(spirv, SPV_ENV_UNIVERSAL_1_4),
|
||||
Eq(MakeInstruction(spv::Op::OpPtrNotEqual, {1, 2, 3, 4})));
|
||||
std::string disassembly = EncodeAndDecodeSuccessfully(
|
||||
spirv, SPV_BINARY_TO_TEXT_OPTION_NONE, SPV_ENV_UNIVERSAL_1_4);
|
||||
spirv, SPV_BINARY_TO_TEXT_OPTION_NONE, SPV_TEXT_TO_BINARY_OPTION_NONE,
|
||||
SPV_ENV_UNIVERSAL_1_4);
|
||||
EXPECT_THAT(disassembly, Eq(spirv));
|
||||
}
|
||||
|
||||
@ -141,7 +143,8 @@ TEST_F(MemoryRoundTripTest, OpPtrDiffGood) {
|
||||
EXPECT_THAT(CompiledInstructions(spirv, SPV_ENV_UNIVERSAL_1_4),
|
||||
Eq(MakeInstruction(spv::Op::OpPtrDiff, {1, 2, 3, 4})));
|
||||
std::string disassembly = EncodeAndDecodeSuccessfully(
|
||||
spirv, SPV_BINARY_TO_TEXT_OPTION_NONE, SPV_ENV_UNIVERSAL_1_4);
|
||||
spirv, SPV_BINARY_TO_TEXT_OPTION_NONE, SPV_TEXT_TO_BINARY_OPTION_NONE,
|
||||
SPV_ENV_UNIVERSAL_1_4);
|
||||
EXPECT_THAT(disassembly, Eq(spirv));
|
||||
}
|
||||
|
||||
@ -151,7 +154,8 @@ TEST_F(MemoryRoundTripTest, OpPtrDiffV13Good) {
|
||||
// write tests.
|
||||
std::string spirv = "%2 = OpPtrDiff %1 %3 %4\n";
|
||||
std::string disassembly = EncodeAndDecodeSuccessfully(
|
||||
spirv, SPV_BINARY_TO_TEXT_OPTION_NONE, SPV_ENV_UNIVERSAL_1_4);
|
||||
spirv, SPV_BINARY_TO_TEXT_OPTION_NONE, SPV_TEXT_TO_BINARY_OPTION_NONE,
|
||||
SPV_ENV_UNIVERSAL_1_4);
|
||||
}
|
||||
|
||||
// OpCopyMemory
|
||||
@ -160,8 +164,7 @@ TEST_F(MemoryRoundTripTest, OpCopyMemoryNoMemAccessGood) {
|
||||
std::string spirv = "OpCopyMemory %1 %2\n";
|
||||
EXPECT_THAT(CompiledInstructions(spirv),
|
||||
Eq(MakeInstruction(spv::Op::OpCopyMemory, {1, 2})));
|
||||
std::string disassembly =
|
||||
EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
|
||||
std::string disassembly = EncodeAndDecodeSuccessfully(spirv);
|
||||
EXPECT_THAT(disassembly, Eq(spirv));
|
||||
}
|
||||
|
||||
@ -182,8 +185,7 @@ TEST_F(MemoryRoundTripTest, OpCopyMemoryAccessNoneGood) {
|
||||
std::string spirv = "OpCopyMemory %1 %2 None\n";
|
||||
EXPECT_THAT(CompiledInstructions(spirv),
|
||||
Eq(MakeInstruction(spv::Op::OpCopyMemory, {1, 2, 0})));
|
||||
std::string disassembly =
|
||||
EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
|
||||
std::string disassembly = EncodeAndDecodeSuccessfully(spirv);
|
||||
EXPECT_THAT(disassembly, Eq(spirv));
|
||||
}
|
||||
|
||||
@ -191,8 +193,7 @@ TEST_F(MemoryRoundTripTest, OpCopyMemoryAccessVolatileGood) {
|
||||
std::string spirv = "OpCopyMemory %1 %2 Volatile\n";
|
||||
EXPECT_THAT(CompiledInstructions(spirv),
|
||||
Eq(MakeInstruction(spv::Op::OpCopyMemory, {1, 2, 1})));
|
||||
std::string disassembly =
|
||||
EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
|
||||
std::string disassembly = EncodeAndDecodeSuccessfully(spirv);
|
||||
EXPECT_THAT(disassembly, Eq(spirv));
|
||||
}
|
||||
|
||||
@ -200,8 +201,7 @@ TEST_F(MemoryRoundTripTest, OpCopyMemoryAccessAligned8Good) {
|
||||
std::string spirv = "OpCopyMemory %1 %2 Aligned 8\n";
|
||||
EXPECT_THAT(CompiledInstructions(spirv),
|
||||
Eq(MakeInstruction(spv::Op::OpCopyMemory, {1, 2, 2, 8})));
|
||||
std::string disassembly =
|
||||
EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
|
||||
std::string disassembly = EncodeAndDecodeSuccessfully(spirv);
|
||||
EXPECT_THAT(disassembly, Eq(spirv));
|
||||
}
|
||||
|
||||
@ -209,8 +209,7 @@ TEST_F(MemoryRoundTripTest, OpCopyMemoryAccessNontemporalGood) {
|
||||
std::string spirv = "OpCopyMemory %1 %2 Nontemporal\n";
|
||||
EXPECT_THAT(CompiledInstructions(spirv),
|
||||
Eq(MakeInstruction(spv::Op::OpCopyMemory, {1, 2, 4})));
|
||||
std::string disassembly =
|
||||
EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
|
||||
std::string disassembly = EncodeAndDecodeSuccessfully(spirv);
|
||||
EXPECT_THAT(disassembly, Eq(spirv));
|
||||
}
|
||||
|
||||
@ -218,8 +217,7 @@ TEST_F(MemoryRoundTripTest, OpCopyMemoryAccessAvGood) {
|
||||
std::string spirv = "OpCopyMemory %1 %2 MakePointerAvailable %3\n";
|
||||
EXPECT_THAT(CompiledInstructions(spirv),
|
||||
Eq(MakeInstruction(spv::Op::OpCopyMemory, {1, 2, 8, 3})));
|
||||
std::string disassembly =
|
||||
EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
|
||||
std::string disassembly = EncodeAndDecodeSuccessfully(spirv);
|
||||
EXPECT_THAT(disassembly, Eq(spirv));
|
||||
}
|
||||
|
||||
@ -227,8 +225,7 @@ TEST_F(MemoryRoundTripTest, OpCopyMemoryAccessVisGood) {
|
||||
std::string spirv = "OpCopyMemory %1 %2 MakePointerVisible %3\n";
|
||||
EXPECT_THAT(CompiledInstructions(spirv),
|
||||
Eq(MakeInstruction(spv::Op::OpCopyMemory, {1, 2, 16, 3})));
|
||||
std::string disassembly =
|
||||
EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
|
||||
std::string disassembly = EncodeAndDecodeSuccessfully(spirv);
|
||||
EXPECT_THAT(disassembly, Eq(spirv));
|
||||
}
|
||||
|
||||
@ -236,8 +233,7 @@ TEST_F(MemoryRoundTripTest, OpCopyMemoryAccessNonPrivateGood) {
|
||||
std::string spirv = "OpCopyMemory %1 %2 NonPrivatePointer\n";
|
||||
EXPECT_THAT(CompiledInstructions(spirv),
|
||||
Eq(MakeInstruction(spv::Op::OpCopyMemory, {1, 2, 32})));
|
||||
std::string disassembly =
|
||||
EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
|
||||
std::string disassembly = EncodeAndDecodeSuccessfully(spirv);
|
||||
EXPECT_THAT(disassembly, Eq(spirv));
|
||||
}
|
||||
|
||||
@ -248,8 +244,7 @@ TEST_F(MemoryRoundTripTest, OpCopyMemoryAccessMixedGood) {
|
||||
"MakePointerVisible|NonPrivatePointer 16 %3 %4\n";
|
||||
EXPECT_THAT(CompiledInstructions(spirv),
|
||||
Eq(MakeInstruction(spv::Op::OpCopyMemory, {1, 2, 63, 16, 3, 4})));
|
||||
std::string disassembly =
|
||||
EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
|
||||
std::string disassembly = EncodeAndDecodeSuccessfully(spirv);
|
||||
EXPECT_THAT(disassembly, Eq(spirv));
|
||||
}
|
||||
|
||||
@ -258,8 +253,7 @@ TEST_F(MemoryRoundTripTest, OpCopyMemoryTwoAccessV13Good) {
|
||||
// Note: This will assemble but should not validate for SPIR-V 1.3
|
||||
EXPECT_THAT(CompiledInstructions(spirv, SPV_ENV_UNIVERSAL_1_3),
|
||||
Eq(MakeInstruction(spv::Op::OpCopyMemory, {1, 2, 1, 1})));
|
||||
std::string disassembly =
|
||||
EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
|
||||
std::string disassembly = EncodeAndDecodeSuccessfully(spirv);
|
||||
EXPECT_THAT(disassembly, Eq(spirv));
|
||||
}
|
||||
|
||||
@ -267,8 +261,7 @@ TEST_F(MemoryRoundTripTest, OpCopyMemoryTwoAccessV14Good) {
|
||||
std::string spirv = "OpCopyMemory %1 %2 Volatile Volatile\n";
|
||||
EXPECT_THAT(CompiledInstructions(spirv, SPV_ENV_UNIVERSAL_1_4),
|
||||
Eq(MakeInstruction(spv::Op::OpCopyMemory, {1, 2, 1, 1})));
|
||||
std::string disassembly =
|
||||
EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
|
||||
std::string disassembly = EncodeAndDecodeSuccessfully(spirv);
|
||||
EXPECT_THAT(disassembly, Eq(spirv));
|
||||
}
|
||||
|
||||
@ -280,8 +273,7 @@ TEST_F(MemoryRoundTripTest, OpCopyMemoryTwoAccessMixedV14Good) {
|
||||
EXPECT_THAT(
|
||||
CompiledInstructions(spirv),
|
||||
Eq(MakeInstruction(spv::Op::OpCopyMemory, {1, 2, 21, 3, 42, 16, 4})));
|
||||
std::string disassembly =
|
||||
EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
|
||||
std::string disassembly = EncodeAndDecodeSuccessfully(spirv);
|
||||
EXPECT_THAT(disassembly, Eq(spirv));
|
||||
}
|
||||
|
||||
@ -291,8 +283,7 @@ TEST_F(MemoryRoundTripTest, OpCopyMemorySizedNoMemAccessGood) {
|
||||
std::string spirv = "OpCopyMemorySized %1 %2 %3\n";
|
||||
EXPECT_THAT(CompiledInstructions(spirv),
|
||||
Eq(MakeInstruction(spv::Op::OpCopyMemorySized, {1, 2, 3})));
|
||||
std::string disassembly =
|
||||
EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
|
||||
std::string disassembly = EncodeAndDecodeSuccessfully(spirv);
|
||||
EXPECT_THAT(disassembly, Eq(spirv));
|
||||
}
|
||||
|
||||
@ -313,8 +304,7 @@ TEST_F(MemoryRoundTripTest, OpCopyMemorySizedAccessNoneGood) {
|
||||
std::string spirv = "OpCopyMemorySized %1 %2 %3 None\n";
|
||||
EXPECT_THAT(CompiledInstructions(spirv),
|
||||
Eq(MakeInstruction(spv::Op::OpCopyMemorySized, {1, 2, 3, 0})));
|
||||
std::string disassembly =
|
||||
EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
|
||||
std::string disassembly = EncodeAndDecodeSuccessfully(spirv);
|
||||
EXPECT_THAT(disassembly, Eq(spirv));
|
||||
}
|
||||
|
||||
@ -322,8 +312,7 @@ TEST_F(MemoryRoundTripTest, OpCopyMemorySizedAccessVolatileGood) {
|
||||
std::string spirv = "OpCopyMemorySized %1 %2 %3 Volatile\n";
|
||||
EXPECT_THAT(CompiledInstructions(spirv),
|
||||
Eq(MakeInstruction(spv::Op::OpCopyMemorySized, {1, 2, 3, 1})));
|
||||
std::string disassembly =
|
||||
EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
|
||||
std::string disassembly = EncodeAndDecodeSuccessfully(spirv);
|
||||
EXPECT_THAT(disassembly, Eq(spirv));
|
||||
}
|
||||
|
||||
@ -331,8 +320,7 @@ TEST_F(MemoryRoundTripTest, OpCopyMemorySizedAccessAligned8Good) {
|
||||
std::string spirv = "OpCopyMemorySized %1 %2 %3 Aligned 8\n";
|
||||
EXPECT_THAT(CompiledInstructions(spirv),
|
||||
Eq(MakeInstruction(spv::Op::OpCopyMemorySized, {1, 2, 3, 2, 8})));
|
||||
std::string disassembly =
|
||||
EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
|
||||
std::string disassembly = EncodeAndDecodeSuccessfully(spirv);
|
||||
EXPECT_THAT(disassembly, Eq(spirv));
|
||||
}
|
||||
|
||||
@ -340,8 +328,7 @@ TEST_F(MemoryRoundTripTest, OpCopyMemorySizedAccessNontemporalGood) {
|
||||
std::string spirv = "OpCopyMemorySized %1 %2 %3 Nontemporal\n";
|
||||
EXPECT_THAT(CompiledInstructions(spirv),
|
||||
Eq(MakeInstruction(spv::Op::OpCopyMemorySized, {1, 2, 3, 4})));
|
||||
std::string disassembly =
|
||||
EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
|
||||
std::string disassembly = EncodeAndDecodeSuccessfully(spirv);
|
||||
EXPECT_THAT(disassembly, Eq(spirv));
|
||||
}
|
||||
|
||||
@ -349,8 +336,7 @@ TEST_F(MemoryRoundTripTest, OpCopyMemorySizedAccessAvGood) {
|
||||
std::string spirv = "OpCopyMemorySized %1 %2 %3 MakePointerAvailable %4\n";
|
||||
EXPECT_THAT(CompiledInstructions(spirv),
|
||||
Eq(MakeInstruction(spv::Op::OpCopyMemorySized, {1, 2, 3, 8, 4})));
|
||||
std::string disassembly =
|
||||
EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
|
||||
std::string disassembly = EncodeAndDecodeSuccessfully(spirv);
|
||||
EXPECT_THAT(disassembly, Eq(spirv));
|
||||
}
|
||||
|
||||
@ -359,8 +345,7 @@ TEST_F(MemoryRoundTripTest, OpCopyMemorySizedAccessVisGood) {
|
||||
EXPECT_THAT(
|
||||
CompiledInstructions(spirv),
|
||||
Eq(MakeInstruction(spv::Op::OpCopyMemorySized, {1, 2, 3, 16, 4})));
|
||||
std::string disassembly =
|
||||
EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
|
||||
std::string disassembly = EncodeAndDecodeSuccessfully(spirv);
|
||||
EXPECT_THAT(disassembly, Eq(spirv));
|
||||
}
|
||||
|
||||
@ -368,8 +353,7 @@ TEST_F(MemoryRoundTripTest, OpCopyMemorySizedAccessNonPrivateGood) {
|
||||
std::string spirv = "OpCopyMemorySized %1 %2 %3 NonPrivatePointer\n";
|
||||
EXPECT_THAT(CompiledInstructions(spirv),
|
||||
Eq(MakeInstruction(spv::Op::OpCopyMemorySized, {1, 2, 3, 32})));
|
||||
std::string disassembly =
|
||||
EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
|
||||
std::string disassembly = EncodeAndDecodeSuccessfully(spirv);
|
||||
EXPECT_THAT(disassembly, Eq(spirv));
|
||||
}
|
||||
|
||||
@ -381,8 +365,7 @@ TEST_F(MemoryRoundTripTest, OpCopyMemorySizedAccessMixedGood) {
|
||||
EXPECT_THAT(
|
||||
CompiledInstructions(spirv),
|
||||
Eq(MakeInstruction(spv::Op::OpCopyMemorySized, {1, 2, 3, 63, 16, 4, 5})));
|
||||
std::string disassembly =
|
||||
EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
|
||||
std::string disassembly = EncodeAndDecodeSuccessfully(spirv);
|
||||
EXPECT_THAT(disassembly, Eq(spirv));
|
||||
}
|
||||
|
||||
@ -391,8 +374,7 @@ TEST_F(MemoryRoundTripTest, OpCopyMemorySizedTwoAccessV13Good) {
|
||||
// Note: This will assemble but should not validate for SPIR-V 1.3
|
||||
EXPECT_THAT(CompiledInstructions(spirv, SPV_ENV_UNIVERSAL_1_3),
|
||||
Eq(MakeInstruction(spv::Op::OpCopyMemorySized, {1, 2, 3, 1, 1})));
|
||||
std::string disassembly =
|
||||
EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
|
||||
std::string disassembly = EncodeAndDecodeSuccessfully(spirv);
|
||||
EXPECT_THAT(disassembly, Eq(spirv));
|
||||
}
|
||||
|
||||
@ -400,8 +382,7 @@ TEST_F(MemoryRoundTripTest, OpCopyMemorySizedTwoAccessV14Good) {
|
||||
std::string spirv = "OpCopyMemorySized %1 %2 %3 Volatile Volatile\n";
|
||||
EXPECT_THAT(CompiledInstructions(spirv, SPV_ENV_UNIVERSAL_1_4),
|
||||
Eq(MakeInstruction(spv::Op::OpCopyMemorySized, {1, 2, 3, 1, 1})));
|
||||
std::string disassembly =
|
||||
EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
|
||||
std::string disassembly = EncodeAndDecodeSuccessfully(spirv);
|
||||
EXPECT_THAT(disassembly, Eq(spirv));
|
||||
}
|
||||
|
||||
@ -413,8 +394,7 @@ TEST_F(MemoryRoundTripTest, OpCopyMemorySizedTwoAccessMixedV14Good) {
|
||||
EXPECT_THAT(CompiledInstructions(spirv),
|
||||
Eq(MakeInstruction(spv::Op::OpCopyMemorySized,
|
||||
{1, 2, 3, 21, 4, 42, 16, 5})));
|
||||
std::string disassembly =
|
||||
EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
|
||||
std::string disassembly = EncodeAndDecodeSuccessfully(spirv);
|
||||
EXPECT_THAT(disassembly, Eq(spirv));
|
||||
}
|
||||
|
||||
|
@ -35,42 +35,51 @@ or if the filename is "-", then the binary is read from standard input.
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help Print this help.
|
||||
--version Display disassembler version information.
|
||||
-h, --help Print this help.
|
||||
--version Display disassembler version information.
|
||||
|
||||
-o <filename> Set the output filename.
|
||||
Output goes to standard output if this option is
|
||||
not specified, or if the filename is "-".
|
||||
-o <filename> Set the output filename.
|
||||
Output goes to standard output if this option is
|
||||
not specified, or if the filename is "-".
|
||||
|
||||
--color Force color output. The default when printing to a terminal.
|
||||
Overrides a previous --no-color option.
|
||||
--no-color Don't print in color. Overrides a previous --color option.
|
||||
The default when output goes to something other than a
|
||||
terminal (e.g. a file, a pipe, or a shell redirection).
|
||||
--color Force color output. The default when printing to a terminal.
|
||||
Overrides a previous --no-color option.
|
||||
--no-color Don't print in color. Overrides a previous --color option.
|
||||
The default when output goes to something other than a
|
||||
terminal (e.g. a file, a pipe, or a shell redirection).
|
||||
|
||||
--no-indent Don't indent instructions.
|
||||
--no-indent Don't indent instructions.
|
||||
|
||||
--no-header Don't output the header as leading comments.
|
||||
--no-header Don't output the header as leading comments.
|
||||
|
||||
--raw-id Show raw Id values instead of friendly names.
|
||||
--raw-id Show raw Id values instead of friendly names.
|
||||
|
||||
--offsets Show byte offsets for each instruction.
|
||||
--nested-indent Indentation is adjusted to indicate nesting in structured
|
||||
control flow.
|
||||
|
||||
--comment Add comments to make reading easier
|
||||
--reorder-blocks Reorder blocks to match the structured control flow of SPIR-V.
|
||||
With this option, the order of instructions will no longer
|
||||
match the input binary, but the result will be more readable.
|
||||
|
||||
--offsets Show byte offsets for each instruction.
|
||||
|
||||
--comment Add comments to make reading easier
|
||||
)";
|
||||
|
||||
// clang-format off
|
||||
FLAG_SHORT_bool (h, /* default_value= */ false, /* required= */ false);
|
||||
FLAG_SHORT_string(o, /* default_value= */ "-", /* required= */ false);
|
||||
FLAG_LONG_bool (help, /* default_value= */ false, /* required= */false);
|
||||
FLAG_LONG_bool (version, /* default_value= */ false, /* required= */ false);
|
||||
FLAG_LONG_bool (color, /* default_value= */ false, /* required= */ false);
|
||||
FLAG_LONG_bool (no_color, /* default_value= */ false, /* required= */ false);
|
||||
FLAG_LONG_bool (no_indent, /* default_value= */ false, /* required= */ false);
|
||||
FLAG_LONG_bool (no_header, /* default_value= */ false, /* required= */ false);
|
||||
FLAG_LONG_bool (raw_id, /* default_value= */ false, /* required= */ false);
|
||||
FLAG_LONG_bool (offsets, /* default_value= */ false, /* required= */ false);
|
||||
FLAG_LONG_bool (comment, /* default_value= */ false, /* required= */ false);
|
||||
FLAG_SHORT_bool (h, /* default_value= */ false, /* required= */ false);
|
||||
FLAG_SHORT_string(o, /* default_value= */ "-", /* required= */ false);
|
||||
FLAG_LONG_bool (help, /* default_value= */ false, /* required= */false);
|
||||
FLAG_LONG_bool (version, /* default_value= */ false, /* required= */ false);
|
||||
FLAG_LONG_bool (color, /* default_value= */ false, /* required= */ false);
|
||||
FLAG_LONG_bool (no_color, /* default_value= */ false, /* required= */ false);
|
||||
FLAG_LONG_bool (no_indent, /* default_value= */ false, /* required= */ false);
|
||||
FLAG_LONG_bool (no_header, /* default_value= */ false, /* required= */ false);
|
||||
FLAG_LONG_bool (raw_id, /* default_value= */ false, /* required= */ false);
|
||||
FLAG_LONG_bool (nested_indent, /* default_value= */ false, /* required= */ false);
|
||||
FLAG_LONG_bool (reorder_blocks, /* default_value= */ false, /* required= */ false);
|
||||
FLAG_LONG_bool (offsets, /* default_value= */ false, /* required= */ false);
|
||||
FLAG_LONG_bool (comment, /* default_value= */ false, /* required= */ false);
|
||||
// clang-format on
|
||||
|
||||
static const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_5;
|
||||
@ -120,6 +129,12 @@ int main(int, const char** argv) {
|
||||
if (!flags::raw_id.value())
|
||||
options |= SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES;
|
||||
|
||||
if (flags::nested_indent.value())
|
||||
options |= SPV_BINARY_TO_TEXT_OPTION_NESTED_INDENT;
|
||||
|
||||
if (flags::reorder_blocks.value())
|
||||
options |= SPV_BINARY_TO_TEXT_OPTION_REORDER_BLOCKS;
|
||||
|
||||
if (flags::comment.value()) options |= SPV_BINARY_TO_TEXT_OPTION_COMMENT;
|
||||
|
||||
if (flags::o.value() == "-") {
|
||||
|
Loading…
Reference in New Issue
Block a user