mirror of
https://github.com/KhronosGroup/SPIRV-Tools
synced 2024-10-18 11:10:05 +00:00
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:
parent
eb0c73dad6
commit
672494da13
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
38
source/disassemble.h
Normal file
38
source/disassemble.h
Normal 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_
|
@ -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
|
||||
|
@ -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)) {}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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_() {}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)))
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user