|  | // Copyright (c) 2016 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. | 
|  |  | 
|  | #include "source/name_mapper.h" | 
|  |  | 
|  | #include <algorithm> | 
|  | #include <cassert> | 
|  | #include <iterator> | 
|  | #include <sstream> | 
|  | #include <string> | 
|  | #include <unordered_map> | 
|  | #include <unordered_set> | 
|  |  | 
|  | #include "source/binary.h" | 
|  | #include "source/latest_version_spirv_header.h" | 
|  | #include "source/parsed_operand.h" | 
|  | #include "spirv-tools/libspirv.h" | 
|  |  | 
|  | namespace spvtools { | 
|  | 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 | 
|  |  | 
|  | NameMapper GetTrivialNameMapper() { return to_string; } | 
|  |  | 
|  | FriendlyNameMapper::FriendlyNameMapper(const spv_const_context context, | 
|  | const uint32_t* code, | 
|  | const size_t wordCount) | 
|  | : grammar_(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) { | 
|  | if (suggested_name.empty()) return "_"; | 
|  | // Otherwise, 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; | 
|  | } | 
|  |  | 
|  | void FriendlyNameMapper::SaveBuiltInName(uint32_t target_id, | 
|  | uint32_t built_in) { | 
|  | #define GLCASE(name)                  \ | 
|  | case spv::BuiltIn::name:            \ | 
|  | SaveName(target_id, "gl_" #name); \ | 
|  | return; | 
|  | #define GLCASE2(name, suggested)           \ | 
|  | case spv::BuiltIn::name:                 \ | 
|  | SaveName(target_id, "gl_" #suggested); \ | 
|  | return; | 
|  | #define CASE(name)              \ | 
|  | case spv::BuiltIn::name:      \ | 
|  | SaveName(target_id, #name); \ | 
|  | return; | 
|  | switch (spv::BuiltIn(built_in)) { | 
|  | GLCASE(Position) | 
|  | GLCASE(PointSize) | 
|  | GLCASE(ClipDistance) | 
|  | GLCASE(CullDistance) | 
|  | GLCASE2(VertexId, VertexID) | 
|  | GLCASE2(InstanceId, InstanceID) | 
|  | GLCASE2(PrimitiveId, PrimitiveID) | 
|  | GLCASE2(InvocationId, InvocationID) | 
|  | GLCASE(Layer) | 
|  | GLCASE(ViewportIndex) | 
|  | GLCASE(TessLevelOuter) | 
|  | GLCASE(TessLevelInner) | 
|  | GLCASE(TessCoord) | 
|  | GLCASE(PatchVertices) | 
|  | GLCASE(FragCoord) | 
|  | GLCASE(PointCoord) | 
|  | GLCASE(FrontFacing) | 
|  | GLCASE2(SampleId, SampleID) | 
|  | GLCASE(SamplePosition) | 
|  | GLCASE(SampleMask) | 
|  | GLCASE(FragDepth) | 
|  | GLCASE(HelperInvocation) | 
|  | GLCASE2(NumWorkgroups, NumWorkGroups) | 
|  | GLCASE2(WorkgroupSize, WorkGroupSize) | 
|  | GLCASE2(WorkgroupId, WorkGroupID) | 
|  | GLCASE2(LocalInvocationId, LocalInvocationID) | 
|  | GLCASE2(GlobalInvocationId, GlobalInvocationID) | 
|  | GLCASE(LocalInvocationIndex) | 
|  | CASE(WorkDim) | 
|  | CASE(GlobalSize) | 
|  | CASE(EnqueuedWorkgroupSize) | 
|  | CASE(GlobalOffset) | 
|  | CASE(GlobalLinearId) | 
|  | CASE(SubgroupSize) | 
|  | CASE(SubgroupMaxSize) | 
|  | CASE(NumSubgroups) | 
|  | CASE(NumEnqueuedSubgroups) | 
|  | CASE(SubgroupId) | 
|  | CASE(SubgroupLocalInvocationId) | 
|  | GLCASE(VertexIndex) | 
|  | GLCASE(InstanceIndex) | 
|  | GLCASE(BaseInstance) | 
|  | CASE(SubgroupEqMaskKHR) | 
|  | CASE(SubgroupGeMaskKHR) | 
|  | CASE(SubgroupGtMaskKHR) | 
|  | CASE(SubgroupLeMaskKHR) | 
|  | CASE(SubgroupLtMaskKHR) | 
|  | default: | 
|  | break; | 
|  | } | 
|  | #undef GLCASE | 
|  | #undef GLCASE2 | 
|  | #undef CASE | 
|  | } | 
|  |  | 
|  | spv_result_t FriendlyNameMapper::ParseInstruction( | 
|  | const spv_parsed_instruction_t& inst) { | 
|  | const auto result_id = inst.result_id; | 
|  | switch (spv::Op(inst.opcode)) { | 
|  | case spv::Op::OpName: | 
|  | SaveName(inst.words[1], spvDecodeLiteralStringOperand(inst, 1)); | 
|  | break; | 
|  | case spv::Op::OpDecorate: | 
|  | // Decorations come after OpName.  So OpName will take precedence over | 
|  | // decorations. | 
|  | // | 
|  | // In theory, we should also handle OpGroupDecorate.  But that's unlikely | 
|  | // to occur. | 
|  | if (spv::Decoration(inst.words[2]) == spv::Decoration::BuiltIn) { | 
|  | assert(inst.num_words > 3); | 
|  | SaveBuiltInName(inst.words[1], inst.words[3]); | 
|  | } | 
|  | break; | 
|  | case spv::Op::OpTypeVoid: | 
|  | SaveName(result_id, "void"); | 
|  | break; | 
|  | case spv::Op::OpTypeBool: | 
|  | SaveName(result_id, "bool"); | 
|  | break; | 
|  | case spv::Op::OpTypeInt: { | 
|  | 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 spv::Op::OpTypeFloat: { | 
|  | 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 spv::Op::OpTypeVector: | 
|  | SaveName(result_id, std::string("v") + to_string(inst.words[3]) + | 
|  | NameForId(inst.words[2])); | 
|  | break; | 
|  | case spv::Op::OpTypeMatrix: | 
|  | SaveName(result_id, std::string("mat") + to_string(inst.words[3]) + | 
|  | NameForId(inst.words[2])); | 
|  | break; | 
|  | case spv::Op::OpTypeArray: | 
|  | SaveName(result_id, std::string("_arr_") + NameForId(inst.words[2]) + | 
|  | "_" + NameForId(inst.words[3])); | 
|  | break; | 
|  | case spv::Op::OpTypeRuntimeArray: | 
|  | SaveName(result_id, | 
|  | std::string("_runtimearr_") + NameForId(inst.words[2])); | 
|  | break; | 
|  | case spv::Op::OpTypePointer: | 
|  | SaveName(result_id, std::string("_ptr_") + | 
|  | NameForEnumOperand(SPV_OPERAND_TYPE_STORAGE_CLASS, | 
|  | inst.words[2]) + | 
|  | "_" + NameForId(inst.words[3])); | 
|  | break; | 
|  | case spv::Op::OpTypePipe: | 
|  | SaveName(result_id, | 
|  | std::string("Pipe") + | 
|  | NameForEnumOperand(SPV_OPERAND_TYPE_ACCESS_QUALIFIER, | 
|  | inst.words[2])); | 
|  | break; | 
|  | case spv::Op::OpTypeEvent: | 
|  | SaveName(result_id, "Event"); | 
|  | break; | 
|  | case spv::Op::OpTypeDeviceEvent: | 
|  | SaveName(result_id, "DeviceEvent"); | 
|  | break; | 
|  | case spv::Op::OpTypeReserveId: | 
|  | SaveName(result_id, "ReserveId"); | 
|  | break; | 
|  | case spv::Op::OpTypeQueue: | 
|  | SaveName(result_id, "Queue"); | 
|  | break; | 
|  | case spv::Op::OpTypeOpaque: | 
|  | SaveName(result_id, std::string("Opaque_") + | 
|  | Sanitize(spvDecodeLiteralStringOperand(inst, 1))); | 
|  | break; | 
|  | case spv::Op::OpTypePipeStorage: | 
|  | SaveName(result_id, "PipeStorage"); | 
|  | break; | 
|  | case spv::Op::OpTypeNamedBarrier: | 
|  | SaveName(result_id, "NamedBarrier"); | 
|  | break; | 
|  | case spv::Op::OpTypeStruct: | 
|  | // 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; | 
|  | case spv::Op::OpConstantTrue: | 
|  | SaveName(result_id, "true"); | 
|  | break; | 
|  | case spv::Op::OpConstantFalse: | 
|  | SaveName(result_id, "false"); | 
|  | break; | 
|  | case spv::Op::OpConstant: { | 
|  | std::ostringstream value; | 
|  | EmitNumericLiteral(&value, inst, inst.operands[2]); | 
|  | auto value_str = value.str(); | 
|  | // Use 'n' to signify negative. Other invalid characters will be mapped | 
|  | // to underscore. | 
|  | for (auto& c : value_str) | 
|  | if (c == '-') c = 'n'; | 
|  | SaveName(result_id, NameForId(inst.type_id) + "_" + value_str); | 
|  | } 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. | 
|  | return std::string("StorageClass") + to_string(word); | 
|  | } | 
|  | } | 
|  |  | 
|  | }  // namespace spvtools |