Disassembling: map IDs to friendly names.

Add a FriendlyNameMapper to deduce friendly names for IDs based on
OpName, type structure, etc.
This commit is contained in:
David Neto 2016-07-08 14:29:52 -04:00 коммит произвёл Lei Zhang
Родитель 1a9385bbd0
Коммит 0bdcc23f7e
10 изменённых файлов: 647 добавлений и 22 удалений

Просмотреть файл

@ -4,6 +4,9 @@ v2016.1-dev 2016-07-04
- Start v2016.1
- Fix https://github.com/KhronosGroup/SPIRV-Tools/issues/261
Turn off ClipDistance and CullDistance capability checks for Vulkan.
- The disassembler can emit friendly names based on debug info (OpName
instructions), and will infer somewhat friendly names for most types.
This is turned on by default for the spirv-dis command line tool.
v2016.0 2016-07-04

Просмотреть файл

@ -246,6 +246,10 @@ typedef enum spv_binary_to_text_options_t {
SPV_BINARY_TO_TEXT_OPTION_SHOW_BYTE_OFFSET = SPV_BIT(4),
// Do not output the module header as leading comments in the assembly.
SPV_BINARY_TO_TEXT_OPTION_NO_HEADER = SPV_BIT(5),
// Use friendly names where possible. The heuristic may expand over
// time, but will use common names for scalar types, and debug names from
// OpName instructions.
SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES = SPV_BIT(6),
SPV_FORCE_32_BIT_ENUM(spv_binary_to_text_options_t)
} spv_binary_to_text_options_t;

Просмотреть файл

@ -133,6 +133,7 @@ set(SPIRV_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/ext_inst.h
${CMAKE_CURRENT_SOURCE_DIR}/instruction.h
${CMAKE_CURRENT_SOURCE_DIR}/macro.h
${CMAKE_CURRENT_SOURCE_DIR}/name_mapper.h
${CMAKE_CURRENT_SOURCE_DIR}/opcode.h
${CMAKE_CURRENT_SOURCE_DIR}/operand.h
${CMAKE_CURRENT_SOURCE_DIR}/print.h
@ -151,6 +152,7 @@ set(SPIRV_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/disassemble.cpp
${CMAKE_CURRENT_SOURCE_DIR}/ext_inst.cpp
${CMAKE_CURRENT_SOURCE_DIR}/instruction.cpp
${CMAKE_CURRENT_SOURCE_DIR}/name_mapper.cpp
${CMAKE_CURRENT_SOURCE_DIR}/opcode.cpp
${CMAKE_CURRENT_SOURCE_DIR}/operand.cpp
${CMAKE_CURRENT_SOURCE_DIR}/print.cpp

Просмотреть файл

@ -27,15 +27,18 @@
// This file contains a disassembler: It converts a SPIR-V binary
// to text.
#include <algorithm>
#include <cassert>
#include <cstring>
#include <iomanip>
#include <memory>
#include <unordered_map>
#include "assembly_grammar.h"
#include "binary.h"
#include "diagnostic.h"
#include "ext_inst.h"
#include "name_mapper.h"
#include "opcode.h"
#include "print.h"
#include "spirv-tools/libspirv.h"
@ -49,7 +52,8 @@ namespace {
// representation.
class Disassembler {
public:
Disassembler(const libspirv::AssemblyGrammar& grammar, uint32_t options)
Disassembler(const libspirv::AssemblyGrammar& grammar, uint32_t options,
libspirv::NameMapper name_mapper)
: grammar_(grammar),
print_(spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_PRINT, options)),
color_(print_ &&
@ -63,7 +67,8 @@ class Disassembler {
header_(!spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER, options)),
show_byte_offset_(spvIsInBitfield(
SPV_BINARY_TO_TEXT_OPTION_SHOW_BYTE_OFFSET, options)),
byte_offset_(0) {}
byte_offset_(0),
name_mapper_(std::move(name_mapper)) {}
// Emits the assembly header for the module, and sets up internal state
// so subsequent callbacks can handle the cases where the entire module
@ -127,6 +132,7 @@ class Disassembler {
const bool header_; // Should we output header as the leading comment?
const bool show_byte_offset_; // Should we print byte offset, in hex?
size_t byte_offset_; // The number of bytes processed so far.
libspirv::NameMapper name_mapper_;
};
spv_result_t Disassembler::HandleHeader(spv_endianness_t endian,
@ -159,27 +165,14 @@ spv_result_t Disassembler::HandleHeader(spv_endianness_t endian,
return SPV_SUCCESS;
}
// Returns the number of digits in n.
int NumDigits(uint32_t n) {
if (n < 10) return 0;
if (n < 100) return 1;
if (n < 1000) return 2;
if (n < 10000) return 3;
if (n < 100000) return 4;
if (n < 1000000) return 5;
if (n < 10000000) return 6;
if (n < 100000000) return 7;
if (n < 1000000000) return 8;
return 9;
}
spv_result_t Disassembler::HandleInstruction(
const spv_parsed_instruction_t& inst) {
if (inst.result_id) {
SetBlue();
// Indent if needed, but account for the 4 characters in "%" and " = "
if (indent_) stream_ << std::setw(indent_ - 4 - NumDigits(inst.result_id));
stream_ << "%" << inst.result_id;
const std::string id_name = name_mapper_(inst.result_id);
if (indent_)
stream_ << std::setw(std::max(0, indent_ - 3 - int(id_name.size())));
stream_ << "%" << id_name;
ResetColor();
stream_ << " = ";
} else {
@ -222,14 +215,14 @@ void Disassembler::EmitOperand(const spv_parsed_instruction_t& inst,
case SPV_OPERAND_TYPE_RESULT_ID:
assert(false && "<result-id> is not supposed to be handled here");
SetBlue();
stream_ << "%" << word;
stream_ << "%" << name_mapper_(word);
break;
case SPV_OPERAND_TYPE_ID:
case SPV_OPERAND_TYPE_TYPE_ID:
case SPV_OPERAND_TYPE_SCOPE_ID:
case SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID:
SetYellow();
stream_ << "%" << word;
stream_ << "%" << name_mapper_(word);
break;
case SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER: {
spv_ext_inst_desc ext_inst;
@ -418,7 +411,17 @@ spv_result_t spvBinaryToText(const spv_const_context context,
const libspirv::AssemblyGrammar grammar(context);
if (!grammar.isValid()) return SPV_ERROR_INVALID_TABLE;
Disassembler disassembler(grammar, options);
// 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);
if (auto error = spvBinaryParse(context, &disassembler, code, wordCount,
DisassembleHeader, DisassembleInstruction,
pDiagnostic)) {

244
source/name_mapper.cpp Normal file
Просмотреть файл

@ -0,0 +1,244 @@
// Copyright (c) 2016 Google Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and/or associated documentation files (the
// "Materials"), to deal in the Materials without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Materials, and to
// permit persons to whom the Materials are furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Materials.
//
// MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS
// KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS
// SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT
// https://www.khronos.org/registry/
//
// THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
#include "name_mapper.h"
#include <algorithm>
#include <iterator>
#include <sstream>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include "spirv-tools/libspirv.h"
#include "spirv/1.1/spirv.h"
namespace {
// Converts a uint32_t to its string decimal representation.
std::string to_string(uint32_t id) {
// Use stringstream, since some versions of Android compilers lack
// std::to_string.
std::stringstream os;
os << id;
return os.str();
}
} // anonymous namespace
namespace libspirv {
NameMapper GetTrivialNameMapper() { return to_string; }
FriendlyNameMapper::FriendlyNameMapper(const spv_const_context context,
const uint32_t* code,
const size_t wordCount)
: grammar_(libspirv::AssemblyGrammar(context)) {
spv_diagnostic diag = nullptr;
// We don't care if the parse fails.
spvBinaryParse(context, this, code, wordCount, nullptr,
ParseInstructionForwarder, &diag);
spvDiagnosticDestroy(diag);
}
std::string FriendlyNameMapper::NameForId(uint32_t id) {
auto iter = name_for_id_.find(id);
if (iter == name_for_id_.end()) {
// It must have been an invalid module, so just return a trivial mapping.
// We don't care about uniqueness.
return to_string(id);
} else {
return iter->second;
}
}
std::string FriendlyNameMapper::Sanitize(const std::string& suggested_name) {
// Just replace invalid characters by '_'.
std::string result;
std::string valid =
"abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"_0123456789";
std::transform(suggested_name.begin(), suggested_name.end(),
std::back_inserter(result), [&valid](const char c) {
return (std::string::npos == valid.find(c)) ? '_' : c;
});
return result;
}
void FriendlyNameMapper::SaveName(uint32_t id,
const std::string& suggested_name) {
if (name_for_id_.find(id) != name_for_id_.end()) return;
const std::string sanitized_suggested_name = Sanitize(suggested_name);
std::string name = sanitized_suggested_name;
auto inserted = used_names_.insert(name);
if (!inserted.second) {
const std::string base_name = sanitized_suggested_name + "_";
for (uint32_t index = 0; !inserted.second; ++index) {
name = base_name + to_string(index);
inserted = used_names_.insert(name);
}
}
name_for_id_[id] = name;
}
spv_result_t FriendlyNameMapper::ParseInstruction(
const spv_parsed_instruction_t& inst) {
const auto result_id = inst.result_id;
switch (inst.opcode) {
case SpvOpName:
SaveName(inst.words[1], reinterpret_cast<const char*>(inst.words + 2));
break;
case SpvOpTypeVoid:
SaveName(result_id, "void");
break;
case SpvOpTypeBool:
SaveName(result_id, "bool");
break;
case SpvOpTypeInt: {
std::string signedness;
std::string root;
const auto bit_width = inst.words[2];
switch (bit_width) {
case 8:
root = "char";
break;
case 16:
root = "short";
break;
case 32:
root = "int";
break;
case 64:
root = "long";
break;
default:
root = to_string(bit_width);
signedness = "i";
break;
}
if (0 == inst.words[3]) signedness = "u";
SaveName(result_id, signedness + root);
} break;
case SpvOpTypeFloat: {
const auto bit_width = inst.words[2];
switch (bit_width) {
case 16:
SaveName(result_id, "half");
break;
case 32:
SaveName(result_id, "float");
break;
case 64:
SaveName(result_id, "double");
break;
default:
SaveName(result_id, std::string("fp") + to_string(bit_width));
break;
}
} break;
case SpvOpTypeVector:
SaveName(result_id, std::string("v") + to_string(inst.words[3]) +
NameForId(inst.words[2]));
break;
case SpvOpTypeMatrix:
SaveName(result_id, std::string("mat") + to_string(inst.words[3]) +
NameForId(inst.words[2]));
break;
case SpvOpTypeArray:
SaveName(result_id, std::string("_arr_") + NameForId(inst.words[2]) +
"_" + NameForId(inst.words[3]));
break;
case SpvOpTypeRuntimeArray:
SaveName(result_id,
std::string("_runtimearr_") + NameForId(inst.words[2]));
break;
case SpvOpTypePointer:
SaveName(result_id, std::string("_ptr_") +
NameForEnumOperand(SPV_OPERAND_TYPE_STORAGE_CLASS,
inst.words[2]) +
"_" + NameForId(inst.words[3]));
break;
case SpvOpTypePipe:
SaveName(result_id,
std::string("Pipe") +
NameForEnumOperand(SPV_OPERAND_TYPE_ACCESS_QUALIFIER,
inst.words[2]));
break;
case SpvOpTypeEvent:
SaveName(result_id, "Event");
break;
case SpvOpTypeDeviceEvent:
SaveName(result_id, "DeviceEvent");
break;
case SpvOpTypeReserveId:
SaveName(result_id, "ReserveId");
break;
case SpvOpTypeQueue:
SaveName(result_id, "Queue");
break;
case SpvOpTypeOpaque:
SaveName(result_id,
std::string("Opaque_") +
Sanitize(reinterpret_cast<const char*>(inst.words + 2)));
break;
case SpvOpTypePipeStorage:
SaveName(result_id, "PipeStorage");
break;
case SpvOpTypeNamedBarrier:
SaveName(result_id, "NamedBarrier");
break;
case SpvOpTypeStruct:
// Structs are mapped rather simplisitically. Just indicate that they
// are a struct and then give the raw Id number.
SaveName(result_id, std::string("_struct_") + to_string(result_id));
break;
default:
// If this instruction otherwise defines an Id, then save a mapping for
// it. This is needed to ensure uniqueness in there is an OpName with
// string something like "1" that might collide with this result_id.
// We should only do this if a name hasn't already been registered by some
// previous forward reference.
if (result_id && name_for_id_.find(result_id) == name_for_id_.end())
SaveName(result_id, to_string(result_id));
break;
}
return SPV_SUCCESS;
}
std::string FriendlyNameMapper::NameForEnumOperand(spv_operand_type_t type,
uint32_t word) {
spv_operand_desc desc = nullptr;
if (SPV_SUCCESS == grammar_.lookupOperand(type, word, &desc)) {
return desc->name;
} else {
// Invalid input. Just give something sane.
return std::string("StorageClass") + to_string(word);
}
}
} // namespace libspirv

126
source/name_mapper.h Normal file
Просмотреть файл

@ -0,0 +1,126 @@
// Copyright (c) 2016 Google Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and/or associated documentation files (the
// "Materials"), to deal in the Materials without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Materials, and to
// permit persons to whom the Materials are furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Materials.
//
// MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS
// KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS
// SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT
// https://www.khronos.org/registry/
//
// THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
#ifndef LIBSPIRV_NAME_MAPPER_H_
#define LIBSPIRV_NAME_MAPPER_H_
#include <functional>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include "spirv-tools/libspirv.h"
#include "assembly_grammar.h"
namespace libspirv {
// A NameMapper maps SPIR-V Id values to names. Each name is valid to use in
// SPIR-V assembly. The mapping is one-to-one, i.e. no two Ids map to the same
// name.
using NameMapper = std::function<std::string(uint32_t)>;
// Returns a NameMapper which always maps an Id to its decimal representation.
NameMapper GetTrivialNameMapper();
// A FriendlyNameMapper parses a module upon construction. If the parse is
// successful, then the NameForId method maps an Id to a friendly name
// while also satisfying the constraints on a NameMapper.
//
// The mapping is friendly in the following sense:
// - If an Id has a debug name (via OpName), then that will be used when
// possible.
// - Well known scalar types map to friendly names. For example,
// OpTypeVoid should be %void. Scalar types map to their names in OpenCL when
// there is a correspondence, and otherwise as follows:
// - unsigned integer type of n bits map to "u" followed by n
// - signed integer type of n bits map to "i" followed by n
// - floating point type of n bits map to "fp" followed by n
// - Vector type names map to "v" followed by the number of components,
// followed by the friendly name for the base type.
// - Matrix type names map to "mat" followed by the number of columns,
// followed by the friendly name for the base vector type.
// - Pointer types map to "_ptr_", then the name of the storage class, then the
// name for the pointee type.
// - Exotic types like event, pipe, opaque, queue, reserve-id map to their own
// human readable names.
// - A struct type maps to "_struct_" followed by the raw Id number. That's
// pretty simplistic, but workable.
class FriendlyNameMapper {
public:
// Construct a friendly name mapper, and determine friendly names for each
// defined Id in the specified module. The module is specified by the code
// wordCount, and should be parseable in the specified context.
FriendlyNameMapper(const spv_const_context context, const uint32_t* code,
const size_t wordCount);
// Returns a NameMapper which maps ids to the friendly names parsed from the
// module provided to the constructor.
NameMapper GetNameMapper() {
return [this](uint32_t id) { return this->NameForId(id); };
}
// Returns the friendly name for the given id. If the module parsed during
// construction is valid, then the mapping satisfies the rules for a
// NameMapper.
std::string NameForId(uint32_t id);
private:
// Transforms the given string so that it is acceptable as an Id name in
// assembly language. Two distinct inputs can map to the same output.
std::string Sanitize(const std::string& suggested_name);
// Records a name for the given id. Use the given suggested_name if it
// hasn't already been taken, and otherwise generate a new (unused) name
// based on the suggested name.
void SaveName(uint32_t id, const std::string& suggested_name);
// Collects information from the given parsed instruction to populate
// name_for_id_. Returns SPV_SUCCESS;
spv_result_t ParseInstruction(const spv_parsed_instruction_t& inst);
// Forwards a parsed-instruction callback from the binary parser into the
// FriendlyNameMapper hidden inside the user_data parameter.
static spv_result_t ParseInstructionForwarder(
void* user_data, const spv_parsed_instruction_t* parsed_instruction) {
return reinterpret_cast<FriendlyNameMapper*>(user_data)->ParseInstruction(
*parsed_instruction);
}
// Returns the friendly name for an enumerant.
std::string NameForEnumOperand(spv_operand_type_t type, uint32_t word);
// Maps an id to its friendly name. This will have an entry for each Id
// defined in the module.
std::unordered_map<uint32_t, std::string> name_for_id_;
// The set of names that have a mapping in name_for_id_;
std::unordered_set<std::string> used_names_;
// The assembly grammar for the current context.
const libspirv::AssemblyGrammar grammar_;
};
} // namespace libspirv
#endif // _LIBSPIRV_NAME_MAPPER_H_

Просмотреть файл

@ -406,6 +406,28 @@ OpStore %2 %3 Aligned|Volatile 4 ; bogus, but not indented
expected);
}
using FriendlyNameDisassemblyTest = spvtest::TextToBinaryTest;
TEST_F(FriendlyNameDisassemblyTest, Sample) {
const std::string input = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
%1 = OpTypeInt 32 0
%2 = OpTypeStruct %1 %3 %4 %5 %6 %7 %8 %9 %10 ; force IDs into double digits
%11 = OpConstant %1 42
)";
const std::string expected =
R"(OpCapability Shader
OpMemoryModel Logical GLSL450
%uint = OpTypeInt 32 0
%_struct_2 = OpTypeStruct %uint %3 %4 %5 %6 %7 %8 %9 %10
%11 = OpConstant %uint 42
)";
EXPECT_THAT(EncodeAndDecodeSuccessfully(
input, SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES),
expected);
}
TEST_F(TextToBinaryTest, ShowByteOffsetsWhenRequested) {
const std::string input = R"(
OpCapability Shader

Просмотреть файл

@ -87,6 +87,7 @@ if (NOT ${SPIRV_SKIP_EXECUTABLES})
${CMAKE_CURRENT_SOURCE_DIR}/ImmediateInt.cpp
${CMAKE_CURRENT_SOURCE_DIR}/LibspirvMacros.cpp
${CMAKE_CURRENT_SOURCE_DIR}/NamedId.cpp
${CMAKE_CURRENT_SOURCE_DIR}/NameMapper.cpp
${CMAKE_CURRENT_SOURCE_DIR}/OpcodeMake.cpp
${CMAKE_CURRENT_SOURCE_DIR}/OpcodeRequiresCapabilities.cpp
${CMAKE_CURRENT_SOURCE_DIR}/OpcodeSplit.cpp

213
test/NameMapper.cpp Normal file
Просмотреть файл

@ -0,0 +1,213 @@
// Copyright (c) 2016 Google Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and/or associated documentation files (the
// "Materials"), to deal in the Materials without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Materials, and to
// permit persons to whom the Materials are furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Materials.
//
// MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS
// KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS
// SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT
// https://www.khronos.org/registry/
//
// THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
#include "gmock/gmock.h"
#include "TestFixture.h"
#include "UnitSPIRV.h"
#include "source/name_mapper.h"
using libspirv::NameMapper;
using libspirv::FriendlyNameMapper;
using spvtest::ScopedContext;
using ::testing::Eq;
namespace {
TEST(TrivialNameTest, Samples) {
auto mapper = libspirv::GetTrivialNameMapper();
EXPECT_EQ(mapper(1), "1");
EXPECT_EQ(mapper(1999), "1999");
EXPECT_EQ(mapper(1024), "1024");
}
// A test case for the name mappers that actually look at an assembled module.
struct NameIdCase {
std::string assembly; // Input assembly text
uint32_t id;
std::string expected_name;
};
using FriendlyNameTest =
spvtest::TextToBinaryTestBase<::testing::TestWithParam<NameIdCase>>;
TEST_P(FriendlyNameTest, SingleMapping) {
ScopedContext context(SPV_ENV_UNIVERSAL_1_1);
auto words = CompileSuccessfully(GetParam().assembly, SPV_ENV_UNIVERSAL_1_1);
auto friendly_mapper =
FriendlyNameMapper(context.context, words.data(), words.size());
NameMapper mapper = friendly_mapper.GetNameMapper();
EXPECT_THAT(mapper(GetParam().id), Eq(GetParam().expected_name))
<< GetParam().assembly << std::endl
<< " for id " << GetParam().id;
}
INSTANTIATE_TEST_CASE_P(ScalarType, FriendlyNameTest,
::testing::ValuesIn(std::vector<NameIdCase>{
{"%1 = OpTypeVoid", 1, "void"},
{"%1 = OpTypeBool", 1, "bool"},
{"%1 = OpTypeInt 8 0", 1, "uchar"},
{"%1 = OpTypeInt 8 1", 1, "char"},
{"%1 = OpTypeInt 16 0", 1, "ushort"},
{"%1 = OpTypeInt 16 1", 1, "short"},
{"%1 = OpTypeInt 32 0", 1, "uint"},
{"%1 = OpTypeInt 32 1", 1, "int"},
{"%1 = OpTypeInt 64 0", 1, "ulong"},
{"%1 = OpTypeInt 64 1", 1, "long"},
{"%1 = OpTypeInt 1 0", 1, "u1"},
{"%1 = OpTypeInt 1 1", 1, "i1"},
{"%1 = OpTypeInt 33 0", 1, "u33"},
{"%1 = OpTypeInt 33 1", 1, "i33"},
{"%1 = OpTypeFloat 16", 1, "half"},
{"%1 = OpTypeFloat 32", 1, "float"},
{"%1 = OpTypeFloat 64", 1, "double"},
{"%1 = OpTypeFloat 10", 1, "fp10"},
{"%1 = OpTypeFloat 55", 1, "fp55"},
}), );
INSTANTIATE_TEST_CASE_P(
VectorType, FriendlyNameTest,
::testing::ValuesIn(std::vector<NameIdCase>{
{"%1 = OpTypeBool %2 = OpTypeVector %1 1", 2, "v1bool"},
{"%1 = OpTypeBool %2 = OpTypeVector %1 2", 2, "v2bool"},
{"%1 = OpTypeBool %2 = OpTypeVector %1 3", 2, "v3bool"},
{"%1 = OpTypeBool %2 = OpTypeVector %1 4", 2, "v4bool"},
{"%1 = OpTypeInt 8 0 %2 = OpTypeVector %1 2", 2, "v2uchar"},
{"%1 = OpTypeInt 16 1 %2 = OpTypeVector %1 3", 2, "v3short"},
{"%1 = OpTypeInt 32 0 %2 = OpTypeVector %1 4", 2, "v4uint"},
{"%1 = OpTypeInt 64 1 %2 = OpTypeVector %1 3", 2, "v3long"},
{"%1 = OpTypeInt 20 0 %2 = OpTypeVector %1 4", 2, "v4u20"},
{"%1 = OpTypeInt 21 1 %2 = OpTypeVector %1 3", 2, "v3i21"},
{"%1 = OpTypeFloat 32 %2 = OpTypeVector %1 2", 2, "v2float"},
// OpName overrides the element name.
{"OpName %1 \"time\" %1 = OpTypeFloat 32 %2 = OpTypeVector %1 2", 2,
"v2time"},
}), );
INSTANTIATE_TEST_CASE_P(
MatrixType, FriendlyNameTest,
::testing::ValuesIn(std::vector<NameIdCase>{
{"%1 = OpTypeBool %2 = OpTypeVector %1 2 %3 = OpTypeMatrix %2 2", 3,
"mat2v2bool"},
{"%1 = OpTypeFloat 32 %2 = OpTypeVector %1 2 %3 = OpTypeMatrix %2 3", 3,
"mat3v2float"},
{"%1 = OpTypeFloat 32 %2 = OpTypeVector %1 2 %3 = OpTypeMatrix %2 4", 3,
"mat4v2float"},
{"OpName %1 \"time\" %1 = OpTypeFloat 32 %2 = OpTypeVector %1 2 %3 = "
"OpTypeMatrix %2 4",
3, "mat4v2time"},
{"OpName %2 \"lat_long\" %1 = OpTypeFloat 32 %2 = OpTypeVector %1 2 %3 "
"= OpTypeMatrix %2 4",
3, "mat4lat_long"},
}), );
INSTANTIATE_TEST_CASE_P(
OpName, FriendlyNameTest,
::testing::ValuesIn(std::vector<NameIdCase>{
{"OpName %1 \"abcdefg\"", 1, "abcdefg"},
{"OpName %1 \"Hello world!\"", 1, "Hello_world_"},
{"OpName %1 \"0123456789\"", 1, "0123456789"},
// Test uniqueness of names that are forced to be
// numbers.
{"OpName %1 \"2\" OpName %2 \"2\"", 1, "2"},
{"OpName %1 \"2\" OpName %2 \"2\"", 2, "2_0"},
// Test uniqueness in the face of forward references
// for Ids that don't already have friendly names.
// In particular, the first OpDecorate assigns the name, and
// the second one can't override it.
{"OpDecorate %1 Volatile OpDecorate %1 Restrict", 1, "1"},
// But a forced name can override the name that
// would have been assigned via the OpDecorate
// forward reference.
{"OpName %1 \"mememe\" OpDecorate %1 Volatile OpDecorate %1 Restrict",
1, "mememe"},
// OpName can override other inferences. We assume valid instruction
// ordering, where OpName precedes type definitions.
{"OpName %1 \"myfloat\" %1 = OpTypeFloat 32", 1, "myfloat"},
}), );
INSTANTIATE_TEST_CASE_P(
UniquenessHeuristic, FriendlyNameTest,
::testing::ValuesIn(std::vector<NameIdCase>{
{"%1 = OpTypeVoid %2 = OpTypeVoid %3 = OpTypeVoid", 1, "void"},
{"%1 = OpTypeVoid %2 = OpTypeVoid %3 = OpTypeVoid", 2, "void_0"},
{"%1 = OpTypeVoid %2 = OpTypeVoid %3 = OpTypeVoid", 3, "void_1"},
}), );
INSTANTIATE_TEST_CASE_P(Arrays, FriendlyNameTest,
::testing::ValuesIn(std::vector<NameIdCase>{
{"OpName %2 \"FortyTwo\" %1 = OpTypeFloat 32 "
"%2 = OpConstant %1 42 %3 = OpTypeArray %1 %2",
3, "_arr_float_FortyTwo"},
{"%1 = OpTypeInt 32 0 "
"%2 = OpTypeRuntimeArray %1",
2, "_runtimearr_uint"},
}), );
INSTANTIATE_TEST_CASE_P(Structs, FriendlyNameTest,
::testing::ValuesIn(std::vector<NameIdCase>{
{"%1 = OpTypeBool "
"%2 = OpTypeStruct %1 %1 %1",
2, "_struct_2"},
{"%1 = OpTypeBool "
"%2 = OpTypeStruct %1 %1 %1 "
"%3 = OpTypeStruct %2 %2",
3, "_struct_3"},
}), );
INSTANTIATE_TEST_CASE_P(
Pointer, FriendlyNameTest,
::testing::ValuesIn(std::vector<NameIdCase>{
{"%1 = OpTypeFloat 32 %2 = OpTypePointer Workgroup %1", 2,
"_ptr_Workgroup_float"},
{"%1 = OpTypeBool %2 = OpTypePointer Private %1", 2,
"_ptr_Private_bool"},
// OpTypeForwardPointer doesn't force generation of the name for its
// target type.
{"%1 = OpTypeBool OpTypeForwardPointer %2 Private %2 = OpTypePointer "
"Private %1",
2, "_ptr_Private_bool"},
}), );
INSTANTIATE_TEST_CASE_P(ExoticTypes, FriendlyNameTest,
::testing::ValuesIn(std::vector<NameIdCase>{
{"%1 = OpTypeEvent", 1, "Event"},
{"%1 = OpTypeDeviceEvent", 1, "DeviceEvent"},
{"%1 = OpTypeReserveId", 1, "ReserveId"},
{"%1 = OpTypeQueue", 1, "Queue"},
{"%1 = OpTypeOpaque \"hello world!\"", 1, "Opaque_hello_world_"},
{"%1 = OpTypePipe ReadOnly", 1, "PipeReadOnly"},
{"%1 = OpTypePipe WriteOnly", 1, "PipeWriteOnly"},
{"%1 = OpTypePipe ReadWrite", 1, "PipeReadWrite"},
{"%1 = OpTypePipeStorage", 1, "PipeStorage"},
{"%1 = OpTypeNamedBarrier", 1, "NamedBarrier"},
}), );
} // anonymous namespace

Просмотреть файл

@ -57,6 +57,8 @@ Options:
--no-header Don't output the header as leading comments.
--raw-id Show raw Id values instead of friendly names.
--offsets Show byte offsets for each instruction.
)",
argv0, argv0);
@ -73,6 +75,7 @@ int main(int argc, char** argv) {
bool allow_indent = true;
bool show_byte_offsets = false;
bool no_header = false;
bool friendly_names = true;
for (int argi = 1; argi < argc; ++argi) {
if ('-' == argv[argi][0]) {
@ -98,6 +101,8 @@ int main(int argc, char** argv) {
show_byte_offsets = true;
} else if (0 == strcmp(argv[argi], "--no-header")) {
no_header = true;
} else if (0 == strcmp(argv[argi], "--raw-id")) {
friendly_names = false;
} else if (0 == strcmp(argv[argi], "--help")) {
print_usage(argv[0]);
return 0;
@ -142,6 +147,8 @@ int main(int argc, char** argv) {
if (no_header) options |= SPV_BINARY_TO_TEXT_OPTION_NO_HEADER;
if (friendly_names) options |= SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES;
if (!outFile || (0 == strcmp("-", outFile))) {
// Print to standard output.
options |= SPV_BINARY_TO_TEXT_OPTION_PRINT;