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 "spirv-tools/libspirv.h"
26
27#include "source/latest_version_spirv_header.h"
28#include "source/parsed_operand.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 CASE(SubgroupEqMaskKHR)
157 CASE(SubgroupGeMaskKHR)
158 CASE(SubgroupGtMaskKHR)
159 CASE(SubgroupLeMaskKHR)
160 CASE(SubgroupLtMaskKHR)
161 default:
162 break;
163 }
164#undef GLCASE
165#undef GLCASE2
166#undef CASE
167}
168
169spv_result_t FriendlyNameMapper::ParseInstruction(
170 const spv_parsed_instruction_t& inst) {
171 const auto result_id = inst.result_id;
172 switch (inst.opcode) {
173 case SpvOpName:
174 SaveName(inst.words[1], reinterpret_cast<const char*>(inst.words + 2));
175 break;
176 case SpvOpDecorate:
177 // Decorations come after OpName. So OpName will take precedence over
178 // decorations.
179 //
180 // In theory, we should also handle OpGroupDecorate. But that's unlikely
181 // to occur.
182 if (inst.words[2] == SpvDecorationBuiltIn) {
183 assert(inst.num_words > 3);
184 SaveBuiltInName(inst.words[1], inst.words[3]);
185 }
186 break;
187 case SpvOpTypeVoid:
188 SaveName(result_id, "void");
189 break;
190 case SpvOpTypeBool:
191 SaveName(result_id, "bool");
192 break;
193 case SpvOpTypeInt: {
194 std::string signedness;
195 std::string root;
196 const auto bit_width = inst.words[2];
197 switch (bit_width) {
198 case 8:
199 root = "char";
200 break;
201 case 16:
202 root = "short";
203 break;
204 case 32:
205 root = "int";
206 break;
207 case 64:
208 root = "long";
209 break;
210 default:
211 root = to_string(bit_width);
212 signedness = "i";
213 break;
214 }
215 if (0 == inst.words[3]) signedness = "u";
216 SaveName(result_id, signedness + root);
217 } break;
218 case SpvOpTypeFloat: {
219 const auto bit_width = inst.words[2];
220 switch (bit_width) {
221 case 16:
222 SaveName(result_id, "half");
223 break;
224 case 32:
225 SaveName(result_id, "float");
226 break;
227 case 64:
228 SaveName(result_id, "double");
229 break;
230 default:
231 SaveName(result_id, std::string("fp") + to_string(bit_width));
232 break;
233 }
234 } break;
235 case SpvOpTypeVector:
236 SaveName(result_id, std::string("v") + to_string(inst.words[3]) +
237 NameForId(inst.words[2]));
238 break;
239 case SpvOpTypeMatrix:
240 SaveName(result_id, std::string("mat") + to_string(inst.words[3]) +
241 NameForId(inst.words[2]));
242 break;
243 case SpvOpTypeArray:
244 SaveName(result_id, std::string("_arr_") + NameForId(inst.words[2]) +
245 "_" + NameForId(inst.words[3]));
246 break;
247 case SpvOpTypeRuntimeArray:
248 SaveName(result_id,
249 std::string("_runtimearr_") + NameForId(inst.words[2]));
250 break;
251 case SpvOpTypePointer:
252 SaveName(result_id, std::string("_ptr_") +
253 NameForEnumOperand(SPV_OPERAND_TYPE_STORAGE_CLASS,
254 inst.words[2]) +
255 "_" + NameForId(inst.words[3]));
256 break;
257 case SpvOpTypePipe:
258 SaveName(result_id,
259 std::string("Pipe") +
260 NameForEnumOperand(SPV_OPERAND_TYPE_ACCESS_QUALIFIER,
261 inst.words[2]));
262 break;
263 case SpvOpTypeEvent:
264 SaveName(result_id, "Event");
265 break;
266 case SpvOpTypeDeviceEvent:
267 SaveName(result_id, "DeviceEvent");
268 break;
269 case SpvOpTypeReserveId:
270 SaveName(result_id, "ReserveId");
271 break;
272 case SpvOpTypeQueue:
273 SaveName(result_id, "Queue");
274 break;
275 case SpvOpTypeOpaque:
276 SaveName(result_id,
277 std::string("Opaque_") +
278 Sanitize(reinterpret_cast<const char*>(inst.words + 2)));
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 sane.
327 return std::string("StorageClass") + to_string(word);
328 }
329}
330
331} // namespace spvtools
332