1 // Copyright (c) 2015-2020 The Khronos Group Inc.
2 // Modifications Copyright (C) 2020 Advanced Micro Devices, Inc. All rights
3 // reserved.
4 //
5 // Licensed under the Apache License, Version 2.0 (the "License");
6 // you may not use this file except in compliance with the License.
7 // You may obtain a copy of the License at
8 //
9 // http://www.apache.org/licenses/LICENSE-2.0
10 //
11 // Unless required by applicable law or agreed to in writing, software
12 // distributed under the License is distributed on an "AS IS" BASIS,
13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 // See the License for the specific language governing permissions and
15 // limitations under the License.
16
17 // This file contains a disassembler: It converts a SPIR-V binary
18 // to text.
19
20 #include "source/disassemble.h"
21
22 #include <algorithm>
23 #include <cassert>
24 #include <cstring>
25 #include <iomanip>
26 #include <ios>
27 #include <memory>
28 #include <set>
29 #include <sstream>
30 #include <stack>
31 #include <unordered_map>
32 #include <utility>
33
34 #include "source/assembly_grammar.h"
35 #include "source/binary.h"
36 #include "source/diagnostic.h"
37 #include "source/ext_inst.h"
38 #include "source/opcode.h"
39 #include "source/parsed_operand.h"
40 #include "source/print.h"
41 #include "source/spirv_constant.h"
42 #include "source/spirv_endian.h"
43 #include "source/util/hex_float.h"
44 #include "source/util/make_unique.h"
45 #include "spirv-tools/libspirv.h"
46
47 namespace spvtools {
48 namespace {
49
50 // Indices to ControlFlowGraph's list of blocks from one block to its successors
51 struct BlockSuccessors {
52 // Merge block in OpLoopMerge and OpSelectionMerge
53 uint32_t merge_block_id = 0;
54 // The continue block in OpLoopMerge
55 uint32_t continue_block_id = 0;
56 // The true and false blocks in OpBranchConditional
57 uint32_t true_block_id = 0;
58 uint32_t false_block_id = 0;
59 // The body block of a loop, as specified by OpBranch after a merge
60 // instruction
61 uint32_t body_block_id = 0;
62 // The same-nesting-level block that follows this one, indicated by an
63 // OpBranch with no merge instruction.
64 uint32_t next_block_id = 0;
65 // The cases (including default) of an OpSwitch
66 std::vector<uint32_t> case_block_ids;
67 };
68
69 class ParsedInstruction {
70 public:
ParsedInstruction(const spv_parsed_instruction_t * instruction)71 ParsedInstruction(const spv_parsed_instruction_t* instruction) {
72 // Make a copy of the parsed instruction, including stable memory for its
73 // operands.
74 instruction_ = *instruction;
75 operands_ =
76 std::make_unique<spv_parsed_operand_t[]>(instruction->num_operands);
77 memcpy(operands_.get(), instruction->operands,
78 instruction->num_operands * sizeof(*instruction->operands));
79 instruction_.operands = operands_.get();
80 }
81
get() const82 const spv_parsed_instruction_t* get() const { return &instruction_; }
83
84 private:
85 spv_parsed_instruction_t instruction_;
86 std::unique_ptr<spv_parsed_operand_t[]> operands_;
87 };
88
89 // One block in the CFG
90 struct SingleBlock {
91 // The byte offset in the SPIR-V where the block starts. Used for printing in
92 // a comment.
93 size_t byte_offset;
94
95 // Block instructions
96 std::vector<ParsedInstruction> instructions;
97
98 // Successors of this block
99 BlockSuccessors successors;
100
101 // The nesting level for this block.
102 uint32_t nest_level = 0;
103 bool nest_level_assigned = false;
104
105 // Whether the block was reachable
106 bool reachable = false;
107 };
108
109 // CFG for one function
110 struct ControlFlowGraph {
111 std::vector<SingleBlock> blocks;
112 };
113
114 // A Disassembler instance converts a SPIR-V binary to its assembly
115 // representation.
116 class Disassembler {
117 public:
Disassembler(const AssemblyGrammar & grammar,uint32_t options,NameMapper name_mapper)118 Disassembler(const AssemblyGrammar& grammar, uint32_t options,
119 NameMapper name_mapper)
120 : print_(spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_PRINT, options)),
121 nested_indent_(
122 spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_NESTED_INDENT, options)),
123 reorder_blocks_(
124 spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_REORDER_BLOCKS, options)),
125 text_(),
126 out_(print_ ? out_stream() : out_stream(text_)),
127 instruction_disassembler_(grammar, out_.get(), options, name_mapper),
128 header_(!spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER, options)),
129 byte_offset_(0) {}
130
131 // Emits the assembly header for the module, and sets up internal state
132 // so subsequent callbacks can handle the cases where the entire module
133 // is either big-endian or little-endian.
134 spv_result_t HandleHeader(spv_endianness_t endian, uint32_t version,
135 uint32_t generator, uint32_t id_bound,
136 uint32_t schema);
137 // Emits the assembly text for the given instruction.
138 spv_result_t HandleInstruction(const spv_parsed_instruction_t& inst);
139
140 // If not printing, populates text_result with the accumulated text.
141 // Returns SPV_SUCCESS on success.
142 spv_result_t SaveTextResult(spv_text* text_result) const;
143
144 private:
145 void EmitCFG();
146
147 const bool print_; // Should we also print to the standard output stream?
148 const bool nested_indent_; // Should the blocks be indented according to the
149 // control flow structure?
150 const bool
151 reorder_blocks_; // Should the blocks be reordered for readability?
152 spv_endianness_t endian_; // The detected endianness of the binary.
153 std::stringstream text_; // Captures the text, if not printing.
154 out_stream out_; // The Output stream. Either to text_ or standard output.
155 disassemble::InstructionDisassembler instruction_disassembler_;
156 const bool header_; // Should we output header as the leading comment?
157 size_t byte_offset_; // The number of bytes processed so far.
158 bool inserted_decoration_space_ = false;
159 bool inserted_debug_space_ = false;
160 bool inserted_type_space_ = false;
161
162 // The CFG for the current function
163 ControlFlowGraph current_function_cfg_;
164 };
165
HandleHeader(spv_endianness_t endian,uint32_t version,uint32_t generator,uint32_t id_bound,uint32_t schema)166 spv_result_t Disassembler::HandleHeader(spv_endianness_t endian,
167 uint32_t version, uint32_t generator,
168 uint32_t id_bound, uint32_t schema) {
169 endian_ = endian;
170
171 if (header_) {
172 instruction_disassembler_.EmitHeaderSpirv();
173 instruction_disassembler_.EmitHeaderVersion(version);
174 instruction_disassembler_.EmitHeaderGenerator(generator);
175 instruction_disassembler_.EmitHeaderIdBound(id_bound);
176 instruction_disassembler_.EmitHeaderSchema(schema);
177 }
178
179 byte_offset_ = SPV_INDEX_INSTRUCTION * sizeof(uint32_t);
180
181 return SPV_SUCCESS;
182 }
183
HandleInstruction(const spv_parsed_instruction_t & inst)184 spv_result_t Disassembler::HandleInstruction(
185 const spv_parsed_instruction_t& inst) {
186 instruction_disassembler_.EmitSectionComment(inst, inserted_decoration_space_,
187 inserted_debug_space_,
188 inserted_type_space_);
189
190 // When nesting needs to be calculated or when the blocks are reordered, we
191 // have to have the full picture of the CFG first. Defer processing of the
192 // instructions until the entire function is visited. This is not done
193 // without those options (even if simpler) to improve debuggability; for
194 // example to be able to see whatever is parsed so far even if there is a
195 // parse error.
196 if (nested_indent_ || reorder_blocks_) {
197 switch (static_cast<spv::Op>(inst.opcode)) {
198 case spv::Op::OpLabel: {
199 // Add a new block to the CFG
200 SingleBlock new_block;
201 new_block.byte_offset = byte_offset_;
202 new_block.instructions.emplace_back(&inst);
203 current_function_cfg_.blocks.push_back(std::move(new_block));
204 break;
205 }
206 case spv::Op::OpFunctionEnd:
207 // Process the CFG and output the instructions
208 EmitCFG();
209 // Output OpFunctionEnd itself too
210 [[fallthrough]];
211 default:
212 if (!current_function_cfg_.blocks.empty()) {
213 // If in a function, stash the instruction for later.
214 current_function_cfg_.blocks.back().instructions.emplace_back(&inst);
215 } else {
216 // Otherwise emit the instruction right away.
217 instruction_disassembler_.EmitInstruction(inst, byte_offset_);
218 }
219 break;
220 }
221 } else {
222 instruction_disassembler_.EmitInstruction(inst, byte_offset_);
223 }
224
225 byte_offset_ += inst.num_words * sizeof(uint32_t);
226
227 return SPV_SUCCESS;
228 }
229
230 // Helper to get the operand of an instruction as an id.
GetOperand(const spv_parsed_instruction_t * instruction,uint32_t operand)231 uint32_t GetOperand(const spv_parsed_instruction_t* instruction,
232 uint32_t operand) {
233 return instruction->words[instruction->operands[operand].offset];
234 }
235
BuildControlFlowGraph(ControlFlowGraph & cfg)236 std::unordered_map<uint32_t, uint32_t> BuildControlFlowGraph(
237 ControlFlowGraph& cfg) {
238 std::unordered_map<uint32_t, uint32_t> id_to_index;
239
240 for (size_t index = 0; index < cfg.blocks.size(); ++index) {
241 SingleBlock& block = cfg.blocks[index];
242
243 // For future use, build the ID->index map
244 assert(static_cast<spv::Op>(block.instructions[0].get()->opcode) ==
245 spv::Op::OpLabel);
246 const uint32_t id = block.instructions[0].get()->result_id;
247
248 id_to_index[id] = static_cast<uint32_t>(index);
249
250 // Look for a merge instruction first. The function of OpBranch depends on
251 // that.
252 if (block.instructions.size() >= 3) {
253 const spv_parsed_instruction_t* maybe_merge =
254 block.instructions[block.instructions.size() - 2].get();
255
256 switch (static_cast<spv::Op>(maybe_merge->opcode)) {
257 case spv::Op::OpLoopMerge:
258 block.successors.merge_block_id = GetOperand(maybe_merge, 0);
259 block.successors.continue_block_id = GetOperand(maybe_merge, 1);
260 break;
261
262 case spv::Op::OpSelectionMerge:
263 block.successors.merge_block_id = GetOperand(maybe_merge, 0);
264 break;
265
266 default:
267 break;
268 }
269 }
270
271 // Then look at the last instruction; it must be a branch
272 assert(block.instructions.size() >= 2);
273
274 const spv_parsed_instruction_t* branch = block.instructions.back().get();
275 switch (static_cast<spv::Op>(branch->opcode)) {
276 case spv::Op::OpBranch:
277 if (block.successors.merge_block_id != 0) {
278 block.successors.body_block_id = GetOperand(branch, 0);
279 } else {
280 block.successors.next_block_id = GetOperand(branch, 0);
281 }
282 break;
283
284 case spv::Op::OpBranchConditional:
285 block.successors.true_block_id = GetOperand(branch, 1);
286 block.successors.false_block_id = GetOperand(branch, 2);
287 break;
288
289 case spv::Op::OpSwitch:
290 for (uint32_t case_index = 1; case_index < branch->num_operands;
291 case_index += 2) {
292 block.successors.case_block_ids.push_back(
293 GetOperand(branch, case_index));
294 }
295 break;
296
297 default:
298 break;
299 }
300 }
301
302 return id_to_index;
303 }
304
305 // Helper to deal with nesting and non-existing ids / previously-assigned
306 // levels. It assigns a given nesting level `level` to the block identified by
307 // `id` (unless that block already has a nesting level assigned).
Nest(ControlFlowGraph & cfg,const std::unordered_map<uint32_t,uint32_t> & id_to_index,uint32_t id,uint32_t level)308 void Nest(ControlFlowGraph& cfg,
309 const std::unordered_map<uint32_t, uint32_t>& id_to_index,
310 uint32_t id, uint32_t level) {
311 if (id == 0) {
312 return;
313 }
314
315 const uint32_t block_index = id_to_index.at(id);
316 SingleBlock& block = cfg.blocks[block_index];
317
318 if (!block.nest_level_assigned) {
319 block.nest_level = level;
320 block.nest_level_assigned = true;
321 }
322 }
323
324 // For a given block, assign nesting level to its successors.
NestSuccessors(ControlFlowGraph & cfg,const SingleBlock & block,const std::unordered_map<uint32_t,uint32_t> & id_to_index)325 void NestSuccessors(ControlFlowGraph& cfg, const SingleBlock& block,
326 const std::unordered_map<uint32_t, uint32_t>& id_to_index) {
327 assert(block.nest_level_assigned);
328
329 // Nest loops as such:
330 //
331 // %loop = OpLabel
332 // OpLoopMerge %merge %cont ...
333 // OpBranch %body
334 // %body = OpLabel
335 // Op...
336 // %cont = OpLabel
337 // Op...
338 // %merge = OpLabel
339 // Op...
340 //
341 // Nest conditional branches as such:
342 //
343 // %header = OpLabel
344 // OpSelectionMerge %merge ...
345 // OpBranchConditional ... %true %false
346 // %true = OpLabel
347 // Op...
348 // %false = OpLabel
349 // Op...
350 // %merge = OpLabel
351 // Op...
352 //
353 // Nest switch/case as such:
354 //
355 // %header = OpLabel
356 // OpSelectionMerge %merge ...
357 // OpSwitch ... %default ... %case0 ... %case1 ...
358 // %default = OpLabel
359 // Op...
360 // %case0 = OpLabel
361 // Op...
362 // %case1 = OpLabel
363 // Op...
364 // ...
365 // %merge = OpLabel
366 // Op...
367 //
368 // The following can be observed:
369 //
370 // - In all cases, the merge block has the same nesting as this block
371 // - The continue block of loops is nested 1 level deeper
372 // - The body/branches/cases are nested 2 levels deeper
373 //
374 // Back branches to the header block, branches to the merge block, etc
375 // are correctly handled by processing the header block first (that is
376 // _this_ block, already processed), then following the above rules
377 // (in the same order) for any block that is not already processed.
378 Nest(cfg, id_to_index, block.successors.merge_block_id, block.nest_level);
379 Nest(cfg, id_to_index, block.successors.continue_block_id,
380 block.nest_level + 1);
381 Nest(cfg, id_to_index, block.successors.true_block_id, block.nest_level + 2);
382 Nest(cfg, id_to_index, block.successors.false_block_id, block.nest_level + 2);
383 Nest(cfg, id_to_index, block.successors.body_block_id, block.nest_level + 2);
384 Nest(cfg, id_to_index, block.successors.next_block_id, block.nest_level);
385 for (uint32_t case_block_id : block.successors.case_block_ids) {
386 Nest(cfg, id_to_index, case_block_id, block.nest_level + 2);
387 }
388 }
389
390 struct StackEntry {
391 // The index of the block (in ControlFlowGraph::blocks) to process.
392 uint32_t block_index;
393 // Whether this is the pre or post visit of the block. Because a post-visit
394 // traversal is needed, the same block is pushed back on the stack on
395 // pre-visit so it can be visited again on post-visit.
396 bool post_visit = false;
397 };
398
399 // Helper to deal with DFS traversal and non-existing ids
VisitSuccesor(std::stack<StackEntry> * dfs_stack,const std::unordered_map<uint32_t,uint32_t> & id_to_index,uint32_t id)400 void VisitSuccesor(std::stack<StackEntry>* dfs_stack,
401 const std::unordered_map<uint32_t, uint32_t>& id_to_index,
402 uint32_t id) {
403 if (id != 0) {
404 dfs_stack->push({id_to_index.at(id), false});
405 }
406 }
407
408 // Given the control flow graph, calculates and returns the reverse post-order
409 // ordering of the blocks. The blocks are then disassembled in that order for
410 // readability.
OrderBlocks(ControlFlowGraph & cfg,const std::unordered_map<uint32_t,uint32_t> & id_to_index)411 std::vector<uint32_t> OrderBlocks(
412 ControlFlowGraph& cfg,
413 const std::unordered_map<uint32_t, uint32_t>& id_to_index) {
414 std::vector<uint32_t> post_order;
415
416 // Nest level of a function's first block is 0.
417 cfg.blocks[0].nest_level = 0;
418 cfg.blocks[0].nest_level_assigned = true;
419
420 // Stack of block indices as they are visited.
421 std::stack<StackEntry> dfs_stack;
422 dfs_stack.push({0, false});
423
424 std::set<uint32_t> visited;
425
426 while (!dfs_stack.empty()) {
427 const uint32_t block_index = dfs_stack.top().block_index;
428 const bool post_visit = dfs_stack.top().post_visit;
429 dfs_stack.pop();
430
431 // If this is the second time the block is visited, that's the post-order
432 // visit.
433 if (post_visit) {
434 post_order.push_back(block_index);
435 continue;
436 }
437
438 // If already visited, another path got to it first (like a case
439 // fallthrough), avoid reprocessing it.
440 if (visited.count(block_index) > 0) {
441 continue;
442 }
443 visited.insert(block_index);
444
445 // Push it back in the stack for post-order visit
446 dfs_stack.push({block_index, true});
447
448 SingleBlock& block = cfg.blocks[block_index];
449
450 // Assign nest levels of successors right away. The successors are either
451 // nested under this block, or are back or forward edges to blocks outside
452 // this nesting level (no farther than the merge block), whose nesting
453 // levels are already assigned before this block is visited.
454 NestSuccessors(cfg, block, id_to_index);
455 block.reachable = true;
456
457 // The post-order visit yields the order in which the blocks are naturally
458 // ordered _backwards_. So blocks to be ordered last should be visited
459 // first. In other words, they should be pushed to the DFS stack last.
460 VisitSuccesor(&dfs_stack, id_to_index, block.successors.true_block_id);
461 VisitSuccesor(&dfs_stack, id_to_index, block.successors.false_block_id);
462 VisitSuccesor(&dfs_stack, id_to_index, block.successors.body_block_id);
463 VisitSuccesor(&dfs_stack, id_to_index, block.successors.next_block_id);
464 for (uint32_t case_block_id : block.successors.case_block_ids) {
465 VisitSuccesor(&dfs_stack, id_to_index, case_block_id);
466 }
467 VisitSuccesor(&dfs_stack, id_to_index, block.successors.continue_block_id);
468 VisitSuccesor(&dfs_stack, id_to_index, block.successors.merge_block_id);
469 }
470
471 std::vector<uint32_t> order(post_order.rbegin(), post_order.rend());
472
473 // Finally, dump all unreachable blocks at the end
474 for (size_t index = 0; index < cfg.blocks.size(); ++index) {
475 SingleBlock& block = cfg.blocks[index];
476
477 if (!block.reachable) {
478 order.push_back(static_cast<uint32_t>(index));
479 block.nest_level = 0;
480 block.nest_level_assigned = true;
481 }
482 }
483
484 return order;
485 }
486
EmitCFG()487 void Disassembler::EmitCFG() {
488 // Build the CFG edges. At the same time, build an ID->block index map to
489 // simplify building the CFG edges.
490 const std::unordered_map<uint32_t, uint32_t> id_to_index =
491 BuildControlFlowGraph(current_function_cfg_);
492
493 // Walk the CFG in reverse post-order to find the best ordering of blocks for
494 // presentation
495 std::vector<uint32_t> block_order =
496 OrderBlocks(current_function_cfg_, id_to_index);
497 assert(block_order.size() == current_function_cfg_.blocks.size());
498
499 // Walk the CFG either in block order or input order based on whether the
500 // reorder_blocks_ option is given.
501 for (uint32_t index = 0; index < current_function_cfg_.blocks.size();
502 ++index) {
503 const uint32_t block_index = reorder_blocks_ ? block_order[index] : index;
504 const SingleBlock& block = current_function_cfg_.blocks[block_index];
505
506 // Emit instructions for this block
507 size_t byte_offset = block.byte_offset;
508 assert(block.nest_level_assigned);
509
510 for (const ParsedInstruction& inst : block.instructions) {
511 instruction_disassembler_.EmitInstructionInBlock(*inst.get(), byte_offset,
512 block.nest_level);
513 byte_offset += inst.get()->num_words * sizeof(uint32_t);
514 }
515 }
516
517 current_function_cfg_.blocks.clear();
518 }
519
SaveTextResult(spv_text * text_result) const520 spv_result_t Disassembler::SaveTextResult(spv_text* text_result) const {
521 if (!print_) {
522 size_t length = text_.str().size();
523 char* str = new char[length + 1];
524 if (!str) return SPV_ERROR_OUT_OF_MEMORY;
525 strncpy(str, text_.str().c_str(), length + 1);
526 spv_text text = new spv_text_t();
527 if (!text) {
528 delete[] str;
529 return SPV_ERROR_OUT_OF_MEMORY;
530 }
531 text->str = str;
532 text->length = length;
533 *text_result = text;
534 }
535 return SPV_SUCCESS;
536 }
537
DisassembleHeader(void * user_data,spv_endianness_t endian,uint32_t,uint32_t version,uint32_t generator,uint32_t id_bound,uint32_t schema)538 spv_result_t DisassembleHeader(void* user_data, spv_endianness_t endian,
539 uint32_t /* magic */, uint32_t version,
540 uint32_t generator, uint32_t id_bound,
541 uint32_t schema) {
542 assert(user_data);
543 auto disassembler = static_cast<Disassembler*>(user_data);
544 return disassembler->HandleHeader(endian, version, generator, id_bound,
545 schema);
546 }
547
DisassembleInstruction(void * user_data,const spv_parsed_instruction_t * parsed_instruction)548 spv_result_t DisassembleInstruction(
549 void* user_data, const spv_parsed_instruction_t* parsed_instruction) {
550 assert(user_data);
551 auto disassembler = static_cast<Disassembler*>(user_data);
552 return disassembler->HandleInstruction(*parsed_instruction);
553 }
554
555 // Simple wrapper class to provide extra data necessary for targeted
556 // instruction disassembly.
557 class WrappedDisassembler {
558 public:
WrappedDisassembler(Disassembler * dis,const uint32_t * binary,size_t wc)559 WrappedDisassembler(Disassembler* dis, const uint32_t* binary, size_t wc)
560 : disassembler_(dis), inst_binary_(binary), word_count_(wc) {}
561
disassembler()562 Disassembler* disassembler() { return disassembler_; }
inst_binary() const563 const uint32_t* inst_binary() const { return inst_binary_; }
word_count() const564 size_t word_count() const { return word_count_; }
565
566 private:
567 Disassembler* disassembler_;
568 const uint32_t* inst_binary_;
569 const size_t word_count_;
570 };
571
DisassembleTargetHeader(void * user_data,spv_endianness_t endian,uint32_t,uint32_t version,uint32_t generator,uint32_t id_bound,uint32_t schema)572 spv_result_t DisassembleTargetHeader(void* user_data, spv_endianness_t endian,
573 uint32_t /* magic */, uint32_t version,
574 uint32_t generator, uint32_t id_bound,
575 uint32_t schema) {
576 assert(user_data);
577 auto wrapped = static_cast<WrappedDisassembler*>(user_data);
578 return wrapped->disassembler()->HandleHeader(endian, version, generator,
579 id_bound, schema);
580 }
581
DisassembleTargetInstruction(void * user_data,const spv_parsed_instruction_t * parsed_instruction)582 spv_result_t DisassembleTargetInstruction(
583 void* user_data, const spv_parsed_instruction_t* parsed_instruction) {
584 assert(user_data);
585 auto wrapped = static_cast<WrappedDisassembler*>(user_data);
586 // Check if this is the instruction we want to disassemble.
587 if (wrapped->word_count() == parsed_instruction->num_words &&
588 std::equal(wrapped->inst_binary(),
589 wrapped->inst_binary() + wrapped->word_count(),
590 parsed_instruction->words)) {
591 // Found the target instruction. Disassemble it and signal that we should
592 // stop searching so we don't output the same instruction again.
593 if (auto error =
594 wrapped->disassembler()->HandleInstruction(*parsed_instruction))
595 return error;
596 return SPV_REQUESTED_TERMINATION;
597 }
598 return SPV_SUCCESS;
599 }
600
GetLineLengthWithoutColor(const std::string line)601 uint32_t GetLineLengthWithoutColor(const std::string line) {
602 // Currently, every added color is in the form \x1b...m, so instead of doing a
603 // lot of string comparisons with spvtools::clr::* strings, we just ignore
604 // those ranges.
605 uint32_t length = 0;
606 for (size_t i = 0; i < line.size(); ++i) {
607 if (line[i] == '\x1b') {
608 do {
609 ++i;
610 } while (i < line.size() && line[i] != 'm');
611 continue;
612 }
613
614 ++length;
615 }
616
617 return length;
618 }
619
620 constexpr int kStandardIndent = 15;
621 constexpr int kBlockNestIndent = 2;
622 constexpr int kBlockBodyIndentOffset = 2;
623 constexpr uint32_t kCommentColumn = 50;
624 } // namespace
625
626 namespace disassemble {
InstructionDisassembler(const AssemblyGrammar & grammar,std::ostream & stream,uint32_t options,NameMapper name_mapper)627 InstructionDisassembler::InstructionDisassembler(const AssemblyGrammar& grammar,
628 std::ostream& stream,
629 uint32_t options,
630 NameMapper name_mapper)
631 : grammar_(grammar),
632 stream_(stream),
633 print_(spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_PRINT, options)),
634 color_(spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_COLOR, options)),
635 indent_(spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_INDENT, options)
636 ? kStandardIndent
637 : 0),
638 nested_indent_(
639 spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_NESTED_INDENT, options)),
640 comment_(spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_COMMENT, options)),
641 show_byte_offset_(
642 spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_SHOW_BYTE_OFFSET, options)),
643 name_mapper_(std::move(name_mapper)),
644 last_instruction_comment_alignment_(0) {}
645
EmitHeaderSpirv()646 void InstructionDisassembler::EmitHeaderSpirv() { stream_ << "; SPIR-V\n"; }
647
EmitHeaderVersion(uint32_t version)648 void InstructionDisassembler::EmitHeaderVersion(uint32_t version) {
649 stream_ << "; Version: " << SPV_SPIRV_VERSION_MAJOR_PART(version) << "."
650 << SPV_SPIRV_VERSION_MINOR_PART(version) << "\n";
651 }
652
EmitHeaderGenerator(uint32_t generator)653 void InstructionDisassembler::EmitHeaderGenerator(uint32_t generator) {
654 const char* generator_tool =
655 spvGeneratorStr(SPV_GENERATOR_TOOL_PART(generator));
656 stream_ << "; Generator: " << generator_tool;
657 // For unknown tools, print the numeric tool value.
658 if (0 == strcmp("Unknown", generator_tool)) {
659 stream_ << "(" << SPV_GENERATOR_TOOL_PART(generator) << ")";
660 }
661 // Print the miscellaneous part of the generator word on the same
662 // line as the tool name.
663 stream_ << "; " << SPV_GENERATOR_MISC_PART(generator) << "\n";
664 }
665
EmitHeaderIdBound(uint32_t id_bound)666 void InstructionDisassembler::EmitHeaderIdBound(uint32_t id_bound) {
667 stream_ << "; Bound: " << id_bound << "\n";
668 }
669
EmitHeaderSchema(uint32_t schema)670 void InstructionDisassembler::EmitHeaderSchema(uint32_t schema) {
671 stream_ << "; Schema: " << schema << "\n";
672 }
673
EmitInstruction(const spv_parsed_instruction_t & inst,size_t inst_byte_offset)674 void InstructionDisassembler::EmitInstruction(
675 const spv_parsed_instruction_t& inst, size_t inst_byte_offset) {
676 EmitInstructionImpl(inst, inst_byte_offset, 0, false);
677 }
678
EmitInstructionInBlock(const spv_parsed_instruction_t & inst,size_t inst_byte_offset,uint32_t block_indent)679 void InstructionDisassembler::EmitInstructionInBlock(
680 const spv_parsed_instruction_t& inst, size_t inst_byte_offset,
681 uint32_t block_indent) {
682 EmitInstructionImpl(inst, inst_byte_offset, block_indent, true);
683 }
684
EmitInstructionImpl(const spv_parsed_instruction_t & inst,size_t inst_byte_offset,uint32_t block_indent,bool is_in_block)685 void InstructionDisassembler::EmitInstructionImpl(
686 const spv_parsed_instruction_t& inst, size_t inst_byte_offset,
687 uint32_t block_indent, bool is_in_block) {
688 auto opcode = static_cast<spv::Op>(inst.opcode);
689
690 // To better align the comments (if any), write the instruction to a line
691 // first so its length can be readily available.
692 std::ostringstream line;
693
694 if (nested_indent_ && opcode == spv::Op::OpLabel) {
695 // Separate the blocks by an empty line to make them easier to separate
696 stream_ << std::endl;
697 }
698
699 if (inst.result_id) {
700 SetBlue();
701 const std::string id_name = name_mapper_(inst.result_id);
702 if (indent_)
703 line << std::setw(std::max(0, indent_ - 3 - int(id_name.size())));
704 line << "%" << id_name;
705 ResetColor();
706 line << " = ";
707 } else {
708 line << std::string(indent_, ' ');
709 }
710
711 if (nested_indent_ && is_in_block) {
712 // Output OpLabel at the specified nest level, and instructions inside
713 // blocks nested a little more.
714 uint32_t indent = block_indent;
715 bool body_indent = opcode != spv::Op::OpLabel;
716
717 line << std::string(
718 indent * kBlockNestIndent + (body_indent ? kBlockBodyIndentOffset : 0),
719 ' ');
720 }
721
722 line << "Op" << spvOpcodeString(opcode);
723
724 for (uint16_t i = 0; i < inst.num_operands; i++) {
725 const spv_operand_type_t type = inst.operands[i].type;
726 assert(type != SPV_OPERAND_TYPE_NONE);
727 if (type == SPV_OPERAND_TYPE_RESULT_ID) continue;
728 line << " ";
729 EmitOperand(line, inst, i);
730 }
731
732 // For the sake of comment generation, store information from some
733 // instructions for the future.
734 if (comment_) {
735 GenerateCommentForDecoratedId(inst);
736 }
737
738 std::ostringstream comments;
739 const char* comment_separator = "";
740
741 if (show_byte_offset_) {
742 SetGrey(comments);
743 auto saved_flags = comments.flags();
744 auto saved_fill = comments.fill();
745 comments << comment_separator << "0x" << std::setw(8) << std::hex
746 << std::setfill('0') << inst_byte_offset;
747 comments.flags(saved_flags);
748 comments.fill(saved_fill);
749 ResetColor(comments);
750 comment_separator = ", ";
751 }
752
753 if (comment_ && opcode == spv::Op::OpName) {
754 const spv_parsed_operand_t& operand = inst.operands[0];
755 const uint32_t word = inst.words[operand.offset];
756 comments << comment_separator << "id %" << word;
757 comment_separator = ", ";
758 }
759
760 if (comment_ && inst.result_id && id_comments_.count(inst.result_id) > 0) {
761 comments << comment_separator << id_comments_[inst.result_id].str();
762 comment_separator = ", ";
763 }
764
765 stream_ << line.str();
766
767 if (!comments.str().empty()) {
768 // Align the comments
769 const uint32_t line_length = GetLineLengthWithoutColor(line.str());
770 uint32_t align = std::max(
771 {line_length + 2, last_instruction_comment_alignment_, kCommentColumn});
772 // Round up the alignment to a multiple of 4 for more niceness.
773 align = (align + 3) & ~0x3u;
774 last_instruction_comment_alignment_ = align;
775
776 stream_ << std::string(align - line_length, ' ') << "; " << comments.str();
777 } else {
778 last_instruction_comment_alignment_ = 0;
779 }
780
781 stream_ << "\n";
782 }
783
GenerateCommentForDecoratedId(const spv_parsed_instruction_t & inst)784 void InstructionDisassembler::GenerateCommentForDecoratedId(
785 const spv_parsed_instruction_t& inst) {
786 assert(comment_);
787 auto opcode = static_cast<spv::Op>(inst.opcode);
788
789 std::ostringstream partial;
790 uint32_t id = 0;
791 const char* separator = "";
792
793 switch (opcode) {
794 case spv::Op::OpDecorate:
795 // Take everything after `OpDecorate %id` and associate it with id.
796 id = inst.words[inst.operands[0].offset];
797 for (uint16_t i = 1; i < inst.num_operands; i++) {
798 partial << separator;
799 separator = " ";
800 EmitOperand(partial, inst, i);
801 }
802 break;
803 default:
804 break;
805 }
806
807 if (id == 0) {
808 return;
809 }
810
811 // Add the new comment to the comments of this id
812 std::ostringstream& id_comment = id_comments_[id];
813 if (!id_comment.str().empty()) {
814 id_comment << ", ";
815 }
816 id_comment << partial.str();
817 }
818
EmitSectionComment(const spv_parsed_instruction_t & inst,bool & inserted_decoration_space,bool & inserted_debug_space,bool & inserted_type_space)819 void InstructionDisassembler::EmitSectionComment(
820 const spv_parsed_instruction_t& inst, bool& inserted_decoration_space,
821 bool& inserted_debug_space, bool& inserted_type_space) {
822 auto opcode = static_cast<spv::Op>(inst.opcode);
823 if (comment_ && opcode == spv::Op::OpFunction) {
824 stream_ << std::endl;
825 if (nested_indent_) {
826 // Double the empty lines between Function sections since nested_indent_
827 // also separates blocks by a blank.
828 stream_ << std::endl;
829 }
830 stream_ << std::string(indent_, ' ');
831 stream_ << "; Function " << name_mapper_(inst.result_id) << std::endl;
832 }
833 if (comment_ && !inserted_decoration_space && spvOpcodeIsDecoration(opcode)) {
834 inserted_decoration_space = true;
835 stream_ << std::endl;
836 stream_ << std::string(indent_, ' ');
837 stream_ << "; Annotations" << std::endl;
838 }
839 if (comment_ && !inserted_debug_space && spvOpcodeIsDebug(opcode)) {
840 inserted_debug_space = true;
841 stream_ << std::endl;
842 stream_ << std::string(indent_, ' ');
843 stream_ << "; Debug Information" << std::endl;
844 }
845 if (comment_ && !inserted_type_space && spvOpcodeGeneratesType(opcode)) {
846 inserted_type_space = true;
847 stream_ << std::endl;
848 stream_ << std::string(indent_, ' ');
849 stream_ << "; Types, variables and constants" << std::endl;
850 }
851 }
852
EmitOperand(std::ostream & stream,const spv_parsed_instruction_t & inst,const uint16_t operand_index) const853 void InstructionDisassembler::EmitOperand(std::ostream& stream,
854 const spv_parsed_instruction_t& inst,
855 const uint16_t operand_index) const {
856 assert(operand_index < inst.num_operands);
857 const spv_parsed_operand_t& operand = inst.operands[operand_index];
858 const uint32_t word = inst.words[operand.offset];
859 switch (operand.type) {
860 case SPV_OPERAND_TYPE_RESULT_ID:
861 assert(false && "<result-id> is not supposed to be handled here");
862 SetBlue(stream);
863 stream << "%" << name_mapper_(word);
864 break;
865 case SPV_OPERAND_TYPE_ID:
866 case SPV_OPERAND_TYPE_TYPE_ID:
867 case SPV_OPERAND_TYPE_SCOPE_ID:
868 case SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID:
869 SetYellow(stream);
870 stream << "%" << name_mapper_(word);
871 break;
872 case SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER: {
873 spv_ext_inst_desc ext_inst;
874 SetRed(stream);
875 if (grammar_.lookupExtInst(inst.ext_inst_type, word, &ext_inst) ==
876 SPV_SUCCESS) {
877 stream << ext_inst->name;
878 } else {
879 if (!spvExtInstIsNonSemantic(inst.ext_inst_type)) {
880 assert(false && "should have caught this earlier");
881 } else {
882 // for non-semantic instruction sets we can just print the number
883 stream << word;
884 }
885 }
886 } break;
887 case SPV_OPERAND_TYPE_SPEC_CONSTANT_OP_NUMBER: {
888 spv_opcode_desc opcode_desc;
889 if (grammar_.lookupOpcode(spv::Op(word), &opcode_desc))
890 assert(false && "should have caught this earlier");
891 SetRed(stream);
892 stream << opcode_desc->name;
893 } break;
894 case SPV_OPERAND_TYPE_LITERAL_INTEGER:
895 case SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER:
896 case SPV_OPERAND_TYPE_LITERAL_FLOAT: {
897 SetRed(stream);
898 EmitNumericLiteral(&stream, inst, operand);
899 ResetColor(stream);
900 } break;
901 case SPV_OPERAND_TYPE_LITERAL_STRING: {
902 stream << "\"";
903 SetGreen(stream);
904
905 std::string str = spvDecodeLiteralStringOperand(inst, operand_index);
906 for (char const& c : str) {
907 if (c == '"' || c == '\\') stream << '\\';
908 stream << c;
909 }
910 ResetColor(stream);
911 stream << '"';
912 } break;
913 case SPV_OPERAND_TYPE_CAPABILITY:
914 case SPV_OPERAND_TYPE_SOURCE_LANGUAGE:
915 case SPV_OPERAND_TYPE_EXECUTION_MODEL:
916 case SPV_OPERAND_TYPE_ADDRESSING_MODEL:
917 case SPV_OPERAND_TYPE_MEMORY_MODEL:
918 case SPV_OPERAND_TYPE_EXECUTION_MODE:
919 case SPV_OPERAND_TYPE_STORAGE_CLASS:
920 case SPV_OPERAND_TYPE_DIMENSIONALITY:
921 case SPV_OPERAND_TYPE_SAMPLER_ADDRESSING_MODE:
922 case SPV_OPERAND_TYPE_SAMPLER_FILTER_MODE:
923 case SPV_OPERAND_TYPE_SAMPLER_IMAGE_FORMAT:
924 case SPV_OPERAND_TYPE_FP_ROUNDING_MODE:
925 case SPV_OPERAND_TYPE_LINKAGE_TYPE:
926 case SPV_OPERAND_TYPE_ACCESS_QUALIFIER:
927 case SPV_OPERAND_TYPE_FUNCTION_PARAMETER_ATTRIBUTE:
928 case SPV_OPERAND_TYPE_DECORATION:
929 case SPV_OPERAND_TYPE_BUILT_IN:
930 case SPV_OPERAND_TYPE_GROUP_OPERATION:
931 case SPV_OPERAND_TYPE_KERNEL_ENQ_FLAGS:
932 case SPV_OPERAND_TYPE_KERNEL_PROFILING_INFO:
933 case SPV_OPERAND_TYPE_RAY_FLAGS:
934 case SPV_OPERAND_TYPE_RAY_QUERY_INTERSECTION:
935 case SPV_OPERAND_TYPE_RAY_QUERY_COMMITTED_INTERSECTION_TYPE:
936 case SPV_OPERAND_TYPE_RAY_QUERY_CANDIDATE_INTERSECTION_TYPE:
937 case SPV_OPERAND_TYPE_DEBUG_BASE_TYPE_ATTRIBUTE_ENCODING:
938 case SPV_OPERAND_TYPE_DEBUG_COMPOSITE_TYPE:
939 case SPV_OPERAND_TYPE_DEBUG_TYPE_QUALIFIER:
940 case SPV_OPERAND_TYPE_DEBUG_OPERATION:
941 case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_BASE_TYPE_ATTRIBUTE_ENCODING:
942 case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_COMPOSITE_TYPE:
943 case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_TYPE_QUALIFIER:
944 case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_OPERATION:
945 case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_IMPORTED_ENTITY:
946 case SPV_OPERAND_TYPE_FPDENORM_MODE:
947 case SPV_OPERAND_TYPE_FPOPERATION_MODE:
948 case SPV_OPERAND_TYPE_QUANTIZATION_MODES:
949 case SPV_OPERAND_TYPE_FPENCODING:
950 case SPV_OPERAND_TYPE_OVERFLOW_MODES: {
951 spv_operand_desc entry;
952 if (grammar_.lookupOperand(operand.type, word, &entry))
953 assert(false && "should have caught this earlier");
954 stream << entry->name;
955 } break;
956 case SPV_OPERAND_TYPE_FP_FAST_MATH_MODE:
957 case SPV_OPERAND_TYPE_FUNCTION_CONTROL:
958 case SPV_OPERAND_TYPE_LOOP_CONTROL:
959 case SPV_OPERAND_TYPE_IMAGE:
960 case SPV_OPERAND_TYPE_MEMORY_ACCESS:
961 case SPV_OPERAND_TYPE_SELECTION_CONTROL:
962 case SPV_OPERAND_TYPE_DEBUG_INFO_FLAGS:
963 case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_INFO_FLAGS:
964 case SPV_OPERAND_TYPE_RAW_ACCESS_CHAIN_OPERANDS:
965 EmitMaskOperand(stream, operand.type, word);
966 break;
967 default:
968 if (spvOperandIsConcreteMask(operand.type)) {
969 EmitMaskOperand(stream, operand.type, word);
970 } else if (spvOperandIsConcrete(operand.type)) {
971 spv_operand_desc entry;
972 if (grammar_.lookupOperand(operand.type, word, &entry))
973 assert(false && "should have caught this earlier");
974 stream << entry->name;
975 } else {
976 assert(false && "unhandled or invalid case");
977 }
978 break;
979 }
980 ResetColor(stream);
981 }
982
EmitMaskOperand(std::ostream & stream,const spv_operand_type_t type,const uint32_t word) const983 void InstructionDisassembler::EmitMaskOperand(std::ostream& stream,
984 const spv_operand_type_t type,
985 const uint32_t word) const {
986 // Scan the mask from least significant bit to most significant bit. For each
987 // set bit, emit the name of that bit. Separate multiple names with '|'.
988 uint32_t remaining_word = word;
989 uint32_t mask;
990 int num_emitted = 0;
991 for (mask = 1; remaining_word; mask <<= 1) {
992 if (remaining_word & mask) {
993 remaining_word ^= mask;
994 spv_operand_desc entry;
995 if (grammar_.lookupOperand(type, mask, &entry))
996 assert(false && "should have caught this earlier");
997 if (num_emitted) stream << "|";
998 stream << entry->name;
999 num_emitted++;
1000 }
1001 }
1002 if (!num_emitted) {
1003 // An operand value of 0 was provided, so represent it by the name
1004 // of the 0 value. In many cases, that's "None".
1005 spv_operand_desc entry;
1006 if (SPV_SUCCESS == grammar_.lookupOperand(type, 0, &entry))
1007 stream << entry->name;
1008 }
1009 }
1010
ResetColor(std::ostream & stream) const1011 void InstructionDisassembler::ResetColor(std::ostream& stream) const {
1012 if (color_) stream << spvtools::clr::reset{print_};
1013 }
SetGrey(std::ostream & stream) const1014 void InstructionDisassembler::SetGrey(std::ostream& stream) const {
1015 if (color_) stream << spvtools::clr::grey{print_};
1016 }
SetBlue(std::ostream & stream) const1017 void InstructionDisassembler::SetBlue(std::ostream& stream) const {
1018 if (color_) stream << spvtools::clr::blue{print_};
1019 }
SetYellow(std::ostream & stream) const1020 void InstructionDisassembler::SetYellow(std::ostream& stream) const {
1021 if (color_) stream << spvtools::clr::yellow{print_};
1022 }
SetRed(std::ostream & stream) const1023 void InstructionDisassembler::SetRed(std::ostream& stream) const {
1024 if (color_) stream << spvtools::clr::red{print_};
1025 }
SetGreen(std::ostream & stream) const1026 void InstructionDisassembler::SetGreen(std::ostream& stream) const {
1027 if (color_) stream << spvtools::clr::green{print_};
1028 }
1029
ResetColor()1030 void InstructionDisassembler::ResetColor() { ResetColor(stream_); }
SetGrey()1031 void InstructionDisassembler::SetGrey() { SetGrey(stream_); }
SetBlue()1032 void InstructionDisassembler::SetBlue() { SetBlue(stream_); }
SetYellow()1033 void InstructionDisassembler::SetYellow() { SetYellow(stream_); }
SetRed()1034 void InstructionDisassembler::SetRed() { SetRed(stream_); }
SetGreen()1035 void InstructionDisassembler::SetGreen() { SetGreen(stream_); }
1036 } // namespace disassemble
1037
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)1038 std::string spvInstructionBinaryToText(const spv_target_env env,
1039 const uint32_t* instCode,
1040 const size_t instWordCount,
1041 const uint32_t* code,
1042 const size_t wordCount,
1043 const uint32_t options) {
1044 spv_context context = spvContextCreate(env);
1045 const AssemblyGrammar grammar(context);
1046 if (!grammar.isValid()) {
1047 spvContextDestroy(context);
1048 return "";
1049 }
1050
1051 // Generate friendly names for Ids if requested.
1052 std::unique_ptr<FriendlyNameMapper> friendly_mapper;
1053 NameMapper name_mapper = GetTrivialNameMapper();
1054 if (options & SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES) {
1055 friendly_mapper = MakeUnique<FriendlyNameMapper>(context, code, wordCount);
1056 name_mapper = friendly_mapper->GetNameMapper();
1057 }
1058
1059 // Now disassemble!
1060 Disassembler disassembler(grammar, options, name_mapper);
1061 WrappedDisassembler wrapped(&disassembler, instCode, instWordCount);
1062 spvBinaryParse(context, &wrapped, code, wordCount, DisassembleTargetHeader,
1063 DisassembleTargetInstruction, nullptr);
1064
1065 spv_text text = nullptr;
1066 std::string output;
1067 if (disassembler.SaveTextResult(&text) == SPV_SUCCESS) {
1068 output.assign(text->str, text->str + text->length);
1069 // Drop trailing newline characters.
1070 while (!output.empty() && output.back() == '\n') output.pop_back();
1071 }
1072 spvTextDestroy(text);
1073 spvContextDestroy(context);
1074
1075 return output;
1076 }
1077 } // namespace spvtools
1078
spvBinaryToText(const spv_const_context context,const uint32_t * code,const size_t wordCount,const uint32_t options,spv_text * pText,spv_diagnostic * pDiagnostic)1079 spv_result_t spvBinaryToText(const spv_const_context context,
1080 const uint32_t* code, const size_t wordCount,
1081 const uint32_t options, spv_text* pText,
1082 spv_diagnostic* pDiagnostic) {
1083 spv_context_t hijack_context = *context;
1084 if (pDiagnostic) {
1085 *pDiagnostic = nullptr;
1086 spvtools::UseDiagnosticAsMessageConsumer(&hijack_context, pDiagnostic);
1087 }
1088
1089 const spvtools::AssemblyGrammar grammar(&hijack_context);
1090 if (!grammar.isValid()) return SPV_ERROR_INVALID_TABLE;
1091
1092 // Generate friendly names for Ids if requested.
1093 std::unique_ptr<spvtools::FriendlyNameMapper> friendly_mapper;
1094 spvtools::NameMapper name_mapper = spvtools::GetTrivialNameMapper();
1095 if (options & SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES) {
1096 friendly_mapper = spvtools::MakeUnique<spvtools::FriendlyNameMapper>(
1097 &hijack_context, code, wordCount);
1098 name_mapper = friendly_mapper->GetNameMapper();
1099 }
1100
1101 // Now disassemble!
1102 spvtools::Disassembler disassembler(grammar, options, name_mapper);
1103 if (auto error =
1104 spvBinaryParse(&hijack_context, &disassembler, code, wordCount,
1105 spvtools::DisassembleHeader,
1106 spvtools::DisassembleInstruction, pDiagnostic)) {
1107 return error;
1108 }
1109
1110 return disassembler.SaveTextResult(pText);
1111 }
1112