diff options
author | Alan Baker <alanbaker@google.com> | 2018-01-10 14:23:47 -0500 |
---|---|---|
committer | Alan Baker <alanbaker@google.com> | 2018-01-12 11:19:58 -0500 |
commit | 672494da13d54cf72d41edc67ecc6b53c561afc6 (patch) | |
tree | a9cf3a8aa39bd7d5c7de180c55d20fa45a17f29b | |
parent | eb0c73dad6102fc0d4f03c62fe910348bae43a11 (diff) | |
download | SPIRV-Tools-672494da13d54cf72d41edc67ecc6b53c561afc6.tar.gz SPIRV-Tools-672494da13d54cf72d41edc67ecc6b53c561afc6.tar.bz2 SPIRV-Tools-672494da13d54cf72d41edc67ecc6b53c561afc6.zip |
Adding ostream operators for IR structures
* Added for Instruction, BasicBlock, Function and Module
* Uses new disassembly functionality that can disassemble individual
instructions
* For debug use only (no caching is done)
* Each output converts module to binary, parses and outputs an
individual instruction
* Added a test for whole module output
* Disabling Microsoft checked iterator warnings
* Updated check_copyright.py to accept 2018
-rw-r--r-- | CMakeLists.txt | 2 | ||||
-rw-r--r-- | source/CMakeLists.txt | 1 | ||||
-rw-r--r-- | source/disassemble.cpp | 88 | ||||
-rw-r--r-- | source/disassemble.h | 38 | ||||
-rw-r--r-- | source/opt/basic_block.cpp | 13 | ||||
-rw-r--r-- | source/opt/basic_block.h | 4 | ||||
-rw-r--r-- | source/opt/function.cpp | 12 | ||||
-rw-r--r-- | source/opt/function.h | 3 | ||||
-rw-r--r-- | source/opt/instruction.cpp | 23 | ||||
-rw-r--r-- | source/opt/instruction.h | 17 | ||||
-rw-r--r-- | source/opt/ir_context.h | 3 | ||||
-rw-r--r-- | source/opt/module.cpp | 11 | ||||
-rw-r--r-- | source/opt/module.h | 3 | ||||
-rw-r--r-- | test/opt/module_test.cpp | 70 | ||||
-rwxr-xr-x | utils/check_copyright.py | 4 |
15 files changed, 287 insertions, 5 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index b6feb6f1..e76a6636 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -85,7 +85,7 @@ if(${COMPILER_IS_LIKE_GNU}) set(SPIRV_WARNINGS ${SPIRV_WARNINGS} -Werror) endif() elseif(MSVC) - set(SPIRV_WARNINGS -D_CRT_SECURE_NO_WARNINGS /wd4800) + set(SPIRV_WARNINGS -D_CRT_SECURE_NO_WARNINGS -D_SCL_SECURE_NO_WARNINGS /wd4800) if(${SPIRV_WERROR}) set(SPIRV_WARNINGS ${SPIRV_WARNINGS} /WX) diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index a306730a..61a41122 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -228,6 +228,7 @@ set(SPIRV_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/binary.h ${CMAKE_CURRENT_SOURCE_DIR}/cfa.h ${CMAKE_CURRENT_SOURCE_DIR}/diagnostic.h + ${CMAKE_CURRENT_SOURCE_DIR}/disassemble.h ${CMAKE_CURRENT_SOURCE_DIR}/enum_set.h ${CMAKE_CURRENT_SOURCE_DIR}/enum_string_mapping.h ${CMAKE_CURRENT_SOURCE_DIR}/ext_inst.h diff --git a/source/disassemble.cpp b/source/disassemble.cpp index 5230a502..909886c0 100644 --- a/source/disassemble.cpp +++ b/source/disassemble.cpp @@ -25,6 +25,7 @@ #include "assembly_grammar.h" #include "binary.h" #include "diagnostic.h" +#include "disassemble.h" #include "ext_inst.h" #include "name_mapper.h" #include "opcode.h" @@ -352,6 +353,52 @@ spv_result_t DisassembleInstruction( return disassembler->HandleInstruction(*parsed_instruction); } +// Simple wrapper class to provide extra data necessary for targeted +// instruction disassembly. +class WrappedDisassembler { + public: + WrappedDisassembler(Disassembler* dis, const uint32_t* binary, size_t wc) + : disassembler_(dis), inst_binary_(binary), word_count_(wc) {} + + Disassembler* disassembler() { return disassembler_; } + const uint32_t* inst_binary() const { return inst_binary_; } + size_t word_count() const { return word_count_; } + + private: + Disassembler* disassembler_; + const uint32_t* inst_binary_; + const size_t word_count_; +}; + +spv_result_t DisassembleTargetHeader(void* user_data, spv_endianness_t endian, + uint32_t /* magic */, uint32_t version, + uint32_t generator, uint32_t id_bound, + uint32_t schema) { + assert(user_data); + auto wrapped = static_cast<WrappedDisassembler*>(user_data); + return wrapped->disassembler()->HandleHeader(endian, version, generator, + id_bound, schema); +} + +spv_result_t DisassembleTargetInstruction( + void* user_data, const spv_parsed_instruction_t* parsed_instruction) { + assert(user_data); + auto wrapped = static_cast<WrappedDisassembler*>(user_data); + // Check if this is the instruction we want to disassemble. + if (wrapped->word_count() == parsed_instruction->num_words && + std::equal(wrapped->inst_binary(), + wrapped->inst_binary() + wrapped->word_count(), + parsed_instruction->words)) { + // Found the target instruction. Disassemble it and signal that we should + // stop searching so we don't output the same instruction again. + if (auto error = + wrapped->disassembler()->HandleInstruction(*parsed_instruction)) + return error; + return SPV_REQUESTED_TERMINATION; + } + return SPV_SUCCESS; +} + } // anonymous namespace spv_result_t spvBinaryToText(const spv_const_context context, @@ -386,3 +433,44 @@ spv_result_t spvBinaryToText(const spv_const_context context, return disassembler.SaveTextResult(pText); } + +std::string spvtools::spvInstructionBinaryToText(const spv_target_env env, + const uint32_t* instCode, + const size_t instWordCount, + const uint32_t* code, + const size_t wordCount, + const uint32_t options) { + spv_context context = spvContextCreate(env); + const libspirv::AssemblyGrammar grammar(context); + if (!grammar.isValid()) { + spvContextDestroy(context); + return ""; + } + + // Generate friendly names for Ids if requested. + std::unique_ptr<libspirv::FriendlyNameMapper> friendly_mapper; + libspirv::NameMapper name_mapper = libspirv::GetTrivialNameMapper(); + if (options & SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES) { + friendly_mapper.reset( + new libspirv::FriendlyNameMapper(context, code, wordCount)); + name_mapper = friendly_mapper->GetNameMapper(); + } + + // Now disassemble! + Disassembler disassembler(grammar, options, name_mapper); + WrappedDisassembler wrapped(&disassembler, instCode, instWordCount); + spvBinaryParse(context, &wrapped, code, wordCount, DisassembleTargetHeader, + DisassembleTargetInstruction, nullptr); + + spv_text text = nullptr; + std::string output; + if (disassembler.SaveTextResult(&text) == SPV_SUCCESS) { + output.assign(text->str, text->str + text->length); + // Drop trailing newline characters. + while (!output.empty() && output.back() == '\n') output.pop_back(); + } + spvTextDestroy(text); + spvContextDestroy(context); + + return output; +} diff --git a/source/disassemble.h b/source/disassemble.h new file mode 100644 index 00000000..b833dd07 --- /dev/null +++ b/source/disassemble.h @@ -0,0 +1,38 @@ +// Copyright (c) 2018 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SPIRV_TOOLS_DISASSEMBLE_H_ +#define SPIRV_TOOLS_DISASSEMBLE_H_ + +#include <string> + +#include "spirv-tools/libspirv.h" + +namespace spvtools { + +// Decodes the given SPIR-V instruction binary representation to its assembly +// text. The context is inferred from the provided module binary. The options +// parameter is a bit field of spv_binary_to_text_options_t. Decoded text will +// be stored into *text. Any error will be written into *diagnostic if +// diagnostic is non-null. +std::string spvInstructionBinaryToText(const spv_target_env env, + const uint32_t* inst_binary, + const size_t inst_word_count, + const uint32_t* binary, + const size_t word_count, + const uint32_t options); + +} // namespace spvtools + +#endif // SPIRV_TOOLS_DISASSEMBLE_H_ diff --git a/source/opt/basic_block.cpp b/source/opt/basic_block.cpp index 77ee133a..d2f4b33d 100644 --- a/source/opt/basic_block.cpp +++ b/source/opt/basic_block.cpp @@ -15,9 +15,12 @@ #include "basic_block.h" #include "function.h" #include "module.h" +#include "reflect.h" #include "make_unique.h" +#include <ostream> + namespace spvtools { namespace ir { @@ -155,5 +158,15 @@ uint32_t BasicBlock::ContinueBlockIdIfAny() const { return cbid; } +std::ostream& operator<<(std::ostream& str, const BasicBlock& block) { + block.ForEachInst([&str](const ir::Instruction* inst) { + str << *inst; + if (!IsTerminatorInst(inst->opcode())) { + str << std::endl; + } + }); + return str; +} + } // namespace ir } // namespace spvtools diff --git a/source/opt/basic_block.h b/source/opt/basic_block.h index cfff9021..2e27a4b5 100644 --- a/source/opt/basic_block.h +++ b/source/opt/basic_block.h @@ -148,6 +148,7 @@ class BasicBlock { // Returns the terminator instruction. Assumes the terminator exists. Instruction* terminator() { return &*tail(); } + const Instruction* terminator() const { return &*ctail(); } // Returns true if this basic block exits this function and returns to its // caller. @@ -165,6 +166,9 @@ class BasicBlock { InstructionList insts_; }; +// Pretty-prints |block| to |str|. Returns |str|. +std::ostream& operator<<(std::ostream& str, const BasicBlock& block); + inline BasicBlock::BasicBlock(std::unique_ptr<Instruction> label) : function_(nullptr), label_(std::move(label)) {} diff --git a/source/opt/function.cpp b/source/opt/function.cpp index 4fc87291..d8e43892 100644 --- a/source/opt/function.cpp +++ b/source/opt/function.cpp @@ -16,6 +16,8 @@ #include "make_unique.h" +#include <ostream> + namespace spvtools { namespace ir { @@ -75,5 +77,15 @@ void Function::ForEachParam(const std::function<void(const Instruction*)>& f, ->ForEachInst(f, run_on_debug_line_insts); } +std::ostream& operator<<(std::ostream& str, const Function& func) { + func.ForEachInst([&str](const ir::Instruction* inst) { + str << *inst; + if (inst->opcode() != SpvOpFunctionEnd) { + str << std::endl; + } + }); + return str; +} + } // namespace ir } // namespace spvtools diff --git a/source/opt/function.h b/source/opt/function.h index 8caa3e3f..0da62a8f 100644 --- a/source/opt/function.h +++ b/source/opt/function.h @@ -112,6 +112,9 @@ class Function { std::unique_ptr<Instruction> end_inst_; }; +// Pretty-prints |func| to |str|. Returns |str|. +std::ostream& operator<<(std::ostream& str, const Function& func); + inline Function::Function(std::unique_ptr<Instruction> def_inst) : module_(nullptr), def_inst_(std::move(def_inst)), end_inst_() {} diff --git a/source/opt/instruction.cpp b/source/opt/instruction.cpp index 2b83ca11..8321561d 100644 --- a/source/opt/instruction.cpp +++ b/source/opt/instruction.cpp @@ -14,6 +14,7 @@ #include <initializer_list> +#include "disassemble.h" #include "fold.h" #include "instruction.h" #include "ir_context.h" @@ -476,5 +477,27 @@ bool Instruction::IsFoldable() const { return opt::IsFoldableType(type); } +std::string Instruction::PrettyPrint(uint32_t options) const { + // Convert the module to binary. + std::vector<uint32_t> module_binary; + context()->module()->ToBinary(&module_binary, /* skip_nop = */ false); + + // Convert the instruction to binary. This is used to identify the correct + // stream of words to output from the module. + std::vector<uint32_t> inst_binary; + ToBinaryWithoutAttachedDebugInsts(&inst_binary); + + // Do not generate a header. + return spvInstructionBinaryToText( + context()->grammar().target_env(), inst_binary.data(), inst_binary.size(), + module_binary.data(), module_binary.size(), + options | SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); +} + +std::ostream& operator<<(std::ostream& str, const ir::Instruction& inst) { + str << inst.PrettyPrint(); + return str; +} + } // namespace ir } // namespace spvtools diff --git a/source/opt/instruction.h b/source/opt/instruction.h index 61de1f9e..30b3d78e 100644 --- a/source/opt/instruction.h +++ b/source/opt/instruction.h @@ -351,6 +351,15 @@ class Instruction : public utils::IntrusiveNodeBase<Instruction> { // Spec constant. inline bool IsConstant() const; + // Pretty-prints |inst|. + // + // Provides the disassembly of a specific instruction. Utilizes |inst|'s + // context to provide the correct interpretation of types, constants, etc. + // + // |options| are the disassembly options. SPV_BINARY_TO_TEXT_OPTION_NO_HEADER + // is always added to |options|. + std::string PrettyPrint(uint32_t options = 0u) const; + private: // Returns the total count of result type id and result id. uint32_t TypeResultIdCount() const { @@ -388,6 +397,14 @@ class Instruction : public utils::IntrusiveNodeBase<Instruction> { friend InstructionList; }; +// Pretty-prints |inst| to |str| and returns |str|. +// +// Provides the disassembly of a specific instruction. Utilizes |inst|'s context +// to provide the correct interpretation of types, constants, etc. +// +// Disassembly uses raw ids (not pretty printed names). +std::ostream& operator<<(std::ostream& str, const ir::Instruction& inst); + inline bool Instruction::operator==(const Instruction& other) const { return unique_id() == other.unique_id(); } diff --git a/source/opt/ir_context.h b/source/opt/ir_context.h index 98e14331..6602ddf1 100644 --- a/source/opt/ir_context.h +++ b/source/opt/ir_context.h @@ -391,6 +391,9 @@ class IRContext { return feature_mgr_.get(); } + // Returns the grammar for this context. + const libspirv::AssemblyGrammar& grammar() const { return grammar_; } + private: // Builds the def-use manager from scratch, even if it was already valid. void BuildDefUseManager() { diff --git a/source/opt/module.cpp b/source/opt/module.cpp index ba31384e..fea49ef3 100644 --- a/source/opt/module.cpp +++ b/source/opt/module.cpp @@ -16,6 +16,7 @@ #include <algorithm> #include <cstring> +#include <ostream> #include "operand.h" #include "reflect.h" @@ -158,5 +159,15 @@ uint32_t Module::GetExtInstImportId(const char* extstr) { return 0; } +std::ostream& operator<<(std::ostream& str, const Module& module) { + module.ForEachInst([&str](const ir::Instruction* inst) { + str << *inst; + if (inst->opcode() != SpvOpFunctionEnd) { + str << std::endl; + } + }); + return str; +} + } // namespace ir } // namespace spvtools diff --git a/source/opt/module.h b/source/opt/module.h index 99ed3a26..163c4e30 100644 --- a/source/opt/module.h +++ b/source/opt/module.h @@ -273,6 +273,9 @@ class Module { std::vector<std::unique_ptr<Function>> functions_; }; +// Pretty-prints |module| to |str|. Returns |str|. +std::ostream& operator<<(std::ostream& str, const Module& module); + inline void Module::AddCapability(std::unique_ptr<Instruction> c) { capabilities_.push_back(std::move(c)); } diff --git a/test/opt/module_test.cpp b/test/opt/module_test.cpp index 43be818f..177b45b9 100644 --- a/test/opt/module_test.cpp +++ b/test/opt/module_test.cpp @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include <sstream> #include <vector> #include "gmock/gmock.h" @@ -26,10 +27,10 @@ namespace { +using ::testing::Eq; using spvtest::GetIdBound; using spvtools::ir::IRContext; using spvtools::ir::Module; -using ::testing::Eq; TEST(ModuleTest, SetIdBound) { Module m; @@ -46,7 +47,8 @@ TEST(ModuleTest, SetIdBound) { // Returns an IRContext owning the module formed by assembling the given text, // then loading the result. inline std::unique_ptr<IRContext> BuildModule(std::string text) { - return spvtools::BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text); + return spvtools::BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); } TEST(ModuleTest, ComputeIdBound) { @@ -74,4 +76,68 @@ TEST(ModuleTest, ComputeIdBound) { ->ComputeIdBound()); } +TEST(ModuleTest, OstreamOperator) { + const std::string text = R"(OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpName %7 "restrict" +OpDecorate %8 Restrict +%9 = OpTypeVoid +%10 = OpTypeInt 32 0 +%11 = OpTypeStruct %10 %10 +%12 = OpTypePointer Function %10 +%13 = OpTypePointer Function %11 +%14 = OpConstant %10 0 +%15 = OpConstant %10 1 +%7 = OpTypeFunction %9 +%1 = OpFunction %9 None %7 +%2 = OpLabel +%8 = OpVariable %13 Function +%3 = OpAccessChain %12 %8 %14 +%4 = OpLoad %10 %3 +%5 = OpAccessChain %12 %8 %15 +%6 = OpLoad %10 %5 +OpReturn +OpFunctionEnd)"; + + std::string s; + std::ostringstream str(s); + str << *BuildModule(text)->module(); + EXPECT_EQ(text, str.str()); +} + +TEST(ModuleTest, OstreamOperatorInt64) { + const std::string text = R"(OpCapability Shader +OpCapability Linkage +OpCapability Int64 +OpMemoryModel Logical GLSL450 +OpName %7 "restrict" +OpDecorate %5 Restrict +%9 = OpTypeVoid +%10 = OpTypeInt 64 0 +%11 = OpTypeStruct %10 %10 +%12 = OpTypePointer Function %10 +%13 = OpTypePointer Function %11 +%14 = OpConstant %10 0 +%15 = OpConstant %10 1 +%16 = OpConstant %10 4294967297 +%7 = OpTypeFunction %9 +%1 = OpFunction %9 None %7 +%2 = OpLabel +%5 = OpVariable %12 Function +%6 = OpLoad %10 %5 +OpSelectionMerge %3 None +OpSwitch %6 %3 4294967297 %4 +%4 = OpLabel +OpBranch %3 +%3 = OpLabel +OpReturn +OpFunctionEnd)"; + + std::string s; + std::ostringstream str(s); + str << *BuildModule(text)->module(); + EXPECT_EQ(text, str.str()); +} + } // anonymous namespace diff --git a/utils/check_copyright.py b/utils/check_copyright.py index a2ed459b..de7bf471 100755 --- a/utils/check_copyright.py +++ b/utils/check_copyright.py @@ -31,9 +31,9 @@ AUTHORS = ['The Khronos Group Inc.', 'LunarG Inc.', 'Google Inc.', 'Pierre Moreau'] -CURRENT_YEAR='2017' +CURRENT_YEAR='2018' -YEARS = '(2014-2016|2015-2016|2016|2016-2017|2017)' +YEARS = '(2014-2016|2015-2016|2016|2016-2017|2017|2018)' COPYRIGHT_RE = re.compile( 'Copyright \(c\) {} ({})'.format(YEARS, '|'.join(AUTHORS))) |