1// Copyright (c) 2016 Google Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#include "source/name_mapper.h"
16
17#include <algorithm>
18#include <cassert>
19#include <iterator>
20#include <sstream>
21#include <string>
22#include <unordered_map>
23#include <unordered_set>
24
25#include "source/binary.h"
26#include "source/latest_version_spirv_header.h"
27#include "source/parsed_operand.h"
28#include "spirv-tools/libspirv.h"
29
30namespace spvtools {
31namespace {
32
33// Converts a uint32_t to its string decimal representation.
34std::string to_string(uint32_t id) {
35 // Use stringstream, since some versions of Android compilers lack
36 // std::to_string.
37 std::stringstream os;
38 os << id;
39 return os.str();
40}
41
42} // anonymous namespace
43
44NameMapper GetTrivialNameMapper() { return to_string; }
45
46FriendlyNameMapper::FriendlyNameMapper(const spv_const_context context,
47 const uint32_t* code,
48 const size_t wordCount)
49 : grammar_(AssemblyGrammar(context)) {
50 spv_diagnostic diag = nullptr;
51 // We don't care if the parse fails.
52 spvBinaryParse(context, this, code, wordCount, nullptr,
53 ParseInstructionForwarder, &diag);
54 spvDiagnosticDestroy(diag);
55}
56
57std::string FriendlyNameMapper::NameForId(uint32_t id) {
58 auto iter = name_for_id_.find(id);
59 if (iter == name_for_id_.end()) {
60 // It must have been an invalid module, so just return a trivial mapping.
61 // We don't care about uniqueness.
62 return to_string(id);
63 } else {
64 return iter->second;
65 }
66}
67
68std::string FriendlyNameMapper::Sanitize(const std::string& suggested_name) {
69 if (suggested_name.empty()) return "_";
70 // Otherwise, replace invalid characters by '_'.
71 std::string result;
72 std::string valid =
73 "abcdefghijklmnopqrstuvwxyz"
74 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
75 "_0123456789";
76 std::transform(suggested_name.begin(), suggested_name.end(),
77 std::back_inserter(result), [&valid](const char c) {
78 return (std::string::npos == valid.find(c)) ? '_' : c;
79 });
80 return result;
81}
82
83void FriendlyNameMapper::SaveName(uint32_t id,
84 const std::string& suggested_name) {
85 if (name_for_id_.find(id) != name_for_id_.end()) return;
86
87 const std::string sanitized_suggested_name = Sanitize(suggested_name);
88 std::string name = sanitized_suggested_name;
89 auto inserted = used_names_.insert(name);
90 if (!inserted.second) {
91 const std::string base_name = sanitized_suggested_name + "_";
92 for (uint32_t index = 0; !inserted.second; ++index) {
93 name = base_name + to_string(index);
94 inserted = used_names_.insert(name);
95 }
96 }
97 name_for_id_[id] = name;
98}
99
100void FriendlyNameMapper::SaveBuiltInName(uint32_t target_id,
101 uint32_t built_in) {
102#define GLCASE(name) \
103 case SpvBuiltIn##name: \
104 SaveName(target_id, "gl_" #name); \
105 return;
106#define GLCASE2(name, suggested) \
107 case SpvBuiltIn##name: \
108 SaveName(target_id, "gl_" #suggested); \
109 return;
110#define CASE(name) \
111 case SpvBuiltIn##name: \
112 SaveName(target_id, #name); \
113 return;
114 switch (built_in) {
115 GLCASE(Position)
116 GLCASE(PointSize)
117 GLCASE(ClipDistance)
118 GLCASE(CullDistance)
119 GLCASE2(VertexId, VertexID)
120 GLCASE2(InstanceId, InstanceID)
121 GLCASE2(PrimitiveId, PrimitiveID)
122 GLCASE2(InvocationId, InvocationID)
123 GLCASE(Layer)
124 GLCASE(ViewportIndex)
125 GLCASE(TessLevelOuter)
126 GLCASE(TessLevelInner)
127 GLCASE(TessCoord)
128 GLCASE(PatchVertices)
129 GLCASE(FragCoord)
130 GLCASE(PointCoord)
131 GLCASE(FrontFacing)
132 GLCASE2(SampleId, SampleID)
133 GLCASE(SamplePosition)
134 GLCASE(SampleMask)
135 GLCASE(FragDepth)
136 GLCASE(HelperInvocation)
137 GLCASE2(NumWorkgroups, NumWorkGroups)
138 GLCASE2(WorkgroupSize, WorkGroupSize)
139 GLCASE2(WorkgroupId, WorkGroupID)
140 GLCASE2(LocalInvocationId, LocalInvocationID)
141 GLCASE2(GlobalInvocationId, GlobalInvocationID)
142 GLCASE(LocalInvocationIndex)
143 CASE(WorkDim)
144 CASE(GlobalSize)
145 CASE(EnqueuedWorkgroupSize)
146 CASE(GlobalOffset)
147 CASE(GlobalLinearId)
148 CASE(SubgroupSize)
149 CASE(SubgroupMaxSize)
150 CASE(NumSubgroups)
151 CASE(NumEnqueuedSubgroups)
152 CASE(SubgroupId)
153 CASE(SubgroupLocalInvocationId)
154 GLCASE(VertexIndex)
155 GLCASE(InstanceIndex)
156 GLCASE(BaseInstance)
157 CASE(SubgroupEqMaskKHR)
158 CASE(SubgroupGeMaskKHR)
159 CASE(SubgroupGtMaskKHR)
160 CASE(SubgroupLeMaskKHR)
161 CASE(SubgroupLtMaskKHR)
162 default:
163 break;
164 }
165#undef GLCASE
166#undef GLCASE2
167#undef CASE
168}
169
170spv_result_t FriendlyNameMapper::ParseInstruction(
171 const spv_parsed_instruction_t& inst) {
172 const auto result_id = inst.result_id;
173 switch (inst.opcode) {
174 case SpvOpName:
175 SaveName(inst.words[1], spvDecodeLiteralStringOperand(inst, 1));
176 break;
177 case SpvOpDecorate:
178 // Decorations come after OpName. So OpName will take precedence over
179 // decorations.
180 //
181 // In theory, we should also handle OpGroupDecorate. But that's unlikely
182 // to occur.
183 if (inst.words[2] == SpvDecorationBuiltIn) {
184 assert(inst.num_words > 3);
185 SaveBuiltInName(inst.words[1], inst.words[3]);
186 }
187 break;
188 case SpvOpTypeVoid:
189 SaveName(result_id, "void");
190 break;
191 case SpvOpTypeBool:
192 SaveName(result_id, "bool");
193 break;
194 case SpvOpTypeInt: {
195 std::string signedness;
196 std::string root;
197 const auto bit_width = inst.words[2];
198 switch (bit_width) {
199 case 8:
200 root = "char";
201 break;
202 case 16:
203 root = "short";
204 break;
205 case 32:
206 root = "int";
207 break;
208 case 64:
209 root = "long";
210 break;
211 default:
212 root = to_string(bit_width);
213 signedness = "i";
214 break;
215 }
216 if (0 == inst.words[3]) signedness = "u";
217 SaveName(result_id, signedness + root);
218 } break;
219 case SpvOpTypeFloat: {
220 const auto bit_width = inst.words[2];
221 switch (bit_width) {
222 case 16:
223 SaveName(result_id, "half");
224 break;
225 case 32:
226 SaveName(result_id, "float");
227 break;
228 case 64:
229 SaveName(result_id, "double");
230 break;
231 default:
232 SaveName(result_id, std::string("fp") + to_string(bit_width));
233 break;
234 }
235 } break;
236 case SpvOpTypeVector:
237 SaveName(result_id, std::string("v") + to_string(inst.words[3]) +
238 NameForId(inst.words[2]));
239 break;
240 case SpvOpTypeMatrix:
241 SaveName(result_id, std::string("mat") + to_string(inst.words[3]) +
242 NameForId(inst.words[2]));
243 break;
244 case SpvOpTypeArray:
245 SaveName(result_id, std::string("_arr_") + NameForId(inst.words[2]) +
246 "_" + NameForId(inst.words[3]));
247 break;
248 case SpvOpTypeRuntimeArray:
249 SaveName(result_id,
250 std::string("_runtimearr_") + NameForId(inst.words[2]));
251 break;
252 case SpvOpTypePointer:
253 SaveName(result_id, std::string("_ptr_") +
254 NameForEnumOperand(SPV_OPERAND_TYPE_STORAGE_CLASS,
255 inst.words[2]) +
256 "_" + NameForId(inst.words[3]));
257 break;
258 case SpvOpTypePipe:
259 SaveName(result_id,
260 std::string("Pipe") +
261 NameForEnumOperand(SPV_OPERAND_TYPE_ACCESS_QUALIFIER,
262 inst.words[2]));
263 break;
264 case SpvOpTypeEvent:
265 SaveName(result_id, "Event");
266 break;
267 case SpvOpTypeDeviceEvent:
268 SaveName(result_id, "DeviceEvent");
269 break;
270 case SpvOpTypeReserveId:
271 SaveName(result_id, "ReserveId");
272 break;
273 case SpvOpTypeQueue:
274 SaveName(result_id, "Queue");
275 break;
276 case SpvOpTypeOpaque:
277 SaveName(result_id, std::string("Opaque_") +
278 Sanitize(spvDecodeLiteralStringOperand(inst, 1)));
279 break;
280 case SpvOpTypePipeStorage:
281 SaveName(result_id, "PipeStorage");
282 break;
283 case SpvOpTypeNamedBarrier:
284 SaveName(result_id, "NamedBarrier");
285 break;
286 case SpvOpTypeStruct:
287 // Structs are mapped rather simplisitically. Just indicate that they
288 // are a struct and then give the raw Id number.
289 SaveName(result_id, std::string("_struct_") + to_string(result_id));
290 break;
291 case SpvOpConstantTrue:
292 SaveName(result_id, "true");
293 break;
294 case SpvOpConstantFalse:
295 SaveName(result_id, "false");
296 break;
297 case SpvOpConstant: {
298 std::ostringstream value;
299 EmitNumericLiteral(&value, inst, inst.operands[2]);
300 auto value_str = value.str();
301 // Use 'n' to signify negative. Other invalid characters will be mapped
302 // to underscore.
303 for (auto& c : value_str)
304 if (c == '-') c = 'n';
305 SaveName(result_id, NameForId(inst.type_id) + "_" + value_str);
306 } break;
307 default:
308 // If this instruction otherwise defines an Id, then save a mapping for
309 // it. This is needed to ensure uniqueness in there is an OpName with
310 // string something like "1" that might collide with this result_id.
311 // We should only do this if a name hasn't already been registered by some
312 // previous forward reference.
313 if (result_id && name_for_id_.find(result_id) == name_for_id_.end())
314 SaveName(result_id, to_string(result_id));
315 break;
316 }
317 return SPV_SUCCESS;
318}
319
320std::string FriendlyNameMapper::NameForEnumOperand(spv_operand_type_t type,
321 uint32_t word) {
322 spv_operand_desc desc = nullptr;
323 if (SPV_SUCCESS == grammar_.lookupOperand(type, word, &desc)) {
324 return desc->name;
325 } else {
326 // Invalid input. Just give something.
327 return std::string("StorageClass") + to_string(word);
328 }
329}
330
331} // namespace spvtools
332