| // 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 |