disassemble.cpp 39 KB

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