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
This commit is contained in:
Alan Baker 2018-01-10 14:23:47 -05:00
parent eb0c73dad6
commit 672494da13
15 changed files with 287 additions and 5 deletions

View File

@ -85,7 +85,7 @@ if(${COMPILER_IS_LIKE_GNU})
set(SPIRV_WARNINGS ${SPIRV_WARNINGS} -Werror) set(SPIRV_WARNINGS ${SPIRV_WARNINGS} -Werror)
endif() endif()
elseif(MSVC) 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}) if(${SPIRV_WERROR})
set(SPIRV_WARNINGS ${SPIRV_WARNINGS} /WX) set(SPIRV_WARNINGS ${SPIRV_WARNINGS} /WX)

View File

@ -228,6 +228,7 @@ set(SPIRV_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/binary.h ${CMAKE_CURRENT_SOURCE_DIR}/binary.h
${CMAKE_CURRENT_SOURCE_DIR}/cfa.h ${CMAKE_CURRENT_SOURCE_DIR}/cfa.h
${CMAKE_CURRENT_SOURCE_DIR}/diagnostic.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_set.h
${CMAKE_CURRENT_SOURCE_DIR}/enum_string_mapping.h ${CMAKE_CURRENT_SOURCE_DIR}/enum_string_mapping.h
${CMAKE_CURRENT_SOURCE_DIR}/ext_inst.h ${CMAKE_CURRENT_SOURCE_DIR}/ext_inst.h

View File

@ -25,6 +25,7 @@
#include "assembly_grammar.h" #include "assembly_grammar.h"
#include "binary.h" #include "binary.h"
#include "diagnostic.h" #include "diagnostic.h"
#include "disassemble.h"
#include "ext_inst.h" #include "ext_inst.h"
#include "name_mapper.h" #include "name_mapper.h"
#include "opcode.h" #include "opcode.h"
@ -352,6 +353,52 @@ spv_result_t DisassembleInstruction(
return disassembler->HandleInstruction(*parsed_instruction); 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 } // anonymous namespace
spv_result_t spvBinaryToText(const spv_const_context context, 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); 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;
}

38
source/disassemble.h Normal file
View File

@ -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_

View File

