xref: /aosp_15_r20/external/angle/third_party/spirv-tools/src/source/disassemble.cpp (revision 8975f5c5ed3d1c378011245431ada316dfb6f244)
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