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/inspector/inspector.h"
16
17 #include <limits>
18 #include <utility>
19
20 #include "src/ast/bool_literal_expression.h"
21 #include "src/ast/call_expression.h"
22 #include "src/ast/float_literal_expression.h"
23 #include "src/ast/interpolate_decoration.h"
24 #include "src/ast/location_decoration.h"
25 #include "src/ast/module.h"
26 #include "src/ast/override_decoration.h"
27 #include "src/ast/sint_literal_expression.h"
28 #include "src/ast/uint_literal_expression.h"
29 #include "src/sem/array.h"
30 #include "src/sem/call.h"
31 #include "src/sem/depth_multisampled_texture_type.h"
32 #include "src/sem/f32_type.h"
33 #include "src/sem/function.h"
34 #include "src/sem/i32_type.h"
35 #include "src/sem/matrix_type.h"
36 #include "src/sem/multisampled_texture_type.h"
37 #include "src/sem/sampled_texture_type.h"
38 #include "src/sem/statement.h"
39 #include "src/sem/storage_texture_type.h"
40 #include "src/sem/struct.h"
41 #include "src/sem/u32_type.h"
42 #include "src/sem/variable.h"
43 #include "src/sem/vector_type.h"
44 #include "src/sem/void_type.h"
45 #include "src/utils/math.h"
46 #include "src/utils/unique_vector.h"
47
48 namespace tint {
49 namespace inspector {
50
51 namespace {
52
AppendResourceBindings(std::vector<ResourceBinding> * dest,const std::vector<ResourceBinding> & orig)53 void AppendResourceBindings(std::vector<ResourceBinding>* dest,
54 const std::vector<ResourceBinding>& orig) {
55 TINT_ASSERT(Inspector, dest);
56 if (!dest) {
57 return;
58 }
59
60 dest->reserve(dest->size() + orig.size());
61 dest->insert(dest->end(), orig.begin(), orig.end());
62 }
63
CalculateComponentAndComposition(const sem::Type * type)64 std::tuple<ComponentType, CompositionType> CalculateComponentAndComposition(
65 const sem::Type* type) {
66 if (type->is_float_scalar()) {
67 return {ComponentType::kFloat, CompositionType::kScalar};
68 } else if (type->is_float_vector()) {
69 auto* vec = type->As<sem::Vector>();
70 if (vec->Width() == 2) {
71 return {ComponentType::kFloat, CompositionType::kVec2};
72 } else if (vec->Width() == 3) {
73 return {ComponentType::kFloat, CompositionType::kVec3};
74 } else if (vec->Width() == 4) {
75 return {ComponentType::kFloat, CompositionType::kVec4};
76 }
77 } else if (type->is_unsigned_integer_scalar()) {
78 return {ComponentType::kUInt, CompositionType::kScalar};
79 } else if (type->is_unsigned_integer_vector()) {
80 auto* vec = type->As<sem::Vector>();
81 if (vec->Width() == 2) {
82 return {ComponentType::kUInt, CompositionType::kVec2};
83 } else if (vec->Width() == 3) {
84 return {ComponentType::kUInt, CompositionType::kVec3};
85 } else if (vec->Width() == 4) {
86 return {ComponentType::kUInt, CompositionType::kVec4};
87 }
88 } else if (type->is_signed_integer_scalar()) {
89 return {ComponentType::kSInt, CompositionType::kScalar};
90 } else if (type->is_signed_integer_vector()) {
91 auto* vec = type->As<sem::Vector>();
92 if (vec->Width() == 2) {
93 return {ComponentType::kSInt, CompositionType::kVec2};
94 } else if (vec->Width() == 3) {
95 return {ComponentType::kSInt, CompositionType::kVec3};
96 } else if (vec->Width() == 4) {
97 return {ComponentType::kSInt, CompositionType::kVec4};
98 }
99 }
100 return {ComponentType::kUnknown, CompositionType::kUnknown};
101 }
102
CalculateInterpolationData(const sem::Type * type,const ast::DecorationList & decorations)103 std::tuple<InterpolationType, InterpolationSampling> CalculateInterpolationData(
104 const sem::Type* type,
105 const ast::DecorationList& decorations) {
106 auto* interpolation_decoration =
107 ast::GetDecoration<ast::InterpolateDecoration>(decorations);
108 if (type->is_integer_scalar_or_vector()) {
109 return {InterpolationType::kFlat, InterpolationSampling::kNone};
110 }
111
112 if (!interpolation_decoration) {
113 return {InterpolationType::kPerspective, InterpolationSampling::kCenter};
114 }
115
116 auto interpolation_type = interpolation_decoration->type;
117 auto sampling = interpolation_decoration->sampling;
118 if (interpolation_type != ast::InterpolationType::kFlat &&
119 sampling == ast::InterpolationSampling::kNone) {
120 sampling = ast::InterpolationSampling::kCenter;
121 }
122 return {ASTToInspectorInterpolationType(interpolation_type),
123 ASTToInspectorInterpolationSampling(sampling)};
124 }
125
126 } // namespace
127
Inspector(const Program * program)128 Inspector::Inspector(const Program* program) : program_(program) {}
129
130 Inspector::~Inspector() = default;
131
GetEntryPoints()132 std::vector<EntryPoint> Inspector::GetEntryPoints() {
133 std::vector<EntryPoint> result;
134
135 for (auto* func : program_->AST().Functions()) {
136 if (!func->IsEntryPoint()) {
137 continue;
138 }
139
140 auto* sem = program_->Sem().Get(func);
141
142 EntryPoint entry_point;
143 entry_point.name = program_->Symbols().NameFor(func->symbol);
144 entry_point.remapped_name = program_->Symbols().NameFor(func->symbol);
145 entry_point.stage = func->PipelineStage();
146
147 auto wgsize = sem->WorkgroupSize();
148 entry_point.workgroup_size_x = wgsize[0].value;
149 entry_point.workgroup_size_y = wgsize[1].value;
150 entry_point.workgroup_size_z = wgsize[2].value;
151 if (wgsize[0].overridable_const || wgsize[1].overridable_const ||
152 wgsize[2].overridable_const) {
153 // TODO(crbug.com/tint/713): Handle overridable constants.
154 TINT_ASSERT(Inspector, false);
155 }
156
157 for (auto* param : sem->Parameters()) {
158 AddEntryPointInOutVariables(
159 program_->Symbols().NameFor(param->Declaration()->symbol),
160 param->Type(), param->Declaration()->decorations,
161 entry_point.input_variables);
162
163 entry_point.input_position_used |=
164 ContainsBuiltin(ast::Builtin::kPosition, param->Type(),
165 param->Declaration()->decorations);
166 entry_point.front_facing_used |=
167 ContainsBuiltin(ast::Builtin::kFrontFacing, param->Type(),
168 param->Declaration()->decorations);
169 entry_point.sample_index_used |=
170 ContainsBuiltin(ast::Builtin::kSampleIndex, param->Type(),
171 param->Declaration()->decorations);
172 entry_point.input_sample_mask_used |=
173 ContainsBuiltin(ast::Builtin::kSampleMask, param->Type(),
174 param->Declaration()->decorations);
175 entry_point.num_workgroups_used |=
176 ContainsBuiltin(ast::Builtin::kNumWorkgroups, param->Type(),
177 param->Declaration()->decorations);
178 }
179
180 if (!sem->ReturnType()->Is<sem::Void>()) {
181 AddEntryPointInOutVariables("<retval>", sem->ReturnType(),
182 func->return_type_decorations,
183 entry_point.output_variables);
184
185 entry_point.output_sample_mask_used =
186 ContainsBuiltin(ast::Builtin::kSampleMask, sem->ReturnType(),
187 func->return_type_decorations);
188 }
189
190 for (auto* var : sem->TransitivelyReferencedGlobals()) {
191 auto* decl = var->Declaration();
192
193 auto name = program_->Symbols().NameFor(decl->symbol);
194
195 auto* global = var->As<sem::GlobalVariable>();
196 if (global && global->IsOverridable()) {
197 OverridableConstant overridable_constant;
198 overridable_constant.name = name;
199 overridable_constant.numeric_id = global->ConstantId();
200 auto* type = var->Type();
201 TINT_ASSERT(Inspector, type->is_scalar());
202 if (type->is_bool_scalar_or_vector()) {
203 overridable_constant.type = OverridableConstant::Type::kBool;
204 } else if (type->is_float_scalar()) {
205 overridable_constant.type = OverridableConstant::Type::kFloat32;
206 } else if (type->is_signed_integer_scalar()) {
207 overridable_constant.type = OverridableConstant::Type::kInt32;
208 } else if (type->is_unsigned_integer_scalar()) {
209 overridable_constant.type = OverridableConstant::Type::kUint32;
210 } else {
211 TINT_UNREACHABLE(Inspector, diagnostics_);
212 }
213
214 overridable_constant.is_initialized =
215 global->Declaration()->constructor;
216 auto* override_deco = ast::GetDecoration<ast::OverrideDecoration>(
217 global->Declaration()->decorations);
218 overridable_constant.is_numeric_id_specified =
219 override_deco ? override_deco->has_value : false;
220
221 entry_point.overridable_constants.push_back(overridable_constant);
222 }
223 }
224
225 result.push_back(std::move(entry_point));
226 }
227
228 return result;
229 }
230
GetRemappedNameForEntryPoint(const std::string & entry_point)231 std::string Inspector::GetRemappedNameForEntryPoint(
232 const std::string& entry_point) {
233 // TODO(rharrison): Reenable once all of the backends are using the renamed
234 // entry points.
235
236 // auto* func = FindEntryPointByName(entry_point);
237 // if (!func) {
238 // return {};
239 // }
240 // return func->name();
241 return entry_point;
242 }
243
GetConstantIDs()244 std::map<uint32_t, Scalar> Inspector::GetConstantIDs() {
245 std::map<uint32_t, Scalar> result;
246 for (auto* var : program_->AST().GlobalVariables()) {
247 auto* global = program_->Sem().Get<sem::GlobalVariable>(var);
248 if (!global || !global->IsOverridable()) {
249 continue;
250 }
251
252 // If there are conflicting defintions for a constant id, that is invalid
253 // WGSL, so the resolver should catch it. Thus here the inspector just
254 // assumes all definitions of the constant id are the same, so only needs
255 // to find the first reference to constant id.
256 uint32_t constant_id = global->ConstantId();
257 if (result.find(constant_id) != result.end()) {
258 continue;
259 }
260
261 if (!var->constructor) {
262 result[constant_id] = Scalar();
263 continue;
264 }
265
266 auto* literal = var->constructor->As<ast::LiteralExpression>();
267 if (!literal) {
268 // This is invalid WGSL, but handling gracefully.
269 result[constant_id] = Scalar();
270 continue;
271 }
272
273 if (auto* l = literal->As<ast::BoolLiteralExpression>()) {
274 result[constant_id] = Scalar(l->value);
275 continue;
276 }
277
278 if (auto* l = literal->As<ast::UintLiteralExpression>()) {
279 result[constant_id] = Scalar(l->value);
280 continue;
281 }
282
283 if (auto* l = literal->As<ast::SintLiteralExpression>()) {
284 result[constant_id] = Scalar(l->value);
285 continue;
286 }
287
288 if (auto* l = literal->As<ast::FloatLiteralExpression>()) {
289 result[constant_id] = Scalar(l->value);
290 continue;
291 }
292
293 result[constant_id] = Scalar();
294 }
295
296 return result;
297 }
298
GetConstantNameToIdMap()299 std::map<std::string, uint32_t> Inspector::GetConstantNameToIdMap() {
300 std::map<std::string, uint32_t> result;
301 for (auto* var : program_->AST().GlobalVariables()) {
302 auto* global = program_->Sem().Get<sem::GlobalVariable>(var);
303 if (global && global->IsOverridable()) {
304 auto name = program_->Symbols().NameFor(var->symbol);
305 result[name] = global->ConstantId();
306 }
307 }
308 return result;
309 }
310
GetStorageSize(const std::string & entry_point)311 uint32_t Inspector::GetStorageSize(const std::string& entry_point) {
312 auto* func = FindEntryPointByName(entry_point);
313 if (!func) {
314 return 0;
315 }
316
317 size_t size = 0;
318 auto* func_sem = program_->Sem().Get(func);
319 for (auto& ruv : func_sem->TransitivelyReferencedUniformVariables()) {
320 const sem::Struct* s = ruv.first->Type()->UnwrapRef()->As<sem::Struct>();
321 if (s && s->IsBlockDecorated()) {
322 size += s->Size();
323 }
324 }
325 for (auto& rsv : func_sem->TransitivelyReferencedStorageBufferVariables()) {
326 const sem::Struct* s = rsv.first->Type()->UnwrapRef()->As<sem::Struct>();
327 if (s) {
328 size += s->Size();
329 }
330 }
331
332 if (static_cast<uint64_t>(size) >
333 static_cast<uint64_t>(std::numeric_limits<uint32_t>::max())) {
334 return std::numeric_limits<uint32_t>::max();
335 }
336 return static_cast<uint32_t>(size);
337 }
338
GetResourceBindings(const std::string & entry_point)339 std::vector<ResourceBinding> Inspector::GetResourceBindings(
340 const std::string& entry_point) {
341 auto* func = FindEntryPointByName(entry_point);
342 if (!func) {
343 return {};
344 }
345
346 std::vector<ResourceBinding> result;
347 for (auto fn : {
348 &Inspector::GetUniformBufferResourceBindings,
349 &Inspector::GetStorageBufferResourceBindings,
350 &Inspector::GetReadOnlyStorageBufferResourceBindings,
351 &Inspector::GetSamplerResourceBindings,
352 &Inspector::GetComparisonSamplerResourceBindings,
353 &Inspector::GetSampledTextureResourceBindings,
354 &Inspector::GetMultisampledTextureResourceBindings,
355 &Inspector::GetWriteOnlyStorageTextureResourceBindings,
356 &Inspector::GetDepthTextureResourceBindings,
357 &Inspector::GetDepthMultisampledTextureResourceBindings,
358 &Inspector::GetExternalTextureResourceBindings,
359 }) {
360 AppendResourceBindings(&result, (this->*fn)(entry_point));
361 }
362 return result;
363 }
364
GetUniformBufferResourceBindings(const std::string & entry_point)365 std::vector<ResourceBinding> Inspector::GetUniformBufferResourceBindings(
366 const std::string& entry_point) {
367 auto* func = FindEntryPointByName(entry_point);
368 if (!func) {
369 return {};
370 }
371
372 std::vector<ResourceBinding> result;
373
374 auto* func_sem = program_->Sem().Get(func);
375 for (auto& ruv : func_sem->TransitivelyReferencedUniformVariables()) {
376 auto* var = ruv.first;
377 auto binding_info = ruv.second;
378
379 auto* unwrapped_type = var->Type()->UnwrapRef();
380 auto* str = unwrapped_type->As<sem::Struct>();
381 if (str == nullptr) {
382 continue;
383 }
384
385 if (!str->IsBlockDecorated()) {
386 continue;
387 }
388
389 ResourceBinding entry;
390 entry.resource_type = ResourceBinding::ResourceType::kUniformBuffer;
391 entry.bind_group = binding_info.group->value;
392 entry.binding = binding_info.binding->value;
393 entry.size = str->Size();
394 entry.size_no_padding = str->SizeNoPadding();
395
396 result.push_back(entry);
397 }
398
399 return result;
400 }
401
GetStorageBufferResourceBindings(const std::string & entry_point)402 std::vector<ResourceBinding> Inspector::GetStorageBufferResourceBindings(
403 const std::string& entry_point) {
404 return GetStorageBufferResourceBindingsImpl(entry_point, false);
405 }
406
407 std::vector<ResourceBinding>
GetReadOnlyStorageBufferResourceBindings(const std::string & entry_point)408 Inspector::GetReadOnlyStorageBufferResourceBindings(
409 const std::string& entry_point) {
410 return GetStorageBufferResourceBindingsImpl(entry_point, true);
411 }
412
GetSamplerResourceBindings(const std::string & entry_point)413 std::vector<ResourceBinding> Inspector::GetSamplerResourceBindings(
414 const std::string& entry_point) {
415 auto* func = FindEntryPointByName(entry_point);
416 if (!func) {
417 return {};
418 }
419
420 std::vector<ResourceBinding> result;
421
422 auto* func_sem = program_->Sem().Get(func);
423 for (auto& rs : func_sem->TransitivelyReferencedSamplerVariables()) {
424 auto binding_info = rs.second;
425
426 ResourceBinding entry;
427 entry.resource_type = ResourceBinding::ResourceType::kSampler;
428 entry.bind_group = binding_info.group->value;
429 entry.binding = binding_info.binding->value;
430
431 result.push_back(entry);
432 }
433
434 return result;
435 }
436
GetComparisonSamplerResourceBindings(const std::string & entry_point)437 std::vector<ResourceBinding> Inspector::GetComparisonSamplerResourceBindings(
438 const std::string& entry_point) {
439 auto* func = FindEntryPointByName(entry_point);
440 if (!func) {
441 return {};
442 }
443
444 std::vector<ResourceBinding> result;
445
446 auto* func_sem = program_->Sem().Get(func);
447 for (auto& rcs :
448 func_sem->TransitivelyReferencedComparisonSamplerVariables()) {
449 auto binding_info = rcs.second;
450
451 ResourceBinding entry;
452 entry.resource_type = ResourceBinding::ResourceType::kComparisonSampler;
453 entry.bind_group = binding_info.group->value;
454 entry.binding = binding_info.binding->value;
455
456 result.push_back(entry);
457 }
458
459 return result;
460 }
461
GetSampledTextureResourceBindings(const std::string & entry_point)462 std::vector<ResourceBinding> Inspector::GetSampledTextureResourceBindings(
463 const std::string& entry_point) {
464 return GetSampledTextureResourceBindingsImpl(entry_point, false);
465 }
466
GetMultisampledTextureResourceBindings(const std::string & entry_point)467 std::vector<ResourceBinding> Inspector::GetMultisampledTextureResourceBindings(
468 const std::string& entry_point) {
469 return GetSampledTextureResourceBindingsImpl(entry_point, true);
470 }
471
472 std::vector<ResourceBinding>
GetWriteOnlyStorageTextureResourceBindings(const std::string & entry_point)473 Inspector::GetWriteOnlyStorageTextureResourceBindings(
474 const std::string& entry_point) {
475 return GetStorageTextureResourceBindingsImpl(entry_point);
476 }
477
GetTextureResourceBindings(const std::string & entry_point,const tint::TypeInfo & texture_type,ResourceBinding::ResourceType resource_type)478 std::vector<ResourceBinding> Inspector::GetTextureResourceBindings(
479 const std::string& entry_point,
480 const tint::TypeInfo& texture_type,
481 ResourceBinding::ResourceType resource_type) {
482 auto* func = FindEntryPointByName(entry_point);
483 if (!func) {
484 return {};
485 }
486
487 std::vector<ResourceBinding> result;
488 auto* func_sem = program_->Sem().Get(func);
489 for (auto& ref :
490 func_sem->TransitivelyReferencedVariablesOfType(texture_type)) {
491 auto* var = ref.first;
492 auto binding_info = ref.second;
493
494 ResourceBinding entry;
495 entry.resource_type = resource_type;
496 entry.bind_group = binding_info.group->value;
497 entry.binding = binding_info.binding->value;
498
499 auto* tex = var->Type()->UnwrapRef()->As<sem::Texture>();
500 entry.dim =
501 TypeTextureDimensionToResourceBindingTextureDimension(tex->dim());
502
503 result.push_back(entry);
504 }
505
506 return result;
507 }
508
GetDepthTextureResourceBindings(const std::string & entry_point)509 std::vector<ResourceBinding> Inspector::GetDepthTextureResourceBindings(
510 const std::string& entry_point) {
511 return GetTextureResourceBindings(
512 entry_point, TypeInfo::Of<sem::DepthTexture>(),
513 ResourceBinding::ResourceType::kDepthTexture);
514 }
515
516 std::vector<ResourceBinding>
GetDepthMultisampledTextureResourceBindings(const std::string & entry_point)517 Inspector::GetDepthMultisampledTextureResourceBindings(
518 const std::string& entry_point) {
519 return GetTextureResourceBindings(
520 entry_point, TypeInfo::Of<sem::DepthMultisampledTexture>(),
521 ResourceBinding::ResourceType::kDepthMultisampledTexture);
522 }
523
GetExternalTextureResourceBindings(const std::string & entry_point)524 std::vector<ResourceBinding> Inspector::GetExternalTextureResourceBindings(
525 const std::string& entry_point) {
526 return GetTextureResourceBindings(
527 entry_point, TypeInfo::Of<sem::ExternalTexture>(),
528 ResourceBinding::ResourceType::kExternalTexture);
529 }
530
GetSamplerTextureUses(const std::string & entry_point)531 std::vector<SamplerTexturePair> Inspector::GetSamplerTextureUses(
532 const std::string& entry_point) {
533 auto* func = FindEntryPointByName(entry_point);
534 if (!func) {
535 return {};
536 }
537
538 GenerateSamplerTargets();
539
540 auto it = sampler_targets_->find(entry_point);
541 if (it == sampler_targets_->end()) {
542 return {};
543 }
544 return it->second;
545 }
546
GetWorkgroupStorageSize(const std::string & entry_point)547 uint32_t Inspector::GetWorkgroupStorageSize(const std::string& entry_point) {
548 auto* func = FindEntryPointByName(entry_point);
549 if (!func) {
550 return 0;
551 }
552
553 uint32_t total_size = 0;
554 auto* func_sem = program_->Sem().Get(func);
555 for (const sem::Variable* var : func_sem->TransitivelyReferencedGlobals()) {
556 if (var->StorageClass() == ast::StorageClass::kWorkgroup) {
557 auto* ty = var->Type()->UnwrapRef();
558 uint32_t align = ty->Align();
559 uint32_t size = ty->Size();
560
561 // This essentially matches std430 layout rules from GLSL, which are in
562 // turn specified as an upper bound for Vulkan layout sizing. Since D3D
563 // and Metal are even less specific, we assume Vulkan behavior as a
564 // good-enough approximation everywhere.
565 total_size += utils::RoundUp(align, size);
566 }
567 }
568
569 return total_size;
570 }
571
FindEntryPointByName(const std::string & name)572 const ast::Function* Inspector::FindEntryPointByName(const std::string& name) {
573 auto* func = program_->AST().Functions().Find(program_->Symbols().Get(name));
574 if (!func) {
575 diagnostics_.add_error(diag::System::Inspector, name + " was not found!");
576 return nullptr;
577 }
578
579 if (!func->IsEntryPoint()) {
580 diagnostics_.add_error(diag::System::Inspector,
581 name + " is not an entry point!");
582 return nullptr;
583 }
584
585 return func;
586 }
587
AddEntryPointInOutVariables(std::string name,const sem::Type * type,const ast::DecorationList & decorations,std::vector<StageVariable> & variables) const588 void Inspector::AddEntryPointInOutVariables(
589 std::string name,
590 const sem::Type* type,
591 const ast::DecorationList& decorations,
592 std::vector<StageVariable>& variables) const {
593 // Skip builtins.
594 if (ast::HasDecoration<ast::BuiltinDecoration>(decorations)) {
595 return;
596 }
597
598 auto* unwrapped_type = type->UnwrapRef();
599
600 if (auto* struct_ty = unwrapped_type->As<sem::Struct>()) {
601 // Recurse into members.
602 for (auto* member : struct_ty->Members()) {
603 AddEntryPointInOutVariables(
604 name + "." +
605 program_->Symbols().NameFor(member->Declaration()->symbol),
606 member->Type(), member->Declaration()->decorations, variables);
607 }
608 return;
609 }
610
611 // Base case: add the variable.
612
613 StageVariable stage_variable;
614 stage_variable.name = name;
615 std::tie(stage_variable.component_type, stage_variable.composition_type) =
616 CalculateComponentAndComposition(type);
617
618 auto* location = ast::GetDecoration<ast::LocationDecoration>(decorations);
619 TINT_ASSERT(Inspector, location != nullptr);
620 stage_variable.has_location_decoration = true;
621 stage_variable.location_decoration = location->value;
622
623 std::tie(stage_variable.interpolation_type,
624 stage_variable.interpolation_sampling) =
625 CalculateInterpolationData(type, decorations);
626
627 variables.push_back(stage_variable);
628 }
629
ContainsBuiltin(ast::Builtin builtin,const sem::Type * type,const ast::DecorationList & decorations) const630 bool Inspector::ContainsBuiltin(ast::Builtin builtin,
631 const sem::Type* type,
632 const ast::DecorationList& decorations) const {
633 auto* unwrapped_type = type->UnwrapRef();
634
635 if (auto* struct_ty = unwrapped_type->As<sem::Struct>()) {
636 // Recurse into members.
637 for (auto* member : struct_ty->Members()) {
638 if (ContainsBuiltin(builtin, member->Type(),
639 member->Declaration()->decorations)) {
640 return true;
641 }
642 }
643 return false;
644 }
645
646 // Base case: check for builtin
647 auto* builtin_declaration =
648 ast::GetDecoration<ast::BuiltinDecoration>(decorations);
649 if (!builtin_declaration || builtin_declaration->builtin != builtin) {
650 return false;
651 }
652
653 return true;
654 }
655
GetStorageBufferResourceBindingsImpl(const std::string & entry_point,bool read_only)656 std::vector<ResourceBinding> Inspector::GetStorageBufferResourceBindingsImpl(
657 const std::string& entry_point,
658 bool read_only) {
659 auto* func = FindEntryPointByName(entry_point);
660 if (!func) {
661 return {};
662 }
663
664 auto* func_sem = program_->Sem().Get(func);
665 std::vector<ResourceBinding> result;
666 for (auto& rsv : func_sem->TransitivelyReferencedStorageBufferVariables()) {
667 auto* var = rsv.first;
668 auto binding_info = rsv.second;
669
670 if (read_only != (var->Access() == ast::Access::kRead)) {
671 continue;
672 }
673
674 auto* str = var->Type()->UnwrapRef()->As<sem::Struct>();
675 if (!str) {
676 continue;
677 }
678
679 ResourceBinding entry;
680 entry.resource_type =
681 read_only ? ResourceBinding::ResourceType::kReadOnlyStorageBuffer
682 : ResourceBinding::ResourceType::kStorageBuffer;
683 entry.bind_group = binding_info.group->value;
684 entry.binding = binding_info.binding->value;
685 entry.size = str->Size();
686 entry.size_no_padding = str->SizeNoPadding();
687
688 result.push_back(entry);
689 }
690
691 return result;
692 }
693
GetSampledTextureResourceBindingsImpl(const std::string & entry_point,bool multisampled_only)694 std::vector<ResourceBinding> Inspector::GetSampledTextureResourceBindingsImpl(
695 const std::string& entry_point,
696 bool multisampled_only) {
697 auto* func = FindEntryPointByName(entry_point);
698 if (!func) {
699 return {};
700 }
701
702 std::vector<ResourceBinding> result;
703 auto* func_sem = program_->Sem().Get(func);
704 auto referenced_variables =
705 multisampled_only
706 ? func_sem->TransitivelyReferencedMultisampledTextureVariables()
707 : func_sem->TransitivelyReferencedSampledTextureVariables();
708 for (auto& ref : referenced_variables) {
709 auto* var = ref.first;
710 auto binding_info = ref.second;
711
712 ResourceBinding entry;
713 entry.resource_type =
714 multisampled_only ? ResourceBinding::ResourceType::kMultisampledTexture
715 : ResourceBinding::ResourceType::kSampledTexture;
716 entry.bind_group = binding_info.group->value;
717 entry.binding = binding_info.binding->value;
718
719 auto* texture_type = var->Type()->UnwrapRef()->As<sem::Texture>();
720 entry.dim = TypeTextureDimensionToResourceBindingTextureDimension(
721 texture_type->dim());
722
723 const sem::Type* base_type = nullptr;
724 if (multisampled_only) {
725 base_type = texture_type->As<sem::MultisampledTexture>()->type();
726 } else {
727 base_type = texture_type->As<sem::SampledTexture>()->type();
728 }
729 entry.sampled_kind = BaseTypeToSampledKind(base_type);
730
731 result.push_back(entry);
732 }
733
734 return result;
735 }
736
GetStorageTextureResourceBindingsImpl(const std::string & entry_point)737 std::vector<ResourceBinding> Inspector::GetStorageTextureResourceBindingsImpl(
738 const std::string& entry_point) {
739 auto* func = FindEntryPointByName(entry_point);
740 if (!func) {
741 return {};
742 }
743
744 auto* func_sem = program_->Sem().Get(func);
745 std::vector<ResourceBinding> result;
746 for (auto& ref :
747 func_sem->TransitivelyReferencedVariablesOfType<sem::StorageTexture>()) {
748 auto* var = ref.first;
749 auto binding_info = ref.second;
750
751 auto* texture_type = var->Type()->UnwrapRef()->As<sem::StorageTexture>();
752
753 ResourceBinding entry;
754 entry.resource_type =
755 ResourceBinding::ResourceType::kWriteOnlyStorageTexture;
756 entry.bind_group = binding_info.group->value;
757 entry.binding = binding_info.binding->value;
758
759 entry.dim = TypeTextureDimensionToResourceBindingTextureDimension(
760 texture_type->dim());
761
762 auto* base_type = texture_type->type();
763 entry.sampled_kind = BaseTypeToSampledKind(base_type);
764 entry.image_format = TypeImageFormatToResourceBindingImageFormat(
765 texture_type->image_format());
766
767 result.push_back(entry);
768 }
769
770 return result;
771 }
772
GenerateSamplerTargets()773 void Inspector::GenerateSamplerTargets() {
774 // Do not re-generate, since |program_| should not change during the lifetime
775 // of the inspector.
776 if (sampler_targets_ != nullptr) {
777 return;
778 }
779
780 sampler_targets_ = std::make_unique<std::unordered_map<
781 std::string, utils::UniqueVector<SamplerTexturePair>>>();
782
783 auto& sem = program_->Sem();
784
785 for (auto* node : program_->ASTNodes().Objects()) {
786 auto* c = node->As<ast::CallExpression>();
787 if (!c) {
788 continue;
789 }
790
791 auto* call = sem.Get(c);
792 if (!call) {
793 continue;
794 }
795
796 auto* i = call->Target()->As<sem::Intrinsic>();
797 if (!i) {
798 continue;
799 }
800
801 const auto& signature = i->Signature();
802 int sampler_index = signature.IndexOf(sem::ParameterUsage::kSampler);
803 if (sampler_index == -1) {
804 continue;
805 }
806
807 int texture_index = signature.IndexOf(sem::ParameterUsage::kTexture);
808 if (texture_index == -1) {
809 continue;
810 }
811
812 auto* call_func = call->Stmt()->Function();
813 std::vector<const sem::Function*> entry_points;
814 if (call_func->Declaration()->IsEntryPoint()) {
815 entry_points = {call_func};
816 } else {
817 entry_points = call_func->AncestorEntryPoints();
818 }
819
820 if (entry_points.empty()) {
821 continue;
822 }
823
824 auto* t = c->args[texture_index];
825 auto* s = c->args[sampler_index];
826
827 GetOriginatingResources(
828 std::array<const ast::Expression*, 2>{t, s},
829 [&](std::array<const sem::GlobalVariable*, 2> globals) {
830 auto* texture = globals[0];
831 sem::BindingPoint texture_binding_point = {
832 texture->Declaration()->BindingPoint().group->value,
833 texture->Declaration()->BindingPoint().binding->value};
834
835 auto* sampler = globals[1];
836 sem::BindingPoint sampler_binding_point = {
837 sampler->Declaration()->BindingPoint().group->value,
838 sampler->Declaration()->BindingPoint().binding->value};
839
840 for (auto* entry_point : entry_points) {
841 const auto& ep_name =
842 program_->Symbols().NameFor(entry_point->Declaration()->symbol);
843 (*sampler_targets_)[ep_name].add(
844 {sampler_binding_point, texture_binding_point});
845 }
846 });
847 }
848 }
849
850 template <size_t N, typename F>
GetOriginatingResources(std::array<const ast::Expression *,N> exprs,F && callback)851 void Inspector::GetOriginatingResources(
852 std::array<const ast::Expression*, N> exprs,
853 F&& callback) {
854 if (!program_->IsValid()) {
855 TINT_ICE(Inspector, diagnostics_)
856 << "attempting to get originating resources in invalid program";
857 return;
858 }
859
860 auto& sem = program_->Sem();
861
862 std::array<const sem::GlobalVariable*, N> globals{};
863 std::array<const sem::Parameter*, N> parameters{};
864 utils::UniqueVector<const ast::CallExpression*> callsites;
865
866 for (size_t i = 0; i < N; i++) {
867 auto*& expr = exprs[i];
868 // Resolve each of the expressions
869 while (true) {
870 if (auto* user = sem.Get<sem::VariableUser>(expr)) {
871 auto* var = user->Variable();
872
873 if (auto* global = tint::As<sem::GlobalVariable>(var)) {
874 // Found the global resource declaration.
875 globals[i] = global;
876 break; // Done with this expression.
877 }
878
879 if (auto* local = tint::As<sem::LocalVariable>(var)) {
880 // Chase the variable
881 expr = local->Declaration()->constructor;
882 if (!expr) {
883 TINT_ICE(Inspector, diagnostics_)
884 << "resource variable had no initializer";
885 return;
886 }
887 continue; // Continue chasing the expression in this function
888 }
889
890 if (auto* param = tint::As<sem::Parameter>(var)) {
891 // Gather each of the callers of this function
892 auto* func = tint::As<sem::Function>(param->Owner());
893 if (func->CallSites().empty()) {
894 // One or more of the expressions is a parameter, but this function
895 // is not called. Ignore.
896 return;
897 }
898 for (auto* call : func->CallSites()) {
899 callsites.add(call->Declaration());
900 }
901 // Need to evaluate each function call with the group of
902 // expressions, so move on to the next expression.
903 parameters[i] = param;
904 break;
905 }
906
907 TINT_ICE(Inspector, diagnostics_)
908 << "unexpected variable type " << var->TypeInfo().name;
909 }
910
911 if (auto* unary = tint::As<ast::UnaryOpExpression>(expr)) {
912 switch (unary->op) {
913 case ast::UnaryOp::kAddressOf:
914 case ast::UnaryOp::kIndirection:
915 // `*` and `&` are the only valid unary ops for a resource type,
916 // and must be balanced in order for the program to have passed
917 // validation. Just skip past these.
918 expr = unary->expr;
919 continue;
920 default: {
921 TINT_ICE(Inspector, diagnostics_)
922 << "unexpected unary op on resource: " << unary->op;
923 return;
924 }
925 }
926 }
927
928 TINT_ICE(Inspector, diagnostics_)
929 << "cannot resolve originating resource with expression type "
930 << expr->TypeInfo().name;
931 return;
932 }
933 }
934
935 if (callsites.size()) {
936 for (auto* call_expr : callsites) {
937 // Make a copy of the expressions for this callsite
938 std::array<const ast::Expression*, N> call_exprs = exprs;
939 // Patch all the parameter expressions with their argument
940 for (size_t i = 0; i < N; i++) {
941 if (auto* param = parameters[i]) {
942 call_exprs[i] = call_expr->args[param->Index()];
943 }
944 }
945 // Now call GetOriginatingResources() with from the callsite
946 GetOriginatingResources(call_exprs, callback);
947 }
948 } else {
949 // All the expressions resolved to globals
950 callback(globals);
951 }
952 }
953
954 } // namespace inspector
955 } // namespace tint
956