/src/spirv-cross/spirv_cross_parsed_ir.cpp
Line | Count | Source |
1 | | /* |
2 | | * Copyright 2018-2021 Arm Limited |
3 | | * SPDX-License-Identifier: Apache-2.0 OR MIT |
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 | | |
18 | | /* |
19 | | * At your option, you may choose to accept this material under either: |
20 | | * 1. The Apache License, Version 2.0, found at <http://www.apache.org/licenses/LICENSE-2.0>, or |
21 | | * 2. The MIT License, found at <http://opensource.org/licenses/MIT>. |
22 | | */ |
23 | | |
24 | | #include "spirv_cross_parsed_ir.hpp" |
25 | | #include <algorithm> |
26 | | #include <assert.h> |
27 | | |
28 | | using namespace std; |
29 | | using namespace SPIRV_CROSS_SPV_HEADER_NAMESPACE; |
30 | | |
31 | | namespace SPIRV_CROSS_NAMESPACE |
32 | | { |
33 | | ParsedIR::ParsedIR() |
34 | 516 | { |
35 | | // If we move ParsedIR, we need to make sure the pointer stays fixed since the child Variant objects consume a pointer to this group, |
36 | | // so need an extra pointer here. |
37 | 516 | pool_group.reset(new ObjectPoolGroup); |
38 | | |
39 | 516 | pool_group->pools[TypeType].reset(new ObjectPool<SPIRType>); |
40 | 516 | pool_group->pools[TypeVariable].reset(new ObjectPool<SPIRVariable>); |
41 | 516 | pool_group->pools[TypeConstant].reset(new ObjectPool<SPIRConstant>); |
42 | 516 | pool_group->pools[TypeFunction].reset(new ObjectPool<SPIRFunction>); |
43 | 516 | pool_group->pools[TypeFunctionPrototype].reset(new ObjectPool<SPIRFunctionPrototype>); |
44 | 516 | pool_group->pools[TypeBlock].reset(new ObjectPool<SPIRBlock>); |
45 | 516 | pool_group->pools[TypeExtension].reset(new ObjectPool<SPIRExtension>); |
46 | 516 | pool_group->pools[TypeExpression].reset(new ObjectPool<SPIRExpression>); |
47 | 516 | pool_group->pools[TypeConstantOp].reset(new ObjectPool<SPIRConstantOp>); |
48 | 516 | pool_group->pools[TypeCombinedImageSampler].reset(new ObjectPool<SPIRCombinedImageSampler>); |
49 | 516 | pool_group->pools[TypeAccessChain].reset(new ObjectPool<SPIRAccessChain>); |
50 | 516 | pool_group->pools[TypeUndef].reset(new ObjectPool<SPIRUndef>); |
51 | 516 | pool_group->pools[TypeString].reset(new ObjectPool<SPIRString>); |
52 | 516 | pool_group->pools[TypeDebugLocalVariable].reset(new ObjectPool<SPIRDebugLocalVariable>); |
53 | 516 | } |
54 | | |
55 | | // Should have been default-implemented, but need this on MSVC 2013. |
56 | | ParsedIR::ParsedIR(ParsedIR &&other) SPIRV_CROSS_NOEXCEPT |
57 | 0 | { |
58 | 0 | *this = std::move(other); |
59 | 0 | } |
60 | | |
61 | | ParsedIR &ParsedIR::operator=(ParsedIR &&other) SPIRV_CROSS_NOEXCEPT |
62 | 0 | { |
63 | 0 | if (this != &other) |
64 | 0 | { |
65 | 0 | pool_group = std::move(other.pool_group); |
66 | 0 | spirv = std::move(other.spirv); |
67 | 0 | meta = std::move(other.meta); |
68 | 0 | for (int i = 0; i < TypeCount; i++) |
69 | 0 | ids_for_type[i] = std::move(other.ids_for_type[i]); |
70 | 0 | ids_for_constant_undef_or_type = std::move(other.ids_for_constant_undef_or_type); |
71 | 0 | ids_for_constant_or_variable = std::move(other.ids_for_constant_or_variable); |
72 | 0 | declared_capabilities = std::move(other.declared_capabilities); |
73 | 0 | declared_extensions = std::move(other.declared_extensions); |
74 | 0 | block_meta = std::move(other.block_meta); |
75 | 0 | continue_block_to_loop_header = std::move(other.continue_block_to_loop_header); |
76 | 0 | entry_points = std::move(other.entry_points); |
77 | 0 | ids = std::move(other.ids); |
78 | 0 | addressing_model = other.addressing_model; |
79 | 0 | memory_model = other.memory_model; |
80 | |
|
81 | 0 | default_entry_point = other.default_entry_point; |
82 | 0 | sources = std::move(other.sources); |
83 | 0 | loop_iteration_depth_hard = other.loop_iteration_depth_hard; |
84 | 0 | loop_iteration_depth_soft = other.loop_iteration_depth_soft; |
85 | |
|
86 | 0 | meta_needing_name_fixup = std::move(other.meta_needing_name_fixup); |
87 | 0 | load_type_width = std::move(other.load_type_width); |
88 | 0 | } |
89 | 0 | return *this; |
90 | 0 | } |
91 | | |
92 | | ParsedIR::ParsedIR(const ParsedIR &other) |
93 | 0 | : ParsedIR() |
94 | 0 | { |
95 | 0 | *this = other; |
96 | 0 | } |
97 | | |
98 | | ParsedIR &ParsedIR::operator=(const ParsedIR &other) |
99 | 0 | { |
100 | 0 | if (this != &other) |
101 | 0 | { |
102 | 0 | spirv = other.spirv; |
103 | 0 | meta = other.meta; |
104 | 0 | for (int i = 0; i < TypeCount; i++) |
105 | 0 | ids_for_type[i] = other.ids_for_type[i]; |
106 | 0 | ids_for_constant_undef_or_type = other.ids_for_constant_undef_or_type; |
107 | 0 | ids_for_constant_or_variable = other.ids_for_constant_or_variable; |
108 | 0 | declared_capabilities = other.declared_capabilities; |
109 | 0 | declared_extensions = other.declared_extensions; |
110 | 0 | block_meta = other.block_meta; |
111 | 0 | continue_block_to_loop_header = other.continue_block_to_loop_header; |
112 | 0 | entry_points = other.entry_points; |
113 | 0 | default_entry_point = other.default_entry_point; |
114 | 0 | sources = other.sources; |
115 | 0 | loop_iteration_depth_hard = other.loop_iteration_depth_hard; |
116 | 0 | loop_iteration_depth_soft = other.loop_iteration_depth_soft; |
117 | 0 | addressing_model = other.addressing_model; |
118 | 0 | memory_model = other.memory_model; |
119 | | |
120 | |
|
121 | 0 | meta_needing_name_fixup = other.meta_needing_name_fixup; |
122 | 0 | load_type_width = other.load_type_width; |
123 | | |
124 | | // Very deliberate copying of IDs. There is no default copy constructor, nor a simple default constructor. |
125 | | // Construct object first so we have the correct allocator set-up, then we can copy object into our new pool group. |
126 | 0 | ids.clear(); |
127 | 0 | ids.reserve(other.ids.size()); |
128 | 0 | for (size_t i = 0; i < other.ids.size(); i++) |
129 | 0 | { |
130 | 0 | ids.emplace_back(pool_group.get()); |
131 | 0 | ids.back() = other.ids[i]; |
132 | 0 | } |
133 | 0 | } |
134 | 0 | return *this; |
135 | 0 | } |
136 | | |
137 | | void ParsedIR::set_id_bounds(uint32_t bounds) |
138 | 511 | { |
139 | 511 | ids.reserve(bounds); |
140 | 93.7M | while (ids.size() < bounds) |
141 | 93.7M | ids.emplace_back(pool_group.get()); |
142 | | |
143 | 511 | block_meta.resize(bounds); |
144 | 511 | } |
145 | | |
146 | | // Roll our own versions of these functions to avoid potential locale shenanigans. |
147 | | static bool is_alpha(char c) |
148 | 33.8k | { |
149 | 33.8k | return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); |
150 | 33.8k | } |
151 | | |
152 | | static bool is_numeric(char c) |
153 | 24.1k | { |
154 | 24.1k | return c >= '0' && c <= '9'; |
155 | 24.1k | } |
156 | | |
157 | | static bool is_alphanumeric(char c) |
158 | 33.8k | { |
159 | 33.8k | return is_alpha(c) || is_numeric(c); |
160 | 33.8k | } |
161 | | |
162 | | static bool is_valid_identifier(const string &name) |
163 | 11.6k | { |
164 | 11.6k | if (name.empty()) |
165 | 163 | return true; |
166 | | |
167 | 11.4k | if (is_numeric(name[0])) |
168 | 6 | return false; |
169 | | |
170 | 11.4k | for (auto c : name) |
171 | 33.8k | if (!is_alphanumeric(c) && c != '_') |
172 | 7.19k | return false; |
173 | | |
174 | 4.27k | bool saw_underscore = false; |
175 | | // Two underscores in a row is not a valid identifier either. |
176 | | // Technically reserved, but it's easier to treat it as invalid. |
177 | 4.27k | for (auto c : name) |
178 | 22.9k | { |
179 | 22.9k | bool is_underscore = c == '_'; |
180 | 22.9k | if (is_underscore && saw_underscore) |
181 | 394 | return false; |
182 | 22.5k | saw_underscore = is_underscore; |
183 | 22.5k | } |
184 | | |
185 | 3.87k | return true; |
186 | 4.27k | } |
187 | | |
188 | | static bool is_reserved_prefix(const string &name) |
189 | 4.04k | { |
190 | | // Generic reserved identifiers used by the implementation. |
191 | 4.04k | return name.compare(0, 3, "gl_", 3) == 0 || |
192 | | // Ignore this case for now, might rewrite internal code to always use spv prefix. |
193 | | //name.compare(0, 11, "SPIRV_Cross", 11) == 0 || |
194 | 3.93k | name.compare(0, 3, "spv", 3) == 0; |
195 | 4.04k | } |
196 | | |
197 | | static bool is_reserved_identifier(const string &name, bool member, bool allow_reserved_prefixes) |
198 | 4.04k | { |
199 | 4.04k | if (!allow_reserved_prefixes && is_reserved_prefix(name)) |
200 | 115 | return true; |
201 | | |
202 | 3.92k | if (member) |
203 | 1.09k | { |
204 | | // Reserved member identifiers come in one form: |
205 | | // _m[0-9]+$. |
206 | 1.09k | if (name.size() < 3) |
207 | 728 | return false; |
208 | | |
209 | 371 | if (name.compare(0, 2, "_m", 2) != 0) |
210 | 153 | return false; |
211 | | |
212 | 218 | size_t index = 2; |
213 | 218 | while (index < name.size() && is_numeric(name[index])) |
214 | 0 | index++; |
215 | | |
216 | 218 | return index == name.size(); |
217 | 371 | } |
218 | 2.82k | else |
219 | 2.82k | { |
220 | | // Reserved non-member identifiers come in two forms: |
221 | | // _[0-9]+$, used for temporaries which map directly to a SPIR-V ID. |
222 | | // _[0-9]+_, used for auxillary temporaries which derived from a SPIR-V ID. |
223 | 2.82k | if (name.size() < 2) |
224 | 762 | return false; |
225 | | |
226 | 2.06k | if (name[0] != '_' || !is_numeric(name[1])) |
227 | 1.75k | return false; |
228 | | |
229 | 311 | size_t index = 2; |
230 | 936 | while (index < name.size() && is_numeric(name[index])) |
231 | 625 | index++; |
232 | | |
233 | 311 | return index == name.size() || (index < name.size() && name[index] == '_'); |
234 | 2.06k | } |
235 | 3.92k | } |
236 | | |
237 | | bool ParsedIR::is_globally_reserved_identifier(std::string &str, bool allow_reserved_prefixes) |
238 | 0 | { |
239 | 0 | return is_reserved_identifier(str, false, allow_reserved_prefixes); |
240 | 0 | } |
241 | | |
242 | | uint32_t ParsedIR::get_spirv_version() const |
243 | 0 | { |
244 | 0 | return spirv[1]; |
245 | 0 | } |
246 | | |
247 | | static string make_unreserved_identifier(const string &name) |
248 | 0 | { |
249 | 0 | if (is_reserved_prefix(name)) |
250 | 0 | return "_RESERVED_IDENTIFIER_FIXUP_" + name; |
251 | 0 | else |
252 | 0 | return "_RESERVED_IDENTIFIER_FIXUP" + name; |
253 | 0 | } |
254 | | |
255 | | void ParsedIR::sanitize_underscores(std::string &str) |
256 | 0 | { |
257 | | // Compact adjacent underscores to make it valid. |
258 | 0 | auto dst = str.begin(); |
259 | 0 | auto src = dst; |
260 | 0 | bool saw_underscore = false; |
261 | 0 | while (src != str.end()) |
262 | 0 | { |
263 | 0 | bool is_underscore = *src == '_'; |
264 | 0 | if (saw_underscore && is_underscore) |
265 | 0 | { |
266 | 0 | src++; |
267 | 0 | } |
268 | 0 | else |
269 | 0 | { |
270 | 0 | if (dst != src) |
271 | 0 | *dst = *src; |
272 | 0 | dst++; |
273 | 0 | src++; |
274 | 0 | saw_underscore = is_underscore; |
275 | 0 | } |
276 | 0 | } |
277 | 0 | str.erase(dst, str.end()); |
278 | 0 | } |
279 | | |
280 | | static string ensure_valid_identifier(const string &name) |
281 | 0 | { |
282 | | // Functions in glslangValidator are mangled with name(<mangled> stuff. |
283 | | // Normally, we would never see '(' in any legal identifiers, so just strip them out. |
284 | 0 | auto str = name.substr(0, name.find('(')); |
285 | |
|
286 | 0 | if (str.empty()) |
287 | 0 | return str; |
288 | | |
289 | 0 | if (is_numeric(str[0])) |
290 | 0 | str[0] = '_'; |
291 | |
|
292 | 0 | for (auto &c : str) |
293 | 0 | if (!is_alphanumeric(c) && c != '_') |
294 | 0 | c = '_'; |
295 | |
|
296 | 0 | ParsedIR::sanitize_underscores(str); |
297 | 0 | return str; |
298 | 0 | } |
299 | | |
300 | | const string &ParsedIR::get_name(ID id) const |
301 | 10.4k | { |
302 | 10.4k | auto *m = find_meta(id); |
303 | 10.4k | if (m) |
304 | 6.38k | return m->decoration.alias; |
305 | 4.09k | else |
306 | 4.09k | return empty_string; |
307 | 10.4k | } |
308 | | |
309 | | const string &ParsedIR::get_member_name(TypeID id, uint32_t index) const |
310 | 0 | { |
311 | 0 | auto *m = find_meta(id); |
312 | 0 | if (m) |
313 | 0 | { |
314 | 0 | if (index >= m->members.size()) |
315 | 0 | return empty_string; |
316 | 0 | return m->members[index].alias; |
317 | 0 | } |
318 | 0 | else |
319 | 0 | return empty_string; |
320 | 0 | } |
321 | | |
322 | | void ParsedIR::sanitize_identifier(std::string &name, bool member, bool allow_reserved_prefixes) |
323 | 0 | { |
324 | 0 | if (!is_valid_identifier(name)) |
325 | 0 | name = ensure_valid_identifier(name); |
326 | 0 | if (is_reserved_identifier(name, member, allow_reserved_prefixes)) |
327 | 0 | name = make_unreserved_identifier(name); |
328 | 0 | } |
329 | | |
330 | | void ParsedIR::fixup_reserved_names() |
331 | 0 | { |
332 | 0 | for (uint32_t id : meta_needing_name_fixup) |
333 | 0 | { |
334 | | // Don't rename remapped variables like 'gl_LastFragDepthARM'. |
335 | 0 | if (ids[id].get_type() == TypeVariable && get<SPIRVariable>(id).remapped_variable) |
336 | 0 | continue; |
337 | | |
338 | 0 | auto &m = meta[id]; |
339 | 0 | sanitize_identifier(m.decoration.alias, false, false); |
340 | 0 | for (auto &memb : m.members) |
341 | 0 | sanitize_identifier(memb.alias, true, false); |
342 | 0 | } |
343 | 0 | meta_needing_name_fixup.clear(); |
344 | 0 | } |
345 | | |
346 | | void ParsedIR::set_name(ID id, const string &name) |
347 | 10.1k | { |
348 | 10.1k | auto &m = meta[id]; |
349 | 10.1k | m.decoration.alias = name; |
350 | 10.1k | if (!is_valid_identifier(name) || is_reserved_identifier(name, false, false)) |
351 | 7.33k | meta_needing_name_fixup.insert(id); |
352 | 10.1k | } |
353 | | |
354 | | void ParsedIR::set_member_name(TypeID id, uint32_t index, const string &name) |
355 | 1.53k | { |
356 | 1.53k | auto &m = meta[id]; |
357 | 1.53k | m.members.resize(max(m.members.size(), size_t(index) + 1)); |
358 | 1.53k | m.members[index].alias = name; |
359 | 1.53k | if (!is_valid_identifier(name) || is_reserved_identifier(name, true, false)) |
360 | 435 | meta_needing_name_fixup.insert(id); |
361 | 1.53k | } |
362 | | |
363 | | void ParsedIR::set_decoration_string(ID id, Decoration decoration, const string &argument) |
364 | 144 | { |
365 | 144 | auto &dec = meta[id].decoration; |
366 | 144 | dec.decoration_flags.set(decoration); |
367 | | |
368 | 144 | switch (decoration) |
369 | 144 | { |
370 | 144 | case DecorationUserSemantic: |
371 | 144 | dec.user_semantic = argument; |
372 | 144 | break; |
373 | | |
374 | 0 | case DecorationUserTypeGOOGLE: |
375 | 0 | dec.user_type = argument; |
376 | 0 | break; |
377 | | |
378 | 0 | default: |
379 | 0 | break; |
380 | 144 | } |
381 | 144 | } |
382 | | |
383 | | void ParsedIR::set_decoration(ID id, Decoration decoration, uint32_t argument) |
384 | 85.6k | { |
385 | 85.6k | auto &dec = meta[id].decoration; |
386 | 85.6k | dec.decoration_flags.set(decoration); |
387 | | |
388 | 85.6k | switch (decoration) |
389 | 85.6k | { |
390 | 690 | case DecorationBuiltIn: |
391 | 690 | dec.builtin = true; |
392 | 690 | dec.builtin_type = static_cast<BuiltIn>(argument); |
393 | 690 | break; |
394 | | |
395 | 569 | case DecorationLocation: |
396 | 569 | dec.location = argument; |
397 | 569 | break; |
398 | | |
399 | 2.52k | case DecorationComponent: |
400 | 2.52k | dec.component = argument; |
401 | 2.52k | break; |
402 | | |
403 | 4 | case DecorationOffset: |
404 | 4 | dec.offset = argument; |
405 | 4 | break; |
406 | | |
407 | 131 | case DecorationXfbBuffer: |
408 | 131 | dec.xfb_buffer = argument; |
409 | 131 | break; |
410 | | |
411 | 0 | case DecorationXfbStride: |
412 | 0 | dec.xfb_stride = argument; |
413 | 0 | break; |
414 | | |
415 | 0 | case DecorationStream: |
416 | 0 | dec.stream = argument; |
417 | 0 | break; |
418 | | |
419 | 941 | case DecorationArrayStride: |
420 | 941 | dec.array_stride = argument; |
421 | 941 | break; |
422 | | |
423 | 18.8k | case DecorationMatrixStride: |
424 | 18.8k | dec.matrix_stride = argument; |
425 | 18.8k | break; |
426 | | |
427 | 1.41k | case DecorationBinding: |
428 | 1.41k | dec.binding = argument; |
429 | 1.41k | break; |
430 | | |
431 | 420 | case DecorationDescriptorSet: |
432 | 420 | dec.set = argument; |
433 | 420 | break; |
434 | | |
435 | 0 | case DecorationInputAttachmentIndex: |
436 | 0 | dec.input_attachment = argument; |
437 | 0 | break; |
438 | | |
439 | 15 | case DecorationSpecId: |
440 | 15 | dec.spec_id = argument; |
441 | 15 | break; |
442 | | |
443 | 1.05k | case DecorationIndex: |
444 | 1.05k | dec.index = argument; |
445 | 1.05k | break; |
446 | | |
447 | 0 | case DecorationHlslCounterBufferGOOGLE: |
448 | 0 | meta[id].hlsl_magic_counter_buffer = argument; |
449 | 0 | meta[argument].hlsl_is_magic_counter_buffer = true; |
450 | 0 | break; |
451 | | |
452 | 0 | case DecorationFPRoundingMode: |
453 | 0 | dec.fp_rounding_mode = static_cast<FPRoundingMode>(argument); |
454 | 0 | break; |
455 | | |
456 | 39 | case DecorationFPFastMathMode: |
457 | 39 | dec.fp_fast_math_mode = static_cast<FPFastMathModeMask>(argument); |
458 | 39 | break; |
459 | | |
460 | 59.0k | default: |
461 | 59.0k | break; |
462 | 85.6k | } |
463 | 85.6k | } |
464 | | |
465 | | void ParsedIR::set_member_decoration(TypeID id, uint32_t index, Decoration decoration, uint32_t argument) |
466 | 8.98k | { |
467 | 8.98k | auto &m = meta[id]; |
468 | 8.98k | m.members.resize(max(m.members.size(), size_t(index) + 1)); |
469 | 8.98k | auto &dec = m.members[index]; |
470 | 8.98k | dec.decoration_flags.set(decoration); |
471 | | |
472 | 8.98k | switch (decoration) |
473 | 8.98k | { |
474 | 2.81k | case DecorationBuiltIn: |
475 | 2.81k | dec.builtin = true; |
476 | 2.81k | dec.builtin_type = static_cast<BuiltIn>(argument); |
477 | 2.81k | break; |
478 | | |
479 | 0 | case DecorationLocation: |
480 | 0 | dec.location = argument; |
481 | 0 | break; |
482 | | |
483 | 18 | case DecorationComponent: |
484 | 18 | dec.component = argument; |
485 | 18 | break; |
486 | | |
487 | 61 | case DecorationBinding: |
488 | 61 | dec.binding = argument; |
489 | 61 | break; |
490 | | |
491 | 348 | case DecorationOffset: |
492 | 348 | dec.offset = argument; |
493 | 348 | break; |
494 | | |
495 | 223 | case DecorationXfbBuffer: |
496 | 223 | dec.xfb_buffer = argument; |
497 | 223 | break; |
498 | | |
499 | 0 | case DecorationXfbStride: |
500 | 0 | dec.xfb_stride = argument; |
501 | 0 | break; |
502 | | |
503 | 0 | case DecorationStream: |
504 | 0 | dec.stream = argument; |
505 | 0 | break; |
506 | | |
507 | 344 | case DecorationSpecId: |
508 | 344 | dec.spec_id = argument; |
509 | 344 | break; |
510 | | |
511 | 3 | case DecorationMatrixStride: |
512 | 3 | dec.matrix_stride = argument; |
513 | 3 | break; |
514 | | |
515 | 65 | case DecorationIndex: |
516 | 65 | dec.index = argument; |
517 | 65 | break; |
518 | | |
519 | 5.10k | default: |
520 | 5.10k | break; |
521 | 8.98k | } |
522 | 8.98k | } |
523 | | |
524 | | // Recursively marks any constants referenced by the specified constant instruction as being used |
525 | | // as an array length. The id must be a constant instruction (SPIRConstant or SPIRConstantOp). |
526 | | void ParsedIR::mark_used_as_array_length(ID id) |
527 | 3.50k | { |
528 | 3.50k | switch (ids[id].get_type()) |
529 | 3.50k | { |
530 | 186 | case TypeConstant: |
531 | 186 | { |
532 | 186 | auto &c = get<SPIRConstant>(id); |
533 | 186 | c.is_used_as_array_length = true; |
534 | | |
535 | | // Mark composite dependencies as well. |
536 | 186 | for (auto &sub_id: c.m.id) |
537 | 744 | if (sub_id) |
538 | 0 | mark_used_as_array_length(sub_id); |
539 | | |
540 | 372 | for (uint32_t col = 0; col < c.m.columns; col++) |
541 | 186 | { |
542 | 186 | for (auto &sub_id : c.m.c[col].id) |
543 | 744 | if (sub_id) |
544 | 0 | mark_used_as_array_length(sub_id); |
545 | 186 | } |
546 | | |
547 | 186 | for (auto &sub_id : c.subconstants) |
548 | 0 | if (sub_id) |
549 | 0 | mark_used_as_array_length(sub_id); |
550 | 186 | break; |
551 | 0 | } |
552 | | |
553 | 0 | case TypeConstantOp: |
554 | 0 | { |
555 | 0 | auto &cop = get<SPIRConstantOp>(id); |
556 | 0 | if (cop.opcode == OpCompositeExtract) |
557 | 0 | mark_used_as_array_length(cop.arguments[0]); |
558 | 0 | else if (cop.opcode == OpCompositeInsert) |
559 | 0 | { |
560 | 0 | mark_used_as_array_length(cop.arguments[0]); |
561 | 0 | mark_used_as_array_length(cop.arguments[1]); |
562 | 0 | } |
563 | 0 | else |
564 | 0 | for (uint32_t arg_id : cop.arguments) |
565 | 0 | mark_used_as_array_length(arg_id); |
566 | 0 | break; |
567 | 0 | } |
568 | | |
569 | 3.31k | case TypeUndef: |
570 | 3.31k | break; |
571 | | |
572 | 0 | default: |
573 | 0 | assert(0); |
574 | 3.50k | } |
575 | 3.50k | } |
576 | | |
577 | | Bitset ParsedIR::get_buffer_block_type_flags(const SPIRType &type) const |
578 | 0 | { |
579 | 0 | if (type.member_types.empty()) |
580 | 0 | return {}; |
581 | | |
582 | 0 | Bitset all_members_flags = get_member_decoration_bitset(type.self, 0); |
583 | 0 | for (uint32_t i = 1; i < uint32_t(type.member_types.size()); i++) |
584 | 0 | all_members_flags.merge_and(get_member_decoration_bitset(type.self, i)); |
585 | 0 | return all_members_flags; |
586 | 0 | } |
587 | | |
588 | | Bitset ParsedIR::get_buffer_block_flags(const SPIRVariable &var) const |
589 | 0 | { |
590 | 0 | auto &type = get<SPIRType>(var.basetype); |
591 | 0 | if (type.basetype != SPIRType::Struct) |
592 | 0 | SPIRV_CROSS_THROW("Cannot get buffer block flags for non-buffer variable."); |
593 | | |
594 | | // Some flags like non-writable, non-readable are actually found |
595 | | // as member decorations. If all members have a decoration set, propagate |
596 | | // the decoration up as a regular variable decoration. |
597 | 0 | Bitset base_flags; |
598 | 0 | auto *m = find_meta(var.self); |
599 | 0 | if (m) |
600 | 0 | base_flags = m->decoration.decoration_flags; |
601 | |
|
602 | 0 | if (type.member_types.empty()) |
603 | 0 | return base_flags; |
604 | | |
605 | 0 | auto all_members_flags = get_buffer_block_type_flags(type); |
606 | 0 | base_flags.merge_or(all_members_flags); |
607 | 0 | return base_flags; |
608 | 0 | } |
609 | | |
610 | | const Bitset &ParsedIR::get_member_decoration_bitset(TypeID id, uint32_t index) const |
611 | 0 | { |
612 | 0 | auto *m = find_meta(id); |
613 | 0 | if (m) |
614 | 0 | { |
615 | 0 | if (index >= m->members.size()) |
616 | 0 | return cleared_bitset; |
617 | 0 | return m->members[index].decoration_flags; |
618 | 0 | } |
619 | 0 | else |
620 | 0 | return cleared_bitset; |
621 | 0 | } |
622 | | |
623 | | bool ParsedIR::has_decoration(ID id, Decoration decoration) const |
624 | 0 | { |
625 | 0 | return get_decoration_bitset(id).get(decoration); |
626 | 0 | } |
627 | | |
628 | | uint32_t ParsedIR::get_decoration(ID id, Decoration decoration) const |
629 | 82.6k | { |
630 | 82.6k | auto *m = find_meta(id); |
631 | 82.6k | if (!m) |
632 | 0 | return 0; |
633 | | |
634 | 82.6k | auto &dec = m->decoration; |
635 | 82.6k | if (!dec.decoration_flags.get(decoration)) |
636 | 0 | return 0; |
637 | | |
638 | 82.6k | switch (decoration) |
639 | 82.6k | { |
640 | 2.81k | case DecorationBuiltIn: |
641 | 2.81k | return dec.builtin_type; |
642 | 0 | case DecorationLocation: |
643 | 0 | return dec.location; |
644 | 18 | case DecorationComponent: |
645 | 18 | return dec.component; |
646 | 0 | case DecorationOffset: |
647 | 0 | return dec.offset; |
648 | 351 | case DecorationXfbBuffer: |
649 | 351 | return dec.xfb_buffer; |
650 | 0 | case DecorationXfbStride: |
651 | 0 | return dec.xfb_stride; |
652 | 0 | case DecorationStream: |
653 | 0 | return dec.stream; |
654 | 61 | case DecorationBinding: |
655 | 61 | return dec.binding; |
656 | 464 | case DecorationDescriptorSet: |
657 | 464 | return dec.set; |
658 | 0 | case DecorationInputAttachmentIndex: |
659 | 0 | return dec.input_attachment; |
660 | 344 | case DecorationSpecId: |
661 | 344 | return dec.spec_id; |
662 | 985 | case DecorationArrayStride: |
663 | 985 | return dec.array_stride; |
664 | 18.4k | case DecorationMatrixStride: |
665 | 18.4k | return dec.matrix_stride; |
666 | 1.11k | case DecorationIndex: |
667 | 1.11k | return dec.index; |
668 | 0 | case DecorationFPRoundingMode: |
669 | 0 | return dec.fp_rounding_mode; |
670 | 78 | case DecorationFPFastMathMode: |
671 | 78 | return dec.fp_fast_math_mode; |
672 | 58.0k | default: |
673 | 58.0k | return 1; |
674 | 82.6k | } |
675 | 82.6k | } |
676 | | |
677 | | const string &ParsedIR::get_decoration_string(ID id, Decoration decoration) const |
678 | 294 | { |
679 | 294 | auto *m = find_meta(id); |
680 | 294 | if (!m) |
681 | 0 | return empty_string; |
682 | | |
683 | 294 | auto &dec = m->decoration; |
684 | | |
685 | 294 | if (!dec.decoration_flags.get(decoration)) |
686 | 0 | return empty_string; |
687 | | |
688 | 294 | switch (decoration) |
689 | 294 | { |
690 | 294 | case DecorationUserSemantic: |
691 | 294 | return dec.user_semantic; |
692 | | |
693 | 0 | case DecorationUserTypeGOOGLE: |
694 | 0 | return dec.user_type; |
695 | | |
696 | 0 | default: |
697 | 0 | return empty_string; |
698 | 294 | } |
699 | 294 | } |
700 | | |
701 | | void ParsedIR::unset_decoration(ID id, Decoration decoration) |
702 | 0 | { |
703 | 0 | auto &dec = meta[id].decoration; |
704 | 0 | dec.decoration_flags.clear(decoration); |
705 | 0 | switch (decoration) |
706 | 0 | { |
707 | 0 | case DecorationBuiltIn: |
708 | 0 | dec.builtin = false; |
709 | 0 | break; |
710 | | |
711 | 0 | case DecorationLocation: |
712 | 0 | dec.location = 0; |
713 | 0 | break; |
714 | | |
715 | 0 | case DecorationComponent: |
716 | 0 | dec.component = 0; |
717 | 0 | break; |
718 | | |
719 | 0 | case DecorationOffset: |
720 | 0 | dec.offset = 0; |
721 | 0 | break; |
722 | | |
723 | 0 | case DecorationXfbBuffer: |
724 | 0 | dec.xfb_buffer = 0; |
725 | 0 | break; |
726 | | |
727 | 0 | case DecorationXfbStride: |
728 | 0 | dec.xfb_stride = 0; |
729 | 0 | break; |
730 | | |
731 | 0 | case DecorationStream: |
732 | 0 | dec.stream = 0; |
733 | 0 | break; |
734 | | |
735 | 0 | case DecorationBinding: |
736 | 0 | dec.binding = 0; |
737 | 0 | break; |
738 | | |
739 | 0 | case DecorationDescriptorSet: |
740 | 0 | dec.set = 0; |
741 | 0 | break; |
742 | | |
743 | 0 | case DecorationInputAttachmentIndex: |
744 | 0 | dec.input_attachment = 0; |
745 | 0 | break; |
746 | | |
747 | 0 | case DecorationSpecId: |
748 | 0 | dec.spec_id = 0; |
749 | 0 | break; |
750 | | |
751 | 0 | case DecorationUserSemantic: |
752 | 0 | dec.user_semantic.clear(); |
753 | 0 | break; |
754 | | |
755 | 0 | case DecorationFPRoundingMode: |
756 | 0 | dec.fp_rounding_mode = FPRoundingModeMax; |
757 | 0 | break; |
758 | | |
759 | 0 | case DecorationFPFastMathMode: |
760 | 0 | dec.fp_fast_math_mode = FPFastMathModeMaskNone; |
761 | 0 | break; |
762 | | |
763 | 0 | case DecorationHlslCounterBufferGOOGLE: |
764 | 0 | { |
765 | 0 | auto &counter = meta[id].hlsl_magic_counter_buffer; |
766 | 0 | if (counter) |
767 | 0 | { |
768 | 0 | meta[counter].hlsl_is_magic_counter_buffer = false; |
769 | 0 | counter = 0; |
770 | 0 | } |
771 | 0 | break; |
772 | 0 | } |
773 | | |
774 | 0 | default: |
775 | 0 | break; |
776 | 0 | } |
777 | 0 | } |
778 | | |
779 | | bool ParsedIR::has_member_decoration(TypeID id, uint32_t index, Decoration decoration) const |
780 | 0 | { |
781 | 0 | return get_member_decoration_bitset(id, index).get(decoration); |
782 | 0 | } |
783 | | |
784 | | uint32_t ParsedIR::get_member_decoration(TypeID id, uint32_t index, Decoration decoration) const |
785 | 0 | { |
786 | 0 | auto *m = find_meta(id); |
787 | 0 | if (!m) |
788 | 0 | return 0; |
789 | | |
790 | 0 | if (index >= m->members.size()) |
791 | 0 | return 0; |
792 | | |
793 | 0 | auto &dec = m->members[index]; |
794 | 0 | if (!dec.decoration_flags.get(decoration)) |
795 | 0 | return 0; |
796 | | |
797 | 0 | switch (decoration) |
798 | 0 | { |
799 | 0 | case DecorationBuiltIn: |
800 | 0 | return dec.builtin_type; |
801 | 0 | case DecorationLocation: |
802 | 0 | return dec.location; |
803 | 0 | case DecorationComponent: |
804 | 0 | return dec.component; |
805 | 0 | case DecorationBinding: |
806 | 0 | return dec.binding; |
807 | 0 | case DecorationOffset: |
808 | 0 | return dec.offset; |
809 | 0 | case DecorationXfbBuffer: |
810 | 0 | return dec.xfb_buffer; |
811 | 0 | case DecorationXfbStride: |
812 | 0 | return dec.xfb_stride; |
813 | 0 | case DecorationStream: |
814 | 0 | return dec.stream; |
815 | 0 | case DecorationSpecId: |
816 | 0 | return dec.spec_id; |
817 | 0 | case DecorationMatrixStride: |
818 | 0 | return dec.matrix_stride; |
819 | 0 | case DecorationIndex: |
820 | 0 | return dec.index; |
821 | 0 | default: |
822 | 0 | return 1; |
823 | 0 | } |
824 | 0 | } |
825 | | |
826 | | const Bitset &ParsedIR::get_decoration_bitset(ID id) const |
827 | 0 | { |
828 | 0 | auto *m = find_meta(id); |
829 | 0 | if (m) |
830 | 0 | { |
831 | 0 | auto &dec = m->decoration; |
832 | 0 | return dec.decoration_flags; |
833 | 0 | } |
834 | 0 | else |
835 | 0 | return cleared_bitset; |
836 | 0 | } |
837 | | |
838 | | void ParsedIR::set_member_decoration_string(TypeID id, uint32_t index, Decoration decoration, const string &argument) |
839 | 301 | { |
840 | 301 | auto &m = meta[id]; |
841 | 301 | m.members.resize(max(m.members.size(), size_t(index) + 1)); |
842 | 301 | auto &dec = meta[id].members[index]; |
843 | 301 | dec.decoration_flags.set(decoration); |
844 | | |
845 | 301 | switch (decoration) |
846 | 301 | { |
847 | 150 | case DecorationUserSemantic: |
848 | 150 | dec.user_semantic = argument; |
849 | 150 | break; |
850 | | |
851 | 151 | default: |
852 | 151 | break; |
853 | 301 | } |
854 | 301 | } |
855 | | |
856 | | const string &ParsedIR::get_member_decoration_string(TypeID id, uint32_t index, Decoration decoration) const |
857 | 0 | { |
858 | 0 | auto *m = find_meta(id); |
859 | 0 | if (m) |
860 | 0 | { |
861 | 0 | if (!has_member_decoration(id, index, decoration)) |
862 | 0 | return empty_string; |
863 | | |
864 | 0 | auto &dec = m->members[index]; |
865 | |
|
866 | 0 | switch (decoration) |
867 | 0 | { |
868 | 0 | case DecorationUserSemantic: |
869 | 0 | return dec.user_semantic; |
870 | | |
871 | 0 | default: |
872 | 0 | return empty_string; |
873 | 0 | } |
874 | 0 | } |
875 | 0 | else |
876 | 0 | return empty_string; |
877 | 0 | } |
878 | | |
879 | | void ParsedIR::unset_member_decoration(TypeID id, uint32_t index, Decoration decoration) |
880 | 0 | { |
881 | 0 | auto &m = meta[id]; |
882 | 0 | if (index >= m.members.size()) |
883 | 0 | return; |
884 | | |
885 | 0 | auto &dec = m.members[index]; |
886 | |
|
887 | 0 | dec.decoration_flags.clear(decoration); |
888 | 0 | switch (decoration) |
889 | 0 | { |
890 | 0 | case DecorationBuiltIn: |
891 | 0 | dec.builtin = false; |
892 | 0 | break; |
893 | | |
894 | 0 | case DecorationLocation: |
895 | 0 | dec.location = 0; |
896 | 0 | break; |
897 | | |
898 | 0 | case DecorationComponent: |
899 | 0 | dec.component = 0; |
900 | 0 | break; |
901 | | |
902 | 0 | case DecorationOffset: |
903 | 0 | dec.offset = 0; |
904 | 0 | break; |
905 | | |
906 | 0 | case DecorationXfbBuffer: |
907 | 0 | dec.xfb_buffer = 0; |
908 | 0 | break; |
909 | | |
910 | 0 | case DecorationXfbStride: |
911 | 0 | dec.xfb_stride = 0; |
912 | 0 | break; |
913 | | |
914 | 0 | case DecorationStream: |
915 | 0 | dec.stream = 0; |
916 | 0 | break; |
917 | | |
918 | 0 | case DecorationSpecId: |
919 | 0 | dec.spec_id = 0; |
920 | 0 | break; |
921 | | |
922 | 0 | case DecorationUserSemantic: |
923 | 0 | dec.user_semantic.clear(); |
924 | 0 | break; |
925 | | |
926 | 0 | default: |
927 | 0 | break; |
928 | 0 | } |
929 | 0 | } |
930 | | |
931 | | uint32_t ParsedIR::increase_bound_by(uint32_t incr_amount) |
932 | 59.1k | { |
933 | 59.1k | auto curr_bound = ids.size(); |
934 | 59.1k | auto new_bound = curr_bound + incr_amount; |
935 | | |
936 | 59.1k | ids.reserve(ids.size() + incr_amount); |
937 | 119k | for (uint32_t i = 0; i < incr_amount; i++) |
938 | 60.4k | ids.emplace_back(pool_group.get()); |
939 | | |
940 | 59.1k | block_meta.resize(new_bound); |
941 | 59.1k | return uint32_t(curr_bound); |
942 | 59.1k | } |
943 | | |
944 | | void ParsedIR::remove_typed_id(Types type, ID id) |
945 | 9 | { |
946 | 9 | auto &type_ids = ids_for_type[type]; |
947 | 9 | type_ids.erase(remove(begin(type_ids), end(type_ids), id), end(type_ids)); |
948 | 9 | } |
949 | | |
950 | | void ParsedIR::reset_all_of_type(Types type) |
951 | 0 | { |
952 | 0 | for (auto &id : ids_for_type[type]) |
953 | 0 | if (ids[id].get_type() == type) |
954 | 0 | ids[id].reset(); |
955 | |
|
956 | 0 | ids_for_type[type].clear(); |
957 | 0 | } |
958 | | |
959 | | void ParsedIR::add_typed_id(Types type, ID id) |
960 | 165k | { |
961 | 165k | assert(id < ids.size()); |
962 | | |
963 | 165k | if (loop_iteration_depth_hard != 0) |
964 | 0 | SPIRV_CROSS_THROW("Cannot add typed ID while looping over it."); |
965 | | |
966 | 165k | if (loop_iteration_depth_soft != 0) |
967 | 0 | { |
968 | 0 | if (!ids[id].empty()) |
969 | 0 | SPIRV_CROSS_THROW("Cannot override IDs when loop is soft locked."); |
970 | 0 | return; |
971 | 0 | } |
972 | | |
973 | 165k | if (ids[id].empty() || ids[id].get_type() != type) |
974 | 76.3k | { |
975 | 76.3k | switch (type) |
976 | 76.3k | { |
977 | 64.1k | case TypeConstant: |
978 | 64.1k | ids_for_constant_or_variable.push_back(id); |
979 | 64.1k | ids_for_constant_undef_or_type.push_back(id); |
980 | 64.1k | break; |
981 | | |
982 | 2.65k | case TypeVariable: |
983 | 2.65k | ids_for_constant_or_variable.push_back(id); |
984 | 2.65k | break; |
985 | | |
986 | 3.63k | case TypeType: |
987 | 3.64k | case TypeConstantOp: |
988 | 3.69k | case TypeUndef: |
989 | 3.69k | ids_for_constant_undef_or_type.push_back(id); |
990 | 3.69k | break; |
991 | | |
992 | 5.87k | default: |
993 | 5.87k | break; |
994 | 76.3k | } |
995 | 76.3k | } |
996 | | |
997 | 165k | if (ids[id].empty()) |
998 | 76.3k | { |
999 | 76.3k | ids_for_type[type].push_back(id); |
1000 | 76.3k | } |
1001 | 89.3k | else if (ids[id].get_type() != type) |
1002 | 9 | { |
1003 | 9 | remove_typed_id(ids[id].get_type(), id); |
1004 | 9 | ids_for_type[type].push_back(id); |
1005 | 9 | } |
1006 | 165k | } |
1007 | | |
1008 | | const Meta *ParsedIR::find_meta(ID id) const |
1009 | 93.4k | { |
1010 | 93.4k | auto itr = meta.find(id); |
1011 | 93.4k | if (itr != end(meta)) |
1012 | 89.3k | return &itr->second; |
1013 | 4.09k | else |
1014 | 4.09k | return nullptr; |
1015 | 93.4k | } |
1016 | | |
1017 | | Meta *ParsedIR::find_meta(ID id) |
1018 | 0 | { |
1019 | 0 | auto itr = meta.find(id); |
1020 | 0 | if (itr != end(meta)) |
1021 | 0 | return &itr->second; |
1022 | 0 | else |
1023 | 0 | return nullptr; |
1024 | 0 | } |
1025 | | |
1026 | | ParsedIR::LoopLock ParsedIR::create_loop_hard_lock() const |
1027 | 0 | { |
1028 | 0 | return ParsedIR::LoopLock(&loop_iteration_depth_hard); |
1029 | 0 | } |
1030 | | |
1031 | | ParsedIR::LoopLock ParsedIR::create_loop_soft_lock() const |
1032 | 0 | { |
1033 | 0 | return ParsedIR::LoopLock(&loop_iteration_depth_soft); |
1034 | 0 | } |
1035 | | |
1036 | | ParsedIR::LoopLock::~LoopLock() |
1037 | 0 | { |
1038 | 0 | if (lock) |
1039 | 0 | (*lock)--; |
1040 | 0 | } |
1041 | | |
1042 | | ParsedIR::LoopLock::LoopLock(uint32_t *lock_) |
1043 | 0 | : lock(lock_) |
1044 | 0 | { |
1045 | 0 | if (lock) |
1046 | 0 | (*lock)++; |
1047 | 0 | } |
1048 | | |
1049 | | ParsedIR::LoopLock::LoopLock(LoopLock &&other) SPIRV_CROSS_NOEXCEPT |
1050 | 0 | { |
1051 | 0 | *this = std::move(other); |
1052 | 0 | } |
1053 | | |
1054 | | ParsedIR::LoopLock &ParsedIR::LoopLock::operator=(LoopLock &&other) SPIRV_CROSS_NOEXCEPT |
1055 | 0 | { |
1056 | 0 | if (lock) |
1057 | 0 | (*lock)--; |
1058 | 0 | lock = other.lock; |
1059 | 0 | other.lock = nullptr; |
1060 | 0 | return *this; |
1061 | 0 | } |
1062 | | |
1063 | | void ParsedIR::make_constant_null(uint32_t id, uint32_t type, bool add_to_typed_id_set) |
1064 | 119k | { |
1065 | 119k | assert(id < ids.size()); |
1066 | | |
1067 | 119k | auto &constant_type = get<SPIRType>(type); |
1068 | | |
1069 | 119k | if (constant_type.pointer) |
1070 | 13 | { |
1071 | 13 | if (add_to_typed_id_set) |
1072 | 13 | add_typed_id(TypeConstant, id); |
1073 | 13 | auto &constant = variant_set<SPIRConstant>(ids[id], type); |
1074 | 13 | constant.self = id; |
1075 | 13 | constant.make_null(constant_type); |
1076 | 13 | } |
1077 | 119k | else if (!constant_type.array.empty()) |
1078 | 57.9k | { |
1079 | 57.9k | assert(constant_type.parent_type); |
1080 | 57.9k | uint32_t parent_id = increase_bound_by(1); |
1081 | 57.9k | make_constant_null(parent_id, constant_type.parent_type, add_to_typed_id_set); |
1082 | | |
1083 | | // The array size of OpConstantNull can be either literal or specialization constant. |
1084 | | // In the latter case, we cannot take the value as-is, as it can be changed to anything. |
1085 | | // Rather, we assume it to be *one* for the sake of initializer. |
1086 | 57.9k | bool is_literal_array_size = constant_type.array_size_literal.back(); |
1087 | 57.9k | uint32_t count = is_literal_array_size ? constant_type.array.back() : 1; |
1088 | | |
1089 | 57.9k | SmallVector<uint32_t> elements(count); |
1090 | 144M | for (uint32_t i = 0; i < count; i++) |
1091 | 144M | elements[i] = parent_id; |
1092 | | |
1093 | 57.9k | if (add_to_typed_id_set) |
1094 | 57.9k | add_typed_id(TypeConstant, id); |
1095 | 57.9k | auto& constant = variant_set<SPIRConstant>(ids[id], type, elements.data(), uint32_t(elements.size()), false); |
1096 | 57.9k | constant.self = id; |
1097 | 57.9k | constant.is_null_array_specialized_length = !is_literal_array_size; |
1098 | 57.9k | } |
1099 | 61.4k | else if (!constant_type.member_types.empty()) |
1100 | 1.03k | { |
1101 | 1.03k | uint32_t member_ids = increase_bound_by(uint32_t(constant_type.member_types.size())); |
1102 | 1.03k | SmallVector<uint32_t> elements(constant_type.member_types.size()); |
1103 | 3.33k | for (uint32_t i = 0; i < constant_type.member_types.size(); i++) |
1104 | 2.30k | { |
1105 | 2.30k | make_constant_null(member_ids + i, constant_type.member_types[i], add_to_typed_id_set); |
1106 | 2.30k | elements[i] = member_ids + i; |
1107 | 2.30k | } |
1108 | | |
1109 | 1.03k | if (add_to_typed_id_set) |
1110 | 1.03k | add_typed_id(TypeConstant, id); |
1111 | 1.03k | variant_set<SPIRConstant>(ids[id], type, elements.data(), uint32_t(elements.size()), false).self = id; |
1112 | 1.03k | } |
1113 | 60.3k | else |
1114 | 60.3k | { |
1115 | 60.3k | if (add_to_typed_id_set) |
1116 | 60.3k | add_typed_id(TypeConstant, id); |
1117 | 60.3k | auto &constant = variant_set<SPIRConstant>(ids[id], type); |
1118 | 60.3k | constant.self = id; |
1119 | 60.3k | constant.make_null(constant_type); |
1120 | 60.3k | } |
1121 | 119k | } |
1122 | | |
1123 | | } // namespace SPIRV_CROSS_NAMESPACE |