@ -15,9 +15,12 @@
#include "basic_block.h" #include "basic_block.h"
#include "function.h" #include "function.h"
#include "module.h" #include "module.h"
#include "reflect.h"
#include "make_unique.h" #include "make_unique.h"
#include <ostream>
namespace spvtools { namespace spvtools {
namespace ir { namespace ir {
@ -155,5 +158,15 @@ uint32_t BasicBlock::ContinueBlockIdIfAny() const {
return cbid; 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 ir
} // namespace spvtools } // namespace spvtools

View File

@ -148,6 +148,7 @@ class BasicBlock {
// Returns the terminator instruction. Assumes the terminator exists. // Returns the terminator instruction. Assumes the terminator exists.
Instruction* terminator() { return &*tail(); } Instruction* terminator() { return &*tail(); }
const Instruction* terminator() const { return &*ctail(); }
// Returns true if this basic block exits this function and returns to its // Returns true if this basic block exits this function and returns to its
// caller. // caller.
@ -165,6 +166,9 @@ class BasicBlock {
InstructionList insts_; 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) inline BasicBlock::BasicBlock(std::unique_ptr<Instruction> label)
: function_(nullptr), label_(std::move(label)) {} : function_(nullptr), label_(std::move(label)) {}

View File

@ -16,6 +16,8 @@
#include "make_unique.h" #include "make_unique.h"
#include <ostream>
namespace spvtools { namespace spvtools {
namespace ir { namespace ir {
@ -75,5 +77,15 @@ void Function::ForEachParam(const std::function<void(const Instruction*)>& f,
->ForEachInst(f, run_on_debug_line_insts); ->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 ir
} // namespace spvtools } // namespace spvtools

View File

@ -112,6 +112,9 @@ class Function {
std::unique_ptr<Instruction> end_inst_; 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) inline Function::Function(std::unique_ptr<Instruction> def_inst)
: module_(nullptr), def_inst_(std::move(def_inst)), end_inst_() {} : module_(nullptr), def_inst_(std::move(def_inst)), end_inst_() {}

View File

@ -14,6 +14,7 @@
#include <initializer_list> #include <initializer_list>
#include "disassemble.h"
#include "fold.h" #include "fold.h"
#include "instruction.h" #include "instruction.h"
#include "ir_context.h" #include "ir_context.h"
@ -476,5 +477,27 @@ bool Instruction::IsFoldable() const {
return opt::IsFoldableType(type); 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 ir
} // namespace spvtools } // namespace spvtools

View File

@ -351,6 +351,15 @@ class Instruction : public utils::IntrusiveNodeBase<Instruction> {
// Spec constant. // Spec constant.
inline bool IsConstant() const; 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: private:
// Returns the total count of result type id and result id. // Returns the total count of result type id and result id.
uint32_t TypeResultIdCount() const { uint32_t TypeResultIdCount() const {
@ -388,6 +397,14 @@ class Instruction : public utils::IntrusiveNodeBase<Instruction> {
friend InstructionList; 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 { inline bool Instruction::operator==(const Instruction& other) const {
return unique_id() == other.unique_id(); return unique_id() == other.unique_id();
} }

View File

@ -391,6 +391,9 @@ class IRContext {
return feature_mgr_.get(); return feature_mgr_.get();
} }
// Returns the grammar for this context.
const libspirv::AssemblyGrammar& grammar() const { return grammar_; }
private: private:
// Builds the def-use manager from scratch, even if it was already valid. // Builds the def-use manager from scratch, even if it was already valid.
void BuildDefUseManager() { void BuildDefUseManager() {

View File

@ -16,6 +16,7 @@
#include <algorithm> #include <algorithm>
#include <cstring> #include <cstring>
#include <ostream>
#include "operand.h" #include "operand.h"
#include "reflect.h" #include "reflect.h"
@ -158,5 +159,15 @@ uint32_t Module::GetExtInstImportId(const char* extstr) {
return 0; 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 ir
} // namespace spvtools } // namespace spvtools

View File

@ -273,6 +273,9 @@ class Module {
std::vector<std::unique_ptr<Function>> functions_; 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) { inline void Module::AddCapability(std::unique_ptr<Instruction> c) {
capabilities_.push_back(std::move(c)); capabilities_.push_back(std::move(c));
} }

View File

@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#include <sstream>
#include <vector> #include <vector>
#include "gmock/gmock.h" #include "gmock/gmock.h"
@ -26,10 +27,10 @@
namespace { namespace {
using ::testing::Eq;
using spvtest::GetIdBound; using spvtest::GetIdBound;
using spvtools::ir::IRContext; using spvtools::ir::IRContext;
using spvtools::ir::Module; using spvtools::ir::Module;
using ::testing::Eq;
TEST(ModuleTest, SetIdBound) { TEST(ModuleTest, SetIdBound) {
Module m; Module m;
@ -46,7 +47,8 @@ TEST(ModuleTest, SetIdBound) {
// Returns an IRContext owning the module formed by assembling the given text, // Returns an IRContext owning the module formed by assembling the given text,
// then loading the result. // then loading the result.
inline std::unique_ptr<IRContext> BuildModule(std::string text) { 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) { TEST(ModuleTest, ComputeIdBound) {
@ -74,4 +76,68 @@ TEST(ModuleTest, ComputeIdBound) {
->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 } // anonymous namespace

View File

@ -31,9 +31,9 @@ AUTHORS = ['The Khronos Group Inc.',
'LunarG Inc.', 'LunarG Inc.',
'Google Inc.', 'Google Inc.',
'Pierre Moreau'] '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_RE = re.compile(
'Copyright \(c\) {} ({})'.format(YEARS, '|'.join(AUTHORS))) 'Copyright \(c\) {} ({})'.format(YEARS, '|'.join(AUTHORS)))