// Copyright 2020 The Tint Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "src/tint/inspector/inspector.h" #include #include #include "src/tint/ast/bool_literal_expression.h" #include "src/tint/ast/call_expression.h" #include "src/tint/ast/float_literal_expression.h" #include "src/tint/ast/id_attribute.h" #include "src/tint/ast/interpolate_attribute.h" #include "src/tint/ast/location_attribute.h" #include "src/tint/ast/module.h" #include "src/tint/sem/array.h" #include "src/tint/sem/call.h" #include "src/tint/sem/depth_multisampled_texture.h" #include "src/tint/sem/f32.h" #include "src/tint/sem/function.h" #include "src/tint/sem/i32.h" #include "src/tint/sem/matrix.h" #include "src/tint/sem/multisampled_texture.h" #include "src/tint/sem/sampled_texture.h" #include "src/tint/sem/statement.h" #include "src/tint/sem/storage_texture.h" #include "src/tint/sem/struct.h" #include "src/tint/sem/u32.h" #include "src/tint/sem/variable.h" #include "src/tint/sem/vector.h" #include "src/tint/sem/void.h" #include "src/tint/utils/math.h" #include "src/tint/utils/unique_vector.h" namespace tint::inspector { namespace { void AppendResourceBindings(std::vector* dest, const std::vector& orig) { TINT_ASSERT(Inspector, dest); if (!dest) { return; } dest->reserve(dest->size() + orig.size()); dest->insert(dest->end(), orig.begin(), orig.end()); } std::tuple CalculateComponentAndComposition(const sem::Type* type) { if (type->is_float_scalar()) { return {ComponentType::kFloat, CompositionType::kScalar}; } else if (type->is_float_vector()) { auto* vec = type->As(); if (vec->Width() == 2) { return {ComponentType::kFloat, CompositionType::kVec2}; } else if (vec->Width() == 3) { return {ComponentType::kFloat, CompositionType::kVec3}; } else if (vec->Width() == 4) { return {ComponentType::kFloat, CompositionType::kVec4}; } } else if (type->is_unsigned_integer_scalar()) { return {ComponentType::kUInt, CompositionType::kScalar}; } else if (type->is_unsigned_integer_vector()) { auto* vec = type->As(); if (vec->Width() == 2) { return {ComponentType::kUInt, CompositionType::kVec2}; } else if (vec->Width() == 3) { return {ComponentType::kUInt, CompositionType::kVec3}; } else if (vec->Width() == 4) { return {ComponentType::kUInt, CompositionType::kVec4}; } } else if (type->is_signed_integer_scalar()) { return {ComponentType::kSInt, CompositionType::kScalar}; } else if (type->is_signed_integer_vector()) { auto* vec = type->As(); if (vec->Width() == 2) { return {ComponentType::kSInt, CompositionType::kVec2}; } else if (vec->Width() == 3) { return {ComponentType::kSInt, CompositionType::kVec3}; } else if (vec->Width() == 4) { return {ComponentType::kSInt, CompositionType::kVec4}; } } return {ComponentType::kUnknown, CompositionType::kUnknown}; } std::tuple CalculateInterpolationData( const sem::Type* type, const ast::AttributeList& attributes) { auto* interpolation_attribute = ast::GetAttribute(attributes); if (type->is_integer_scalar_or_vector()) { return {InterpolationType::kFlat, InterpolationSampling::kNone}; } if (!interpolation_attribute) { return {InterpolationType::kPerspective, InterpolationSampling::kCenter}; } auto interpolation_type = interpolation_attribute->type; auto sampling = interpolation_attribute->sampling; if (interpolation_type != ast::InterpolationType::kFlat && sampling == ast::InterpolationSampling::kNone) { sampling = ast::InterpolationSampling::kCenter; } return {ASTToInspectorInterpolationType(interpolation_type), ASTToInspectorInterpolationSampling(sampling)}; } } // namespace Inspector::Inspector(const Program* program) : program_(program) {} Inspector::~Inspector() = default; std::vector Inspector::GetEntryPoints() { std::vector result; for (auto* func : program_->AST().Functions()) { if (!func->IsEntryPoint()) { continue; } auto* sem = program_->Sem().Get(func); EntryPoint entry_point; entry_point.name = program_->Symbols().NameFor(func->symbol); entry_point.remapped_name = program_->Symbols().NameFor(func->symbol); entry_point.stage = func->PipelineStage(); auto wgsize = sem->WorkgroupSize(); entry_point.workgroup_size_x = wgsize[0].value; entry_point.workgroup_size_y = wgsize[1].value; entry_point.workgroup_size_z = wgsize[2].value; if (wgsize[0].overridable_const || wgsize[1].overridable_const || wgsize[2].overridable_const) { // TODO(crbug.com/tint/713): Handle overridable constants. TINT_ASSERT(Inspector, false); } for (auto* param : sem->Parameters()) { AddEntryPointInOutVariables(program_->Symbols().NameFor(param->Declaration()->symbol), param->Type(), param->Declaration()->attributes, entry_point.input_variables); entry_point.input_position_used |= ContainsBuiltin( ast::Builtin::kPosition, param->Type(), param->Declaration()->attributes); entry_point.front_facing_used |= ContainsBuiltin( ast::Builtin::kFrontFacing, param->Type(), param->Declaration()->attributes); entry_point.sample_index_used |= ContainsBuiltin( ast::Builtin::kSampleIndex, param->Type(), param->Declaration()->attributes); entry_point.input_sample_mask_used |= ContainsBuiltin( ast::Builtin::kSampleMask, param->Type(), param->Declaration()->attributes); entry_point.num_workgroups_used |= ContainsBuiltin( ast::Builtin::kNumWorkgroups, param->Type(), param->Declaration()->attributes); } if (!sem->ReturnType()->Is()) { AddEntryPointInOutVariables("", sem->ReturnType(), func->return_type_attributes, entry_point.output_variables); entry_point.output_sample_mask_used = ContainsBuiltin( ast::Builtin::kSampleMask, sem->ReturnType(), func->return_type_attributes); } for (auto* var : sem->TransitivelyReferencedGlobals()) { auto* decl = var->Declaration(); auto name = program_->Symbols().NameFor(decl->symbol); auto* global = var->As(); if (global && global->IsOverridable()) { OverridableConstant overridable_constant; overridable_constant.name = name; overridable_constant.numeric_id = global->ConstantId(); auto* type = var->Type(); TINT_ASSERT(Inspector, type->is_scalar()); if (type->is_bool_scalar_or_vector()) { overridable_constant.type = OverridableConstant::Type::kBool; } else if (type->is_float_scalar()) { overridable_constant.type = OverridableConstant::Type::kFloat32; } else if (type->is_signed_integer_scalar()) { overridable_constant.type = OverridableConstant::Type::kInt32; } else if (type->is_unsigned_integer_scalar()) { overridable_constant.type = OverridableConstant::Type::kUint32; } else { TINT_UNREACHABLE(Inspector, diagnostics_); } overridable_constant.is_initialized = global->Declaration()->constructor; overridable_constant.is_numeric_id_specified = ast::HasAttribute(global->Declaration()->attributes); entry_point.overridable_constants.push_back(overridable_constant); } } result.push_back(std::move(entry_point)); } return result; } std::map Inspector::GetConstantIDs() { std::map result; for (auto* var : program_->AST().GlobalVariables()) { auto* global = program_->Sem().Get(var); if (!global || !global->IsOverridable()) { continue; } // If there are conflicting defintions for a constant id, that is invalid // WGSL, so the resolver should catch it. Thus here the inspector just // assumes all definitions of the constant id are the same, so only needs // to find the first reference to constant id. uint32_t constant_id = global->ConstantId(); if (result.find(constant_id) != result.end()) { continue; } if (!var->constructor) { result[constant_id] = Scalar(); continue; } auto* literal = var->constructor->As(); if (!literal) { // This is invalid WGSL, but handling gracefully. result[constant_id] = Scalar(); continue; } if (auto* l = literal->As()) { result[constant_id] = Scalar(l->value); continue; } if (auto* l = literal->As()) { switch (l->suffix) { case ast::IntLiteralExpression::Suffix::kNone: case ast::IntLiteralExpression::Suffix::kI: result[constant_id] = Scalar(static_cast(l->value)); continue; case ast::IntLiteralExpression::Suffix::kU: result[constant_id] = Scalar(static_cast(l->value)); continue; } } if (auto* l = literal->As()) { result[constant_id] = Scalar(l->value); continue; } result[constant_id] = Scalar(); } return result; } std::map Inspector::GetConstantNameToIdMap() { std::map result; for (auto* var : program_->AST().GlobalVariables()) { auto* global = program_->Sem().Get(var); if (global && global->IsOverridable()) { auto name = program_->Symbols().NameFor(var->symbol); result[name] = global->ConstantId(); } } return result; } uint32_t Inspector::GetStorageSize(const std::string& entry_point) { auto* func = FindEntryPointByName(entry_point); if (!func) { return 0; } size_t size = 0; auto* func_sem = program_->Sem().Get(func); for (auto& ruv : func_sem->TransitivelyReferencedUniformVariables()) { size += ruv.first->Type()->UnwrapRef()->Size(); } for (auto& rsv : func_sem->TransitivelyReferencedStorageBufferVariables()) { size += rsv.first->Type()->UnwrapRef()->Size(); } if (static_cast(size) > static_cast(std::numeric_limits::max())) { return std::numeric_limits::max(); } return static_cast(size); } std::vector Inspector::GetResourceBindings(const std::string& entry_point) { auto* func = FindEntryPointByName(entry_point); if (!func) { return {}; } std::vector result; for (auto fn : { &Inspector::GetUniformBufferResourceBindings, &Inspector::GetStorageBufferResourceBindings, &Inspector::GetReadOnlyStorageBufferResourceBindings, &Inspector::GetSamplerResourceBindings, &Inspector::GetComparisonSamplerResourceBindings, &Inspector::GetSampledTextureResourceBindings, &Inspector::GetMultisampledTextureResourceBindings, &Inspector::GetWriteOnlyStorageTextureResourceBindings, &Inspector::GetDepthTextureResourceBindings, &Inspector::GetDepthMultisampledTextureResourceBindings, &Inspector::GetExternalTextureResourceBindings, }) { AppendResourceBindings(&result, (this->*fn)(entry_point)); } return result; } std::vector Inspector::GetUniformBufferResourceBindings( const std::string& entry_point) { auto* func = FindEntryPointByName(entry_point); if (!func) { return {}; } std::vector result; auto* func_sem = program_->Sem().Get(func); for (auto& ruv : func_sem->TransitivelyReferencedUniformVariables()) { auto* var = ruv.first; auto binding_info = ruv.second; auto* unwrapped_type = var->Type()->UnwrapRef(); ResourceBinding entry; entry.resource_type = ResourceBinding::ResourceType::kUniformBuffer; entry.bind_group = binding_info.group->value; entry.binding = binding_info.binding->value; entry.size = unwrapped_type->Size(); entry.size_no_padding = entry.size; if (auto* str = unwrapped_type->As()) { entry.size_no_padding = str->SizeNoPadding(); } else { entry.size_no_padding = entry.size; } result.push_back(entry); } return result; } std::vector Inspector::GetStorageBufferResourceBindings( const std::string& entry_point) { return GetStorageBufferResourceBindingsImpl(entry_point, false); } std::vector Inspector::GetReadOnlyStorageBufferResourceBindings( const std::string& entry_point) { return GetStorageBufferResourceBindingsImpl(entry_point, true); } std::vector Inspector::GetSamplerResourceBindings(const std::string& entry_point) { auto* func = FindEntryPointByName(entry_point); if (!func) { return {}; } std::vector result; auto* func_sem = program_->Sem().Get(func); for (auto& rs : func_sem->TransitivelyReferencedSamplerVariables()) { auto binding_info = rs.second; ResourceBinding entry; entry.resource_type = ResourceBinding::ResourceType::kSampler; entry.bind_group = binding_info.group->value; entry.binding = binding_info.binding->value; result.push_back(entry); } return result; } std::vector Inspector::GetComparisonSamplerResourceBindings( const std::string& entry_point) { auto* func = FindEntryPointByName(entry_point); if (!func) { return {}; } std::vector result; auto* func_sem = program_->Sem().Get(func); for (auto& rcs : func_sem->TransitivelyReferencedComparisonSamplerVariables()) { auto binding_info = rcs.second; ResourceBinding entry; entry.resource_type = ResourceBinding::ResourceType::kComparisonSampler; entry.bind_group = binding_info.group->value; entry.binding = binding_info.binding->value; result.push_back(entry); } return result; } std::vector Inspector::GetSampledTextureResourceBindings( const std::string& entry_point) { return GetSampledTextureResourceBindingsImpl(entry_point, false); } std::vector Inspector::GetMultisampledTextureResourceBindings( const std::string& entry_point) { return GetSampledTextureResourceBindingsImpl(entry_point, true); } std::vector Inspector::GetWriteOnlyStorageTextureResourceBindings( const std::string& entry_point) { return GetStorageTextureResourceBindingsImpl(entry_point); } std::vector Inspector::GetTextureResourceBindings( const std::string& entry_point, const tint::TypeInfo* texture_type, ResourceBinding::ResourceType resource_type) { auto* func = FindEntryPointByName(entry_point); if (!func) { return {}; } std::vector result; auto* func_sem = program_->Sem().Get(func); for (auto& ref : func_sem->TransitivelyReferencedVariablesOfType(texture_type)) { auto* var = ref.first; auto binding_info = ref.second; ResourceBinding entry; entry.resource_type = resource_type; entry.bind_group = binding_info.group->value; entry.binding = binding_info.binding->value; auto* tex = var->Type()->UnwrapRef()->As(); entry.dim = TypeTextureDimensionToResourceBindingTextureDimension(tex->dim()); result.push_back(entry); } return result; } std::vector Inspector::GetDepthTextureResourceBindings( const std::string& entry_point) { return GetTextureResourceBindings(entry_point, &TypeInfo::Of(), ResourceBinding::ResourceType::kDepthTexture); } std::vector Inspector::GetDepthMultisampledTextureResourceBindings( const std::string& entry_point) { return GetTextureResourceBindings(entry_point, &TypeInfo::Of(), ResourceBinding::ResourceType::kDepthMultisampledTexture); } std::vector Inspector::GetExternalTextureResourceBindings( const std::string& entry_point) { return GetTextureResourceBindings(entry_point, &TypeInfo::Of(), ResourceBinding::ResourceType::kExternalTexture); } std::vector Inspector::GetSamplerTextureUses( const std::string& entry_point) { auto* func = FindEntryPointByName(entry_point); if (!func) { return {}; } GenerateSamplerTargets(); auto it = sampler_targets_->find(entry_point); if (it == sampler_targets_->end()) { return {}; } return it->second; } std::vector Inspector::GetSamplerTextureUses( const std::string& entry_point, const sem::BindingPoint& placeholder) { auto* func = FindEntryPointByName(entry_point); if (!func) { return {}; } auto* func_sem = program_->Sem().Get(func); std::vector new_pairs; for (auto pair : func_sem->TextureSamplerPairs()) { auto* texture = pair.first->As(); auto* sampler = pair.second ? pair.second->As() : nullptr; SamplerTexturePair new_pair; new_pair.sampler_binding_point = sampler ? sampler->BindingPoint() : placeholder; new_pair.texture_binding_point = texture->BindingPoint(); new_pairs.push_back(new_pair); } return new_pairs; } uint32_t Inspector::GetWorkgroupStorageSize(const std::string& entry_point) { auto* func = FindEntryPointByName(entry_point); if (!func) { return 0; } uint32_t total_size = 0; auto* func_sem = program_->Sem().Get(func); for (const sem::Variable* var : func_sem->TransitivelyReferencedGlobals()) { if (var->StorageClass() == ast::StorageClass::kWorkgroup) { auto* ty = var->Type()->UnwrapRef(); uint32_t align = ty->Align(); uint32_t size = ty->Size(); // This essentially matches std430 layout rules from GLSL, which are in // turn specified as an upper bound for Vulkan layout sizing. Since D3D // and Metal are even less specific, we assume Vulkan behavior as a // good-enough approximation everywhere. total_size += utils::RoundUp(align, size); } } return total_size; } std::vector Inspector::GetUsedExtensionNames() { std::vector result; ast::ExtensionSet set = program_->AST().Extensions(); result.reserve(set.size()); for (auto kind : set) { std::string name = ast::Enable::KindToName(kind); result.push_back(name); } return result; } std::vector> Inspector::GetEnableDirectives() { std::vector> result; // Ast nodes for enable directive are stored within global declarations list auto global_decls = program_->AST().GlobalDeclarations(); for (auto* node : global_decls) { if (auto* ext = node->As()) { result.push_back({ext->name, ext->source}); } } return result; } const ast::Function* Inspector::FindEntryPointByName(const std::string& name) { auto* func = program_->AST().Functions().Find(program_->Symbols().Get(name)); if (!func) { diagnostics_.add_error(diag::System::Inspector, name + " was not found!"); return nullptr; } if (!func->IsEntryPoint()) { diagnostics_.add_error(diag::System::Inspector, name + " is not an entry point!"); return nullptr; } return func; } void Inspector::AddEntryPointInOutVariables(std::string name, const sem::Type* type, const ast::AttributeList& attributes, std::vector& variables) const { // Skip builtins. if (ast::HasAttribute(attributes)) { return; } auto* unwrapped_type = type->UnwrapRef(); if (auto* struct_ty = unwrapped_type->As()) { // Recurse into members. for (auto* member : struct_ty->Members()) { AddEntryPointInOutVariables( name + "." + program_->Symbols().NameFor(member->Declaration()->symbol), member->Type(), member->Declaration()->attributes, variables); } return; } // Base case: add the variable. StageVariable stage_variable; stage_variable.name = name; std::tie(stage_variable.component_type, stage_variable.composition_type) = CalculateComponentAndComposition(type); auto* location = ast::GetAttribute(attributes); TINT_ASSERT(Inspector, location != nullptr); stage_variable.has_location_attribute = true; stage_variable.location_attribute = location->value; std::tie(stage_variable.interpolation_type, stage_variable.interpolation_sampling) = CalculateInterpolationData(type, attributes); variables.push_back(stage_variable); } bool Inspector::ContainsBuiltin(ast::Builtin builtin, const sem::Type* type, const ast::AttributeList& attributes) const { auto* unwrapped_type = type->UnwrapRef(); if (auto* struct_ty = unwrapped_type->As()) { // Recurse into members. for (auto* member : struct_ty->Members()) { if (ContainsBuiltin(builtin, member->Type(), member->Declaration()->attributes)) { return true; } } return false; } // Base case: check for builtin auto* builtin_declaration = ast::GetAttribute(attributes); if (!builtin_declaration || builtin_declaration->builtin != builtin) { return false; } return true; } std::vector Inspector::GetStorageBufferResourceBindingsImpl( const std::string& entry_point, bool read_only) { auto* func = FindEntryPointByName(entry_point); if (!func) { return {}; } auto* func_sem = program_->Sem().Get(func); std::vector result; for (auto& rsv : func_sem->TransitivelyReferencedStorageBufferVariables()) { auto* var = rsv.first; auto binding_info = rsv.second; if (read_only != (var->Access() == ast::Access::kRead)) { continue; } auto* unwrapped_type = var->Type()->UnwrapRef(); ResourceBinding entry; entry.resource_type = read_only ? ResourceBinding::ResourceType::kReadOnlyStorageBuffer : ResourceBinding::ResourceType::kStorageBuffer; entry.bind_group = binding_info.group->value; entry.binding = binding_info.binding->value; entry.size = unwrapped_type->Size(); if (auto* str = unwrapped_type->As()) { entry.size_no_padding = str->SizeNoPadding(); } else { entry.size_no_padding = entry.size; } result.push_back(entry); } return result; } std::vector Inspector::GetSampledTextureResourceBindingsImpl( const std::string& entry_point, bool multisampled_only) { auto* func = FindEntryPointByName(entry_point); if (!func) { return {}; } std::vector result; auto* func_sem = program_->Sem().Get(func); auto referenced_variables = multisampled_only ? func_sem->TransitivelyReferencedMultisampledTextureVariables() : func_sem->TransitivelyReferencedSampledTextureVariables(); for (auto& ref : referenced_variables) { auto* var = ref.first; auto binding_info = ref.second; ResourceBinding entry; entry.resource_type = multisampled_only ? ResourceBinding::ResourceType::kMultisampledTexture : ResourceBinding::ResourceType::kSampledTexture; entry.bind_group = binding_info.group->value; entry.binding = binding_info.binding->value; auto* texture_type = var->Type()->UnwrapRef()->As(); entry.dim = TypeTextureDimensionToResourceBindingTextureDimension(texture_type->dim()); const sem::Type* base_type = nullptr; if (multisampled_only) { base_type = texture_type->As()->type(); } else { base_type = texture_type->As()->type(); } entry.sampled_kind = BaseTypeToSampledKind(base_type); result.push_back(entry); } return result; } std::vector Inspector::GetStorageTextureResourceBindingsImpl( const std::string& entry_point) { auto* func = FindEntryPointByName(entry_point); if (!func) { return {}; } auto* func_sem = program_->Sem().Get(func); std::vector result; for (auto& ref : func_sem->TransitivelyReferencedVariablesOfType()) { auto* var = ref.first; auto binding_info = ref.second; auto* texture_type = var->Type()->UnwrapRef()->As(); ResourceBinding entry; entry.resource_type = ResourceBinding::ResourceType::kWriteOnlyStorageTexture; entry.bind_group = binding_info.group->value; entry.binding = binding_info.binding->value; entry.dim = TypeTextureDimensionToResourceBindingTextureDimension(texture_type->dim()); auto* base_type = texture_type->type(); entry.sampled_kind = BaseTypeToSampledKind(base_type); entry.image_format = TypeTexelFormatToResourceBindingTexelFormat(texture_type->texel_format()); result.push_back(entry); } return result; } void Inspector::GenerateSamplerTargets() { // Do not re-generate, since |program_| should not change during the lifetime // of the inspector. if (sampler_targets_ != nullptr) { return; } sampler_targets_ = std::make_unique< std::unordered_map>>(); auto& sem = program_->Sem(); for (auto* node : program_->ASTNodes().Objects()) { auto* c = node->As(); if (!c) { continue; } auto* call = sem.Get(c); if (!call) { continue; } auto* i = call->Target()->As(); if (!i) { continue; } const auto& signature = i->Signature(); int sampler_index = signature.IndexOf(sem::ParameterUsage::kSampler); if (sampler_index == -1) { continue; } int texture_index = signature.IndexOf(sem::ParameterUsage::kTexture); if (texture_index == -1) { continue; } auto* call_func = call->Stmt()->Function(); std::vector entry_points; if (call_func->Declaration()->IsEntryPoint()) { entry_points = {call_func}; } else { entry_points = call_func->AncestorEntryPoints(); } if (entry_points.empty()) { continue; } auto* t = c->args[texture_index]; auto* s = c->args[sampler_index]; GetOriginatingResources(std::array{t, s}, [&](std::array globals) { auto* texture = globals[0]; sem::BindingPoint texture_binding_point = { texture->Declaration()->BindingPoint().group->value, texture->Declaration()->BindingPoint().binding->value}; auto* sampler = globals[1]; sem::BindingPoint sampler_binding_point = { sampler->Declaration()->BindingPoint().group->value, sampler->Declaration()->BindingPoint().binding->value}; for (auto* entry_point : entry_points) { const auto& ep_name = program_->Symbols().NameFor( entry_point->Declaration()->symbol); (*sampler_targets_)[ep_name].add( {sampler_binding_point, texture_binding_point}); } }); } } template void Inspector::GetOriginatingResources(std::array exprs, F&& callback) { if (!program_->IsValid()) { TINT_ICE(Inspector, diagnostics_) << "attempting to get originating resources in invalid program"; return; } auto& sem = program_->Sem(); std::array globals{}; std::array parameters{}; utils::UniqueVector callsites; for (size_t i = 0; i < N; i++) { const sem::Variable* source_var = sem.Get(exprs[i])->SourceVariable(); if (auto* global = source_var->As()) { globals[i] = global; } else if (auto* param = source_var->As()) { auto* func = tint::As(param->Owner()); if (func->CallSites().empty()) { // One or more of the expressions is a parameter, but this function // is not called. Ignore. return; } for (auto* call : func->CallSites()) { callsites.add(call->Declaration()); } parameters[i] = param; } else { TINT_ICE(Inspector, diagnostics_) << "cannot resolve originating resource with expression type " << exprs[i]->TypeInfo().name; return; } } if (callsites.size()) { for (auto* call_expr : callsites) { // Make a copy of the expressions for this callsite std::array call_exprs = exprs; // Patch all the parameter expressions with their argument for (size_t i = 0; i < N; i++) { if (auto* param = parameters[i]) { call_exprs[i] = call_expr->args[param->Index()]; } } // Now call GetOriginatingResources() with from the callsite GetOriginatingResources(call_exprs, callback); } } else { // All the expressions resolved to globals callback(globals); } } } // namespace tint::inspector