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:
Родитель
1a9385bbd0
Коммит
0bdcc23f7e
3
CHANGES
3
CHANGES
|
@ -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)) {
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
Загрузка…
Ссылка в новой задаче