1 // Copyright 2020 The Tint Authors.
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 "src/reader/spirv/namer.h"
16
17 #include <algorithm>
18 #include <sstream>
19 #include <unordered_set>
20
21 #include "src/debug.h"
22
23 namespace tint {
24 namespace reader {
25 namespace spirv {
26
27 namespace {
28
29 const char* kWGSLReservedWords[] = {
30 // Please keep this list sorted
31 "array", "as", "asm",
32 "bf16", "binding", "block",
33 "bool", "break", "builtin",
34 "case", "cast", "compute",
35 "const", "continue", "default",
36 "discard", "do", "else",
37 "elseif", "entry_point", "enum",
38 "f16", "f32", "fallthrough",
39 "false", "fn", "for",
40 "fragment", "i16", "i32",
41 "i64", "i8", "if",
42 "image", "import", "in",
43 "let", "location", "loop",
44 "mat2x2", "mat2x3", "mat2x4",
45 "mat3x2", "mat3x3", "mat3x4",
46 "mat4x2", "mat4x3", "mat4x4",
47 "offset", "out", "override",
48 "premerge", "private", "ptr",
49 "regardless", "return", "set",
50 "storage", "struct", "switch",
51 "true", "type", "typedef",
52 "u16", "u32", "u64",
53 "u8", "uniform", "uniform_constant",
54 "unless", "using", "var",
55 "vec2", "vec3", "vec4",
56 "vertex", "void", "while",
57 "workgroup",
58 };
59
60 } // namespace
61
Namer(const FailStream & fail_stream)62 Namer::Namer(const FailStream& fail_stream) : fail_stream_(fail_stream) {
63 for (const auto* reserved : kWGSLReservedWords) {
64 name_to_id_[std::string(reserved)] = 0;
65 }
66 }
67
68 Namer::~Namer() = default;
69
Sanitize(const std::string & suggested_name)70 std::string Namer::Sanitize(const std::string& suggested_name) {
71 if (suggested_name.empty()) {
72 return "empty";
73 }
74 // Otherwise, replace invalid characters by '_'.
75 std::string result;
76 std::string invalid_as_first_char = "_0123456789";
77 std::string valid =
78 "abcdefghijklmnopqrstuvwxyz"
79 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
80 "_0123456789";
81 // If the first character is invalid for starting a WGSL identifier, then
82 // prefix the result with "x".
83 if ((std::string::npos != invalid_as_first_char.find(suggested_name[0])) ||
84 (std::string::npos == valid.find(suggested_name[0]))) {
85 result = "x";
86 }
87 std::transform(suggested_name.begin(), suggested_name.end(),
88 std::back_inserter(result), [&valid](const char c) {
89 return (std::string::npos == valid.find(c)) ? '_' : c;
90 });
91 return result;
92 }
93
GetMemberName(uint32_t struct_id,uint32_t member_index) const94 std::string Namer::GetMemberName(uint32_t struct_id,
95 uint32_t member_index) const {
96 std::string result;
97 auto where = struct_member_names_.find(struct_id);
98 if (where != struct_member_names_.end()) {
99 auto& member_names = where->second;
100 if (member_index < member_names.size()) {
101 result = member_names[member_index];
102 }
103 }
104 return result;
105 }
106
FindUnusedDerivedName(const std::string & base_name) const107 std::string Namer::FindUnusedDerivedName(const std::string& base_name) const {
108 // Ensure uniqueness among names.
109 std::string derived_name;
110 int i = 0;
111 do {
112 std::stringstream new_name_stream;
113 new_name_stream << base_name;
114 if (i > 0) {
115 new_name_stream << "_" << i;
116 }
117 i++;
118 derived_name = new_name_stream.str();
119 } while (IsRegistered(derived_name));
120 return derived_name;
121 }
122
MakeDerivedName(const std::string & base_name)123 std::string Namer::MakeDerivedName(const std::string& base_name) {
124 auto result = FindUnusedDerivedName(base_name);
125 const bool registered = RegisterWithoutId(result);
126 TINT_ASSERT(Reader, registered);
127 return result;
128 }
129
Register(uint32_t id,const std::string & name)130 bool Namer::Register(uint32_t id, const std::string& name) {
131 if (HasName(id)) {
132 return Fail() << "internal error: ID " << id
133 << " already has registered name: " << id_to_name_[id];
134 }
135 if (!RegisterWithoutId(name)) {
136 return false;
137 }
138 id_to_name_[id] = name;
139 name_to_id_[name] = id;
140 return true;
141 }
142
RegisterWithoutId(const std::string & name)143 bool Namer::RegisterWithoutId(const std::string& name) {
144 if (IsRegistered(name)) {
145 return Fail() << "internal error: name already registered: " << name;
146 }
147 name_to_id_[name] = 0;
148 return true;
149 }
150
SuggestSanitizedName(uint32_t id,const std::string & suggested_name)151 bool Namer::SuggestSanitizedName(uint32_t id,
152 const std::string& suggested_name) {
153 if (HasName(id)) {
154 return false;
155 }
156
157 return Register(id, FindUnusedDerivedName(Sanitize(suggested_name)));
158 }
159
SuggestSanitizedMemberName(uint32_t struct_id,uint32_t member_index,const std::string & suggested_name)160 bool Namer::SuggestSanitizedMemberName(uint32_t struct_id,
161 uint32_t member_index,
162 const std::string& suggested_name) {
163 // Creates an empty vector the first time we visit this struct.
164 auto& name_vector = struct_member_names_[struct_id];
165 // Resizing will set new entries to the empty string.
166 name_vector.resize(std::max(name_vector.size(), size_t(member_index + 1)));
167 auto& entry = name_vector[member_index];
168 if (entry.empty()) {
169 entry = Sanitize(suggested_name);
170 return true;
171 }
172 return false;
173 }
174
ResolveMemberNamesForStruct(uint32_t struct_id,uint32_t num_members)175 void Namer::ResolveMemberNamesForStruct(uint32_t struct_id,
176 uint32_t num_members) {
177 auto& name_vector = struct_member_names_[struct_id];
178 // Resizing will set new entries to the empty string.
179 // It would have been an error if the client had registered a name for
180 // an out-of-bounds member index, so toss those away.
181 name_vector.resize(num_members);
182
183 std::unordered_set<std::string> used_names;
184
185 // Returns a name, based on the suggestion, which does not equal
186 // any name in the used_names set.
187 auto disambiguate_name =
188 [&used_names](const std::string& suggestion) -> std::string {
189 if (used_names.find(suggestion) == used_names.end()) {
190 // There is no collision.
191 return suggestion;
192 }
193
194 uint32_t i = 1;
195 std::string new_name;
196 do {
197 std::stringstream new_name_stream;
198 new_name_stream << suggestion << "_" << i;
199 new_name = new_name_stream.str();
200 ++i;
201 } while (used_names.find(new_name) != used_names.end());
202 return new_name;
203 };
204
205 // First ensure uniqueness among names for which we have already taken
206 // suggestions.
207 for (auto& name : name_vector) {
208 if (!name.empty()) {
209 // This modifies the names in-place, i.e. update the name_vector
210 // entries.
211 name = disambiguate_name(name);
212 used_names.insert(name);
213 }
214 }
215
216 // Now ensure uniqueness among the rest. Doing this in a second pass
217 // allows us to preserve suggestions as much as possible. Otherwise
218 // a generated name such as 'field1' might collide with a user-suggested
219 // name of 'field1' attached to a later member.
220 uint32_t index = 0;
221 for (auto& name : name_vector) {
222 if (name.empty()) {
223 std::stringstream suggestion;
224 suggestion << "field" << index;
225 // Again, modify the name-vector in-place.
226 name = disambiguate_name(suggestion.str());
227 used_names.insert(name);
228 }
229 index++;
230 }
231 }
232
233 } // namespace spirv
234 } // namespace reader
235 } // namespace tint
236