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