blob: 299c5fb8d3b1f61be3bee92b50eebd5f58945c41 [file] [log] [blame]
// 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/inspector/inspector.h"
#include <limits>
#include <utility>
#include "src/ast/bool_literal.h"
#include "src/ast/call_expression.h"
#include "src/ast/float_literal.h"
#include "src/ast/interpolate_decoration.h"
#include "src/ast/location_decoration.h"
#include "src/ast/module.h"
#include "src/ast/override_decoration.h"
#include "src/ast/scalar_constructor_expression.h"
#include "src/ast/sint_literal.h"
#include "src/ast/uint_literal.h"
#include "src/sem/array.h"
#include "src/sem/call.h"
#include "src/sem/depth_multisampled_texture_type.h"
#include "src/sem/f32_type.h"
#include "src/sem/function.h"
#include "src/sem/i32_type.h"
#include "src/sem/matrix_type.h"
#include "src/sem/multisampled_texture_type.h"
#include "src/sem/sampled_texture_type.h"
#include "src/sem/statement.h"
#include "src/sem/storage_texture_type.h"
#include "src/sem/struct.h"
#include "src/sem/u32_type.h"
#include "src/sem/variable.h"
#include "src/sem/vector_type.h"
#include "src/sem/void_type.h"
#include "src/utils/math.h"
#include "src/utils/unique_vector.h"
namespace tint {
namespace inspector {
namespace {
void AppendResourceBindings(std::vector<ResourceBinding>* dest,
const std::vector<ResourceBinding>& orig) {
TINT_ASSERT(Inspector, dest);
if (!dest) {
return;
}
dest->reserve(dest->size() + orig.size());
dest->insert(dest->end(), orig.begin(), orig.end());
}
std::tuple<ComponentType, CompositionType> 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<sem::Vector>();
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<sem::Vector>();
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<sem::Vector>();
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<InterpolationType, InterpolationSampling> CalculateInterpolationData(
const sem::Type* type,
const ast::DecorationList& decorations) {
auto* interpolation_decoration =
ast::GetDecoration<ast::InterpolateDecoration>(decorations);
if (type->is_integer_scalar_or_vector()) {
return {InterpolationType::kFlat, InterpolationSampling::kNone};
}
if (!interpolation_decoration) {
return {InterpolationType::kPerspective, InterpolationSampling::kCenter};
}
auto interpolation_type = interpolation_decoration->type;
auto sampling = interpolation_decoration->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<EntryPoint> Inspector::GetEntryPoints() {
std::vector<EntryPoint> 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()->decorations,
entry_point.input_variables);
entry_point.input_position_used |=
ContainsBuiltin(ast::Builtin::kPosition, param->Type(),
param->Declaration()->decorations);
entry_point.front_facing_used |=
ContainsBuiltin(ast::Builtin::kFrontFacing, param->Type(),
param->Declaration()->decorations);
entry_point.sample_index_used |=
ContainsBuiltin(ast::Builtin::kSampleIndex, param->Type(),
param->Declaration()->decorations);
entry_point.input_sample_mask_used |=
ContainsBuiltin(ast::Builtin::kSampleMask, param->Type(),
param->Declaration()->decorations);
entry_point.num_workgroups_used |=
ContainsBuiltin(ast::Builtin::kNumWorkgroups, param->Type(),
param->Declaration()->decorations);
}
if (!sem->ReturnType()->Is<sem::Void>()) {
AddEntryPointInOutVariables("<retval>", sem->ReturnType(),
func->return_type_decorations,
entry_point.output_variables);
entry_point.output_sample_mask_used =
ContainsBuiltin(ast::Builtin::kSampleMask, sem->ReturnType(),
func->return_type_decorations);
}
for (auto* var : sem->TransitivelyReferencedGlobals()) {
auto* decl = var->Declaration();
auto name = program_->Symbols().NameFor(decl->symbol);
auto* global = var->As<sem::GlobalVariable>();
if (global && global->IsPipelineConstant()) {
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;
auto* override_deco = ast::GetDecoration<ast::OverrideDecoration>(
global->Declaration()->decorations);
overridable_constant.is_numeric_id_specified =
override_deco ? override_deco->has_value : false;
entry_point.overridable_constants.push_back(overridable_constant);
}
}
result.push_back(std::move(entry_point));
}
return result;
}
std::string Inspector::GetRemappedNameForEntryPoint(
const std::string& entry_point) {
// TODO(rharrison): Reenable once all of the backends are using the renamed
// entry points.
// auto* func = FindEntryPointByName(entry_point);
// if (!func) {
// return {};
// }
// return func->name();
return entry_point;
}
std::map<uint32_t, Scalar> Inspector::GetConstantIDs() {
std::map<uint32_t, Scalar> result;
for (auto* var : program_->AST().GlobalVariables()) {
auto* global = program_->Sem().Get<sem::GlobalVariable>(var);
if (!global || !global->IsPipelineConstant()) {
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* expression = var->constructor;
auto* constructor = expression->As<ast::ConstructorExpression>();
if (constructor == nullptr) {
// This is invalid WGSL, but handling gracefully.
result[constant_id] = Scalar();
continue;
}
auto* scalar_constructor =
constructor->As<ast::ScalarConstructorExpression>();
if (scalar_constructor == nullptr) {
// This is invalid WGSL, but handling gracefully.
result[constant_id] = Scalar();
continue;
}
auto* literal = scalar_constructor->literal;
if (!literal) {
// This is invalid WGSL, but handling gracefully.
result[constant_id] = Scalar();
continue;
}
if (auto* l = literal->As<ast::BoolLiteral>()) {
result[constant_id] = Scalar(l->value);
continue;
}
if (auto* l = literal->As<ast::UintLiteral>()) {
result[constant_id] = Scalar(l->value);
continue;
}
if (auto* l = literal->As<ast::SintLiteral>()) {
result[constant_id] = Scalar(l->value);
continue;
}
if (auto* l = literal->As<ast::FloatLiteral>()) {
result[constant_id] = Scalar(l->value);
continue;
}
result[constant_id] = Scalar();
}
return result;
}
std::map<std::string, uint32_t> Inspector::GetConstantNameToIdMap() {
std::map<std::string, uint32_t> result;
for (auto* var : program_->AST().GlobalVariables()) {
auto* global = program_->Sem().Get<sem::GlobalVariable>(var);
if (global && global->IsPipelineConstant()) {
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()) {
const sem::Struct* s = ruv.first->Type()->UnwrapRef()->As<sem::Struct>();
if (s && s->IsBlockDecorated()) {
size += s->Size();
}
}
for (auto& rsv : func_sem->TransitivelyReferencedStorageBufferVariables()) {
const sem::Struct* s = rsv.first->Type()->UnwrapRef()->As<sem::Struct>();
if (s) {
size += s->Size();
}
}
if (static_cast<uint64_t>(size) >
static_cast<uint64_t>(std::numeric_limits<uint32_t>::max())) {
return std::numeric_limits<uint32_t>::max();
}
return static_cast<uint32_t>(size);
}
std::vector<ResourceBinding> Inspector::GetResourceBindings(
const std::string& entry_point) {
auto* func = FindEntryPointByName(entry_point);
if (!func) {
return {};
}
std::vector<ResourceBinding> 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<ResourceBinding> Inspector::GetUniformBufferResourceBindings(
const std::string& entry_point) {
auto* func = FindEntryPointByName(entry_point);
if (!func) {
return {};
}
std::vector<ResourceBinding> 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();
auto* str = unwrapped_type->As<sem::Struct>();
if (str == nullptr) {
continue;
}
if (!str->IsBlockDecorated()) {
continue;
}
ResourceBinding entry;
entry.resource_type = ResourceBinding::ResourceType::kUniformBuffer;
entry.bind_group = binding_info.group->value;
entry.binding = binding_info.binding->value;
entry.size = str->Size();
entry.size_no_padding = str->SizeNoPadding();
result.push_back(entry);
}
return result;
}
std::vector<ResourceBinding> Inspector::GetStorageBufferResourceBindings(
const std::string& entry_point) {
return GetStorageBufferResourceBindingsImpl(entry_point, false);
}
std::vector<ResourceBinding>
Inspector::GetReadOnlyStorageBufferResourceBindings(
const std::string& entry_point) {
return GetStorageBufferResourceBindingsImpl(entry_point, true);
}
std::vector<ResourceBinding> Inspector::GetSamplerResourceBindings(
const std::string& entry_point) {
auto* func = FindEntryPointByName(entry_point);
if (!func) {
return {};
}
std::vector<ResourceBinding> 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<ResourceBinding> Inspector::GetComparisonSamplerResourceBindings(
const std::string& entry_point) {
auto* func = FindEntryPointByName(entry_point);
if (!func) {
return {};
}
std::vector<ResourceBinding> 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<ResourceBinding> Inspector::GetSampledTextureResourceBindings(
const std::string& entry_point) {
return GetSampledTextureResourceBindingsImpl(entry_point, false);
}
std::vector<ResourceBinding> Inspector::GetMultisampledTextureResourceBindings(
const std::string& entry_point) {
return GetSampledTextureResourceBindingsImpl(entry_point, true);
}
std::vector<ResourceBinding>
Inspector::GetWriteOnlyStorageTextureResourceBindings(
const std::string& entry_point) {
return GetStorageTextureResourceBindingsImpl(entry_point);
}
std::vector<ResourceBinding> 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<ResourceBinding> 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<sem::Texture>();
entry.dim =
TypeTextureDimensionToResourceBindingTextureDimension(tex->dim());
result.push_back(entry);
}
return result;
}
std::vector<ResourceBinding> Inspector::GetDepthTextureResourceBindings(
const std::string& entry_point) {
return GetTextureResourceBindings(
entry_point, TypeInfo::Of<sem::DepthTexture>(),
ResourceBinding::ResourceType::kDepthTexture);
}
std::vector<ResourceBinding>
Inspector::GetDepthMultisampledTextureResourceBindings(
const std::string& entry_point) {
return GetTextureResourceBindings(
entry_point, TypeInfo::Of<sem::DepthMultisampledTexture>(),
ResourceBinding::ResourceType::kDepthMultisampledTexture);
}
std::vector<ResourceBinding> Inspector::GetExternalTextureResourceBindings(
const std::string& entry_point) {
return GetTextureResourceBindings(
entry_point, TypeInfo::Of<sem::ExternalTexture>(),
ResourceBinding::ResourceType::kExternalTexture);
}
std::vector<SamplerTexturePair> 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;
}
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;
}
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::DecorationList& decorations,
std::vector<StageVariable>& variables) const {
// Skip builtins.
if (ast::HasDecoration<ast::BuiltinDecoration>(decorations)) {
return;
}
auto* unwrapped_type = type->UnwrapRef();
if (auto* struct_ty = unwrapped_type->As<sem::Struct>()) {
// Recurse into members.
for (auto* member : struct_ty->Members()) {
AddEntryPointInOutVariables(
name + "." +
program_->Symbols().NameFor(member->Declaration()->symbol),
member->Type(), member->Declaration()->decorations, 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::GetDecoration<ast::LocationDecoration>(decorations);
TINT_ASSERT(Inspector, location != nullptr);
stage_variable.has_location_decoration = true;
stage_variable.location_decoration = location->value;
std::tie(stage_variable.interpolation_type,
stage_variable.interpolation_sampling) =
CalculateInterpolationData(type, decorations);
variables.push_back(stage_variable);
}
bool Inspector::ContainsBuiltin(ast::Builtin builtin,
const sem::Type* type,
const ast::DecorationList& decorations) const {
auto* unwrapped_type = type->UnwrapRef();
if (auto* struct_ty = unwrapped_type->As<sem::Struct>()) {
// Recurse into members.
for (auto* member : struct_ty->Members()) {
if (ContainsBuiltin(builtin, member->Type(),
member->Declaration()->decorations)) {
return true;
}
}
return false;
}
// Base case: check for builtin
auto* builtin_declaration =
ast::GetDecoration<ast::BuiltinDecoration>(decorations);
if (!builtin_declaration || builtin_declaration->builtin != builtin) {
return false;
}
return true;
}
std::vector<ResourceBinding> 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<ResourceBinding> 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* str = var->Type()->UnwrapRef()->As<sem::Struct>();
if (!str) {
continue;
}
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 = str->Size();
entry.size_no_padding = str->SizeNoPadding();
result.push_back(entry);
}
return result;
}
std::vector<ResourceBinding> Inspector::GetSampledTextureResourceBindingsImpl(
const std::string& entry_point,
bool multisampled_only) {
auto* func = FindEntryPointByName(entry_point);
if (!func) {
return {};
}
std::vector<ResourceBinding> 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<sem::Texture>();
entry.dim = TypeTextureDimensionToResourceBindingTextureDimension(
texture_type->dim());
const sem::Type* base_type = nullptr;
if (multisampled_only) {
base_type = texture_type->As<sem::MultisampledTexture>()->type();
} else {
base_type = texture_type->As<sem::SampledTexture>()->type();
}
entry.sampled_kind = BaseTypeToSampledKind(base_type);
result.push_back(entry);
}
return result;
}
std::vector<ResourceBinding> Inspector::GetStorageTextureResourceBindingsImpl(
const std::string& entry_point) {
auto* func = FindEntryPointByName(entry_point);
if (!func) {
return {};
}
auto* func_sem = program_->Sem().Get(func);
std::vector<ResourceBinding> result;
for (auto& ref :
func_sem->TransitivelyReferencedVariablesOfType<sem::StorageTexture>()) {
auto* var = ref.first;
auto binding_info = ref.second;
auto* texture_type = var->Type()->UnwrapRef()->As<sem::StorageTexture>();
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 = TypeImageFormatToResourceBindingImageFormat(
texture_type->image_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<
std::string, utils::UniqueVector<SamplerTexturePair>>>();
auto& sem = program_->Sem();
for (auto* node : program_->ASTNodes().Objects()) {
auto* c = node->As<ast::CallExpression>();
if (!c) {
continue;
}
auto* call = sem.Get(c);
if (!call) {
continue;
}
auto* i = call->Target()->As<sem::Intrinsic>();
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<Symbol> entry_points;
if (call_func->IsEntryPoint()) {
entry_points = {call_func->symbol};
} else {
entry_points = sem.Get(call_func)->AncestorEntryPoints();
}
if (entry_points.empty()) {
continue;
}
auto* t = c->args[texture_index];
auto* s = c->args[sampler_index];
GetOriginatingResources(
std::array<const ast::Expression*, 2>{t, s},
[&](std::array<const sem::GlobalVariable*, 2> 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);
(*sampler_targets_)[ep_name].add(
{sampler_binding_point, texture_binding_point});
}
});
}
}
template <size_t N, typename F>
void Inspector::GetOriginatingResources(
std::array<const ast::Expression*, N> 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<const sem::GlobalVariable*, N> globals{};
std::array<const sem::Parameter*, N> parameters{};
utils::UniqueVector<const ast::CallExpression*> callsites;
for (size_t i = 0; i < N; i++) {
auto*& expr = exprs[i];
// Resolve each of the expressions
while (true) {
if (auto* user = sem.Get<sem::VariableUser>(expr)) {
auto* var = user->Variable();
if (auto* global = tint::As<sem::GlobalVariable>(var)) {
// Found the global resource declaration.
globals[i] = global;
break; // Done with this expression.
}
if (auto* local = tint::As<sem::LocalVariable>(var)) {
// Chase the variable
expr = local->Declaration()->constructor;
if (!expr) {
TINT_ICE(Inspector, diagnostics_)
<< "resource variable had no initializer";
return;
}
continue; // Continue chasing the expression in this function
}
if (auto* param = tint::As<sem::Parameter>(var)) {
// Gather each of the callers of this function
auto* func = tint::As<sem::Function>(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_expr : func->CallSites()) {
callsites.add(call_expr);
}
// Need to evaluate each function call with the group of
// expressions, so move on to the next expression.
parameters[i] = param;
break;
}
TINT_ICE(Inspector, diagnostics_)
<< "unexpected variable type " << var->TypeInfo().name;
}
if (auto* unary = tint::As<ast::UnaryOpExpression>(expr)) {
switch (unary->op) {
case ast::UnaryOp::kAddressOf:
case ast::UnaryOp::kIndirection:
// `*` and `&` are the only valid unary ops for a resource type,
// and must be balanced in order for the program to have passed
// validation. Just skip past these.
expr = unary->expr;
continue;
default: {
TINT_ICE(Inspector, diagnostics_)
<< "unexpected unary op on resource: " << unary->op;
return;
}
}
}
TINT_ICE(Inspector, diagnostics_)
<< "cannot resolve originating resource with expression type "
<< expr->TypeInfo().name;
return;
}
}
if (callsites.size()) {
for (auto* call_expr : callsites) {
// Make a copy of the expressions for this callsite
std::array<const ast::Expression*, N> 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 inspector
} // namespace tint