dawn-cmake/src/tint/builtin_table.cc
Ben Clayton 41f8d2ad52 Use 'final' specifier on leaf classes
Tint makes heavy use of RTTI via virtual methods. Give the compiler the
opportunity to optimize away some of these virtuals.

Bug: tint:1383
Change-Id: I28edfaa0a05bb1a9c506c61c0084542c0aeb37f0
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/82745
Reviewed-by: James Price <jrprice@google.com>
Reviewed-by: David Neto <dneto@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
2022-03-07 18:37:46 +00:00

1170 lines
37 KiB
C++

// Copyright 2021 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/builtin_table.h"
#include <algorithm>
#include <limits>
#include <unordered_map>
#include <utility>
#include "src/tint/program_builder.h"
#include "src/tint/sem/atomic_type.h"
#include "src/tint/sem/depth_multisampled_texture_type.h"
#include "src/tint/sem/depth_texture_type.h"
#include "src/tint/sem/external_texture_type.h"
#include "src/tint/sem/multisampled_texture_type.h"
#include "src/tint/sem/pipeline_stage_set.h"
#include "src/tint/sem/sampled_texture_type.h"
#include "src/tint/sem/storage_texture_type.h"
#include "src/tint/utils/hash.h"
#include "src/tint/utils/map.h"
#include "src/tint/utils/math.h"
#include "src/tint/utils/scoped_assignment.h"
namespace tint {
namespace {
// Forward declarations
struct OverloadInfo;
class Matchers;
class NumberMatcher;
class TypeMatcher;
/// A special type that matches all TypeMatchers
class Any final : public Castable<Any, sem::Type> {
public:
Any() = default;
~Any() override = default;
// Stub implementations for sem::Type conformance.
size_t Hash() const override { return 0; }
bool Equals(const sem::Type&) const override { return false; }
std::string FriendlyName(const SymbolTable&) const override {
return "<any>";
}
};
/// Number is an 32 bit unsigned integer, which can be in one of three states:
/// * Invalid - Number has not been assigned a value
/// * Valid - a fixed integer value
/// * Any - matches any other non-invalid number
struct Number {
static const Number any;
static const Number invalid;
/// Constructed as a valid number with the value v
explicit Number(uint32_t v) : value_(v), state_(kValid) {}
/// @returns the value of the number
inline uint32_t Value() const { return value_; }
/// @returns the true if the number is valid
inline bool IsValid() const { return state_ == kValid; }
/// @returns the true if the number is any
inline bool IsAny() const { return state_ == kAny; }
/// Assignment operator.
/// The number becomes valid, with the value n
inline Number& operator=(uint32_t n) {
value_ = n;
state_ = kValid;
return *this;
}
private:
enum State {
kInvalid,
kValid,
kAny,
};
constexpr explicit Number(State state) : state_(state) {}
uint32_t value_ = 0;
State state_ = kInvalid;
};
const Number Number::any{Number::kAny};
const Number Number::invalid{Number::kInvalid};
/// ClosedState holds the state of the open / closed numbers and types.
/// Used by the MatchState.
class ClosedState {
public:
explicit ClosedState(ProgramBuilder& b) : builder(b) {}
/// If the type with index `idx` is open, then it is closed with type `ty` and
/// Type() returns true. If the type is closed, then `Type()` returns true iff
/// it is equal to `ty`.
bool Type(uint32_t idx, const sem::Type* ty) {
auto res = types_.emplace(idx, ty);
return res.second || res.first->second == ty;
}
/// If the number with index `idx` is open, then it is closed with number
/// `number` and Num() returns true. If the number is closed, then `Num()`
/// returns true iff it is equal to `ty`.
bool Num(uint32_t idx, Number number) {
auto res = numbers_.emplace(idx, number.Value());
return res.second || res.first->second == number.Value();
}
/// Type returns the closed type with index `idx`.
/// An ICE is raised if the type is not closed.
const sem::Type* Type(uint32_t idx) const {
auto it = types_.find(idx);
if (it == types_.end()) {
TINT_ICE(Resolver, builder.Diagnostics())
<< "type with index " << idx << " is not closed";
return nullptr;
}
TINT_ASSERT(Resolver, it != types_.end());
return it->second;
}
/// Type returns the number type with index `idx`.
/// An ICE is raised if the number is not closed.
Number Num(uint32_t idx) const {
auto it = numbers_.find(idx);
if (it == numbers_.end()) {
TINT_ICE(Resolver, builder.Diagnostics())
<< "number with index " << idx << " is not closed";
return Number::invalid;
}
return Number(it->second);
}
private:
ProgramBuilder& builder;
std::unordered_map<uint32_t, const sem::Type*> types_;
std::unordered_map<uint32_t, uint32_t> numbers_;
};
/// Index type used for matcher indices
using MatcherIndex = uint8_t;
/// Index value used for open types / numbers that do not have a constraint
constexpr MatcherIndex kNoMatcher = std::numeric_limits<MatcherIndex>::max();
/// MatchState holds the state used to match an overload.
class MatchState {
public:
MatchState(ProgramBuilder& b,
ClosedState& c,
const Matchers& m,
const OverloadInfo& o,
MatcherIndex const* matcher_indices)
: builder(b),
closed(c),
matchers(m),
overload(o),
matcher_indices_(matcher_indices) {}
/// The program builder
ProgramBuilder& builder;
/// The open / closed types and numbers
ClosedState& closed;
/// The type and number matchers
Matchers const& matchers;
/// The current overload being evaluated
OverloadInfo const& overload;
/// Type uses the next TypeMatcher from the matcher indices to match the type
/// `ty`. If the type matches, the canonical expected type is returned. If the
/// type `ty` does not match, then nullptr is returned.
/// @note: The matcher indices are progressed on calling.
const sem::Type* Type(const sem::Type* ty);
/// Num uses the next NumMatcher from the matcher indices to match the number
/// `num`. If the number matches, the canonical expected number is returned.
/// If the number `num` does not match, then an invalid number is returned.
/// @note: The matcher indices are progressed on calling.
Number Num(Number num);
/// @returns a string representation of the next TypeMatcher from the matcher
/// indices.
/// @note: The matcher indices are progressed on calling.
std::string TypeName();
/// @returns a string representation of the next NumberMatcher from the
/// matcher indices.
/// @note: The matcher indices are progressed on calling.
std::string NumName();
private:
MatcherIndex const* matcher_indices_ = nullptr;
};
/// A TypeMatcher is the interface used to match an type used as part of an
/// overload's parameter or return type.
class TypeMatcher {
public:
/// Destructor
virtual ~TypeMatcher() = default;
/// Checks whether the given type matches the matcher rules, and returns the
/// expected, canonicalized type on success.
/// Match may close open types and numbers in state.
/// @param type the type to match
/// @returns the canonicalized type on match, otherwise nullptr
virtual const sem::Type* Match(MatchState& state,
const sem::Type* type) const = 0;
/// @return a string representation of the matcher. Used for printing error
/// messages when no overload is found.
virtual std::string String(MatchState& state) const = 0;
};
/// A NumberMatcher is the interface used to match a number or enumerator used
/// as part of an overload's parameter or return type.
class NumberMatcher {
public:
/// Destructor
virtual ~NumberMatcher() = default;
/// Checks whether the given number matches the matcher rules.
/// Match may close open numbers in state.
/// @param number the number to match
/// @returns true if the argument type is as expected.
virtual Number Match(MatchState& state, Number number) const = 0;
/// @return a string representation of the matcher. Used for printing error
/// messages when no overload is found.
virtual std::string String(MatchState& state) const = 0;
};
/// OpenTypeMatcher is a Matcher for an open type.
/// The OpenTypeMatcher will match against any type (so long as it is consistent
/// across all uses in the overload)
class OpenTypeMatcher : public TypeMatcher {
public:
/// Constructor
explicit OpenTypeMatcher(uint32_t index) : index_(index) {}
const sem::Type* Match(MatchState& state,
const sem::Type* type) const override {
if (type->Is<Any>()) {
return state.closed.Type(index_);
}
return state.closed.Type(index_, type) ? type : nullptr;
}
std::string String(MatchState& state) const override;
private:
uint32_t index_;
};
/// OpenNumberMatcher is a Matcher for an open number.
/// The OpenNumberMatcher will match against any number (so long as it is
/// consistent for the overload)
class OpenNumberMatcher : public NumberMatcher {
public:
explicit OpenNumberMatcher(uint32_t index) : index_(index) {}
Number Match(MatchState& state, Number number) const override {
if (number.IsAny()) {
return state.closed.Num(index_);
}
return state.closed.Num(index_, number) ? number : Number::invalid;
}
std::string String(MatchState& state) const override;
private:
uint32_t index_;
};
////////////////////////////////////////////////////////////////////////////////
// Binding functions for use in the generated builtin_table.inl
// TODO(bclayton): See if we can move more of this hand-rolled code to the
// template
////////////////////////////////////////////////////////////////////////////////
using TexelFormat = ast::TexelFormat;
using Access = ast::Access;
using StorageClass = ast::StorageClass;
using ParameterUsage = sem::ParameterUsage;
using PipelineStageSet = sem::PipelineStageSet;
using PipelineStage = ast::PipelineStage;
bool match_bool(const sem::Type* ty) {
return ty->IsAnyOf<Any, sem::Bool>();
}
const sem::Bool* build_bool(MatchState& state) {
return state.builder.create<sem::Bool>();
}
bool match_f32(const sem::Type* ty) {
return ty->IsAnyOf<Any, sem::F32>();
}
const sem::I32* build_i32(MatchState& state) {
return state.builder.create<sem::I32>();
}
bool match_i32(const sem::Type* ty) {
return ty->IsAnyOf<Any, sem::I32>();
}
const sem::U32* build_u32(MatchState& state) {
return state.builder.create<sem::U32>();
}
bool match_u32(const sem::Type* ty) {
return ty->IsAnyOf<Any, sem::U32>();
}
const sem::F32* build_f32(MatchState& state) {
return state.builder.create<sem::F32>();
}
bool match_vec(const sem::Type* ty, Number& N, const sem::Type*& T) {
if (ty->Is<Any>()) {
N = Number::any;
T = ty;
return true;
}
if (auto* v = ty->As<sem::Vector>()) {
N = v->Width();
T = v->type();
return true;
}
return false;
}
const sem::Vector* build_vec(MatchState& state, Number N, const sem::Type* el) {
return state.builder.create<sem::Vector>(el, N.Value());
}
template <int N>
bool match_vec(const sem::Type* ty, const sem::Type*& T) {
if (ty->Is<Any>()) {
T = ty;
return true;
}
if (auto* v = ty->As<sem::Vector>()) {
if (v->Width() == N) {
T = v->type();
return true;
}
}
return false;
}
bool match_vec2(const sem::Type* ty, const sem::Type*& T) {
return match_vec<2>(ty, T);
}
const sem::Vector* build_vec2(MatchState& state, const sem::Type* T) {
return build_vec(state, Number(2), T);
}
bool match_vec3(const sem::Type* ty, const sem::Type*& T) {
return match_vec<3>(ty, T);
}
const sem::Vector* build_vec3(MatchState& state, const sem::Type* T) {
return build_vec(state, Number(3), T);
}
bool match_vec4(const sem::Type* ty, const sem::Type*& T) {
return match_vec<4>(ty, T);
}
const sem::Vector* build_vec4(MatchState& state, const sem::Type* T) {
return build_vec(state, Number(4), T);
}
bool match_mat(const sem::Type* ty, Number& M, Number& N, const sem::Type*& T) {
if (ty->Is<Any>()) {
M = Number::any;
N = Number::any;
T = ty;
return true;
}
if (auto* m = ty->As<sem::Matrix>()) {
M = m->columns();
N = m->ColumnType()->Width();
T = m->type();
return true;
}
return false;
}
const sem::Matrix* build_mat(MatchState& state,
Number N,
Number M,
const sem::Type* T) {
auto* column_type = state.builder.create<sem::Vector>(T, M.Value());
return state.builder.create<sem::Matrix>(column_type, N.Value());
}
bool match_array(const sem::Type* ty, const sem::Type*& T) {
if (ty->Is<Any>()) {
T = ty;
return true;
}
if (auto* a = ty->As<sem::Array>()) {
if (a->Count() == 0) {
T = a->ElemType();
return true;
}
}
return false;
}
const sem::Array* build_array(MatchState& state, const sem::Type* el) {
return state.builder.create<sem::Array>(el,
/* count */ 0u,
/* align */ 0u,
/* size */ 0u,
/* stride */ 0u,
/* stride_implicit */ 0u);
}
bool match_ptr(const sem::Type* ty, Number& S, const sem::Type*& T, Number& A) {
if (ty->Is<Any>()) {
S = Number::any;
T = ty;
A = Number::any;
return true;
}
if (auto* p = ty->As<sem::Pointer>()) {
S = Number(static_cast<uint32_t>(p->StorageClass()));
T = p->StoreType();
A = Number(static_cast<uint32_t>(p->Access()));
return true;
}
return false;
}
const sem::Pointer* build_ptr(MatchState& state,
Number S,
const sem::Type* T,
Number& A) {
return state.builder.create<sem::Pointer>(
T, static_cast<ast::StorageClass>(S.Value()),
static_cast<ast::Access>(A.Value()));
}
bool match_atomic(const sem::Type* ty, const sem::Type*& T) {
if (ty->Is<Any>()) {
T = ty;
return true;
}
if (auto* a = ty->As<sem::Atomic>()) {
T = a->Type();
return true;
}
return false;
}
const sem::Atomic* build_atomic(MatchState& state, const sem::Type* T) {
return state.builder.create<sem::Atomic>(T);
}
bool match_sampler(const sem::Type* ty) {
if (ty->Is<Any>()) {
return true;
}
return ty->Is([](const sem::Sampler* s) {
return s->kind() == ast::SamplerKind::kSampler;
});
}
const sem::Sampler* build_sampler(MatchState& state) {
return state.builder.create<sem::Sampler>(ast::SamplerKind::kSampler);
}
bool match_sampler_comparison(const sem::Type* ty) {
if (ty->Is<Any>()) {
return true;
}
return ty->Is([](const sem::Sampler* s) {
return s->kind() == ast::SamplerKind::kComparisonSampler;
});
}
const sem::Sampler* build_sampler_comparison(MatchState& state) {
return state.builder.create<sem::Sampler>(
ast::SamplerKind::kComparisonSampler);
}
bool match_texture(const sem::Type* ty,
ast::TextureDimension dim,
const sem::Type*& T) {
if (ty->Is<Any>()) {
T = ty;
return true;
}
if (auto* v = ty->As<sem::SampledTexture>()) {
if (v->dim() == dim) {
T = v->type();
return true;
}
}
return false;
}
#define JOIN(a, b) a##b
#define DECLARE_SAMPLED_TEXTURE(suffix, dim) \
bool JOIN(match_texture_, suffix)(const sem::Type* ty, \
const sem::Type*& T) { \
return match_texture(ty, dim, T); \
} \
const sem::SampledTexture* JOIN(build_texture_, suffix)( \
MatchState & state, const sem::Type* T) { \
return state.builder.create<sem::SampledTexture>(dim, T); \
}
DECLARE_SAMPLED_TEXTURE(1d, ast::TextureDimension::k1d)
DECLARE_SAMPLED_TEXTURE(2d, ast::TextureDimension::k2d)
DECLARE_SAMPLED_TEXTURE(2d_array, ast::TextureDimension::k2dArray)
DECLARE_SAMPLED_TEXTURE(3d, ast::TextureDimension::k3d)
DECLARE_SAMPLED_TEXTURE(cube, ast::TextureDimension::kCube)
DECLARE_SAMPLED_TEXTURE(cube_array, ast::TextureDimension::kCubeArray)
#undef DECLARE_SAMPLED_TEXTURE
bool match_texture_multisampled(const sem::Type* ty,
ast::TextureDimension dim,
const sem::Type*& T) {
if (ty->Is<Any>()) {
T = ty;
return true;
}
if (auto* v = ty->As<sem::MultisampledTexture>()) {
if (v->dim() == dim) {
T = v->type();
return true;
}
}
return false;
}
#define DECLARE_MULTISAMPLED_TEXTURE(suffix, dim) \
bool JOIN(match_texture_multisampled_, suffix)(const sem::Type* ty, \
const sem::Type*& T) { \
return match_texture_multisampled(ty, dim, T); \
} \
const sem::MultisampledTexture* JOIN(build_texture_multisampled_, suffix)( \
MatchState & state, const sem::Type* T) { \
return state.builder.create<sem::MultisampledTexture>(dim, T); \
}
DECLARE_MULTISAMPLED_TEXTURE(2d, ast::TextureDimension::k2d)
#undef DECLARE_MULTISAMPLED_TEXTURE
bool match_texture_depth(const sem::Type* ty, ast::TextureDimension dim) {
if (ty->Is<Any>()) {
return true;
}
return ty->Is([&](const sem::DepthTexture* t) { return t->dim() == dim; });
}
#define DECLARE_DEPTH_TEXTURE(suffix, dim) \
bool JOIN(match_texture_depth_, suffix)(const sem::Type* ty) { \
return match_texture_depth(ty, dim); \
} \
const sem::DepthTexture* JOIN(build_texture_depth_, \
suffix)(MatchState & state) { \
return state.builder.create<sem::DepthTexture>(dim); \
}
DECLARE_DEPTH_TEXTURE(2d, ast::TextureDimension::k2d)
DECLARE_DEPTH_TEXTURE(2d_array, ast::TextureDimension::k2dArray)
DECLARE_DEPTH_TEXTURE(cube, ast::TextureDimension::kCube)
DECLARE_DEPTH_TEXTURE(cube_array, ast::TextureDimension::kCubeArray)
#undef DECLARE_DEPTH_TEXTURE
bool match_texture_depth_multisampled_2d(const sem::Type* ty) {
if (ty->Is<Any>()) {
return true;
}
return ty->Is([&](const sem::DepthMultisampledTexture* t) {
return t->dim() == ast::TextureDimension::k2d;
});
}
sem::DepthMultisampledTexture* build_texture_depth_multisampled_2d(
MatchState& state) {
return state.builder.create<sem::DepthMultisampledTexture>(
ast::TextureDimension::k2d);
}
bool match_texture_storage(const sem::Type* ty,
ast::TextureDimension dim,
Number& F,
Number& A) {
if (ty->Is<Any>()) {
F = Number::any;
A = Number::any;
return true;
}
if (auto* v = ty->As<sem::StorageTexture>()) {
if (v->dim() == dim) {
F = Number(static_cast<uint32_t>(v->texel_format()));
A = Number(static_cast<uint32_t>(v->access()));
return true;
}
}
return false;
}
#define DECLARE_STORAGE_TEXTURE(suffix, dim) \
bool JOIN(match_texture_storage_, suffix)(const sem::Type* ty, Number& F, \
Number& A) { \
return match_texture_storage(ty, dim, F, A); \
} \
const sem::StorageTexture* JOIN(build_texture_storage_, suffix)( \
MatchState & state, Number F, Number A) { \
auto format = static_cast<TexelFormat>(F.Value()); \
auto access = static_cast<Access>(A.Value()); \
auto* T = sem::StorageTexture::SubtypeFor(format, state.builder.Types()); \
return state.builder.create<sem::StorageTexture>(dim, format, access, T); \
}
DECLARE_STORAGE_TEXTURE(1d, ast::TextureDimension::k1d)
DECLARE_STORAGE_TEXTURE(2d, ast::TextureDimension::k2d)
DECLARE_STORAGE_TEXTURE(2d_array, ast::TextureDimension::k2dArray)
DECLARE_STORAGE_TEXTURE(3d, ast::TextureDimension::k3d)
#undef DECLARE_STORAGE_TEXTURE
bool match_texture_external(const sem::Type* ty) {
return ty->IsAnyOf<Any, sem::ExternalTexture>();
}
const sem::ExternalTexture* build_texture_external(MatchState& state) {
return state.builder.create<sem::ExternalTexture>();
}
// Builtin types starting with a _ prefix cannot be declared in WGSL, so they
// can only be used as return types. Because of this, they must only match Any,
// which is used as the return type matcher.
bool match_modf_result(const sem::Type* ty) {
return ty->Is<Any>();
}
bool match_modf_result_vec(const sem::Type* ty, Number& N) {
if (!ty->Is<Any>()) {
return false;
}
N = Number::any;
return true;
}
bool match_frexp_result(const sem::Type* ty) {
return ty->Is<Any>();
}
bool match_frexp_result_vec(const sem::Type* ty, Number& N) {
if (!ty->Is<Any>()) {
return false;
}
N = Number::any;
return true;
}
struct NameAndType {
std::string name;
sem::Type* type;
};
const sem::Struct* build_struct(
MatchState& state,
std::string name,
std::initializer_list<NameAndType> member_names_and_types) {
uint32_t offset = 0;
uint32_t max_align = 0;
sem::StructMemberList members;
for (auto& m : member_names_and_types) {
uint32_t align = m.type->Align();
uint32_t size = m.type->Size();
offset = utils::RoundUp(align, offset);
max_align = std::max(max_align, align);
members.emplace_back(state.builder.create<sem::StructMember>(
/* declaration */ nullptr,
/* name */ state.builder.Sym(m.name),
/* type */ m.type,
/* index */ static_cast<uint32_t>(members.size()),
/* offset */ offset,
/* align */ align,
/* size */ size));
offset += size;
}
uint32_t size_without_padding = offset;
uint32_t size_with_padding = utils::RoundUp(max_align, offset);
return state.builder.create<sem::Struct>(
/* declaration */ nullptr,
/* name */ state.builder.Sym(name),
/* members */ members,
/* align */ max_align,
/* size */ size_with_padding,
/* size_no_padding */ size_without_padding);
}
const sem::Struct* build_modf_result(MatchState& state) {
auto* f32 = state.builder.create<sem::F32>();
return build_struct(state, "__modf_result", {{"fract", f32}, {"whole", f32}});
}
const sem::Struct* build_modf_result_vec(MatchState& state, Number& n) {
auto* vec_f32 = state.builder.create<sem::Vector>(
state.builder.create<sem::F32>(), n.Value());
return build_struct(state, "__modf_result_vec" + std::to_string(n.Value()),
{{"fract", vec_f32}, {"whole", vec_f32}});
}
const sem::Struct* build_frexp_result(MatchState& state) {
auto* f32 = state.builder.create<sem::F32>();
auto* i32 = state.builder.create<sem::I32>();
return build_struct(state, "__frexp_result", {{"sig", f32}, {"exp", i32}});
}
const sem::Struct* build_frexp_result_vec(MatchState& state, Number& n) {
auto* vec_f32 = state.builder.create<sem::Vector>(
state.builder.create<sem::F32>(), n.Value());
auto* vec_i32 = state.builder.create<sem::Vector>(
state.builder.create<sem::I32>(), n.Value());
return build_struct(state, "__frexp_result_vec" + std::to_string(n.Value()),
{{"sig", vec_f32}, {"exp", vec_i32}});
}
/// ParameterInfo describes a parameter
struct ParameterInfo {
/// The parameter usage (parameter name in definition file)
const ParameterUsage usage;
/// Pointer to a list of indices that are used to match the parameter type.
/// The matcher indices index on Matchers::type and / or Matchers::number.
/// These indices are consumed by the matchers themselves.
/// The first index is always a TypeMatcher.
MatcherIndex const* const matcher_indices;
};
/// OpenTypeInfo describes an open type
struct OpenTypeInfo {
/// Name of the open type (e.g. 'T')
const char* name;
/// Optional type matcher constraint.
/// Either an index in Matchers::type, or kNoMatcher
const MatcherIndex matcher_index;
};
/// OpenNumberInfo describes an open number
struct OpenNumberInfo {
/// Name of the open number (e.g. 'N')
const char* name;
/// Optional number matcher constraint.
/// Either an index in Matchers::number, or kNoMatcher
const MatcherIndex matcher_index;
};
/// OverloadInfo describes a single function overload
struct OverloadInfo {
/// Total number of parameters for the overload
const uint8_t num_parameters;
/// Total number of open types for the overload
const uint8_t num_open_types;
/// Total number of open numbers for the overload
const uint8_t num_open_numbers;
/// Pointer to the first open type
OpenTypeInfo const* const open_types;
/// Pointer to the first open number
OpenNumberInfo const* const open_numbers;
/// Pointer to the first parameter
ParameterInfo const* const parameters;
/// Pointer to a list of matcher indices that index on Matchers::type and
/// Matchers::number, used to build the return type. If the function has no
/// return type then this is null
MatcherIndex const* const return_matcher_indices;
/// The pipeline stages that this overload can be used in
PipelineStageSet supported_stages;
/// True if the overload is marked as deprecated
bool is_deprecated;
};
/// BuiltinInfo describes a builtin function
struct BuiltinInfo {
/// Number of overloads of the builtin function
const uint8_t num_overloads;
/// Pointer to the start of the overloads for the function
OverloadInfo const* const overloads;
};
#include "builtin_table.inl"
/// BuiltinPrototype describes a fully matched builtin function, which is
/// used as a lookup for building unique sem::Builtin instances.
struct BuiltinPrototype {
/// Parameter describes a single parameter
struct Parameter {
/// Parameter type
const sem::Type* const type;
/// Parameter usage
ParameterUsage const usage = ParameterUsage::kNone;
};
/// Hasher provides a hash function for the BuiltinPrototype
struct Hasher {
/// @param i the BuiltinPrototype to create a hash for
/// @return the hash value
inline std::size_t operator()(const BuiltinPrototype& i) const {
size_t hash = utils::Hash(i.parameters.size());
for (auto& p : i.parameters) {
utils::HashCombine(&hash, p.type, p.usage);
}
return utils::Hash(hash, i.type, i.return_type, i.supported_stages,
i.is_deprecated);
}
};
sem::BuiltinType type = sem::BuiltinType::kNone;
std::vector<Parameter> parameters;
sem::Type const* return_type = nullptr;
PipelineStageSet supported_stages;
bool is_deprecated = false;
};
/// Equality operator for BuiltinPrototype
bool operator==(const BuiltinPrototype& a, const BuiltinPrototype& b) {
if (a.type != b.type || a.supported_stages != b.supported_stages ||
a.return_type != b.return_type || a.is_deprecated != b.is_deprecated ||
a.parameters.size() != b.parameters.size()) {
return false;
}
for (size_t i = 0; i < a.parameters.size(); i++) {
auto& pa = a.parameters[i];
auto& pb = b.parameters[i];
if (pa.type != pb.type || pa.usage != pb.usage) {
return false;
}
}
return true;
}
/// Impl is the private implementation of the BuiltinTable interface.
class Impl : public BuiltinTable {
public:
explicit Impl(ProgramBuilder& builder);
const sem::Builtin* Lookup(sem::BuiltinType builtin_type,
const std::vector<const sem::Type*>& args,
const Source& source) override;
private:
const sem::Builtin* Match(sem::BuiltinType builtin_type,
const OverloadInfo& overload,
const std::vector<const sem::Type*>& args,
int& match_score);
MatchState Match(ClosedState& closed,
const OverloadInfo& overload,
MatcherIndex const* matcher_indices) const;
void PrintOverload(std::ostream& ss,
const OverloadInfo& overload,
sem::BuiltinType builtin_type) const;
ProgramBuilder& builder;
Matchers matchers;
std::unordered_map<BuiltinPrototype, sem::Builtin*, BuiltinPrototype::Hasher>
builtins;
};
/// @return a string representing a call to a builtin with the given argument
/// types.
std::string CallSignature(ProgramBuilder& builder,
sem::BuiltinType builtin_type,
const std::vector<const sem::Type*>& args) {
std::stringstream ss;
ss << sem::str(builtin_type) << "(";
{
bool first = true;
for (auto* arg : args) {
if (!first) {
ss << ", ";
}
first = false;
ss << arg->UnwrapRef()->FriendlyName(builder.Symbols());
}
}
ss << ")";
return ss.str();
}
std::string OpenTypeMatcher::String(MatchState& state) const {
return state.overload.open_types[index_].name;
}
std::string OpenNumberMatcher::String(MatchState& state) const {
return state.overload.open_numbers[index_].name;
}
Impl::Impl(ProgramBuilder& b) : builder(b) {}
const sem::Builtin* Impl::Lookup(sem::BuiltinType builtin_type,
const std::vector<const sem::Type*>& args,
const Source& source) {
// Candidate holds information about a mismatched overload that could be what
// the user intended to call.
struct Candidate {
const OverloadInfo* overload;
int score;
};
// The list of failed matches that had promise.
std::vector<Candidate> candidates;
auto& builtin = kBuiltins[static_cast<uint32_t>(builtin_type)];
for (uint32_t o = 0; o < builtin.num_overloads; o++) {
int match_score = 1000;
auto& overload = builtin.overloads[o];
if (auto* match = Match(builtin_type, overload, args, match_score)) {
return match;
}
if (match_score > 0) {
candidates.emplace_back(Candidate{&overload, match_score});
}
}
// Sort the candidates with the most promising first
std::stable_sort(
candidates.begin(), candidates.end(),
[](const Candidate& a, const Candidate& b) { return a.score > b.score; });
// Generate an error message
std::stringstream ss;
ss << "no matching call to " << CallSignature(builder, builtin_type, args)
<< std::endl;
if (!candidates.empty()) {
ss << std::endl;
ss << candidates.size() << " candidate function"
<< (candidates.size() > 1 ? "s:" : ":") << std::endl;
for (auto& candidate : candidates) {
ss << " ";
PrintOverload(ss, *candidate.overload, builtin_type);
ss << std::endl;
}
}
builder.Diagnostics().add_error(diag::System::Resolver, ss.str(), source);
return nullptr;
}
const sem::Builtin* Impl::Match(sem::BuiltinType builtin_type,
const OverloadInfo& overload,
const std::vector<const sem::Type*>& args,
int& match_score) {
// Score wait for argument <-> parameter count matches / mismatches
constexpr int kScorePerParamArgMismatch = -1;
constexpr int kScorePerMatchedParam = 2;
constexpr int kScorePerMatchedOpenType = 1;
constexpr int kScorePerMatchedOpenNumber = 1;
auto num_parameters = overload.num_parameters;
auto num_arguments = static_cast<decltype(num_parameters)>(args.size());
bool overload_matched = true;
if (num_parameters != num_arguments) {
match_score +=
kScorePerParamArgMismatch * (std::max(num_parameters, num_arguments) -
std::min(num_parameters, num_arguments));
overload_matched = false;
}
ClosedState closed(builder);
std::vector<BuiltinPrototype::Parameter> parameters;
auto num_params = std::min(num_parameters, num_arguments);
for (uint32_t p = 0; p < num_params; p++) {
auto& parameter = overload.parameters[p];
auto* indices = parameter.matcher_indices;
auto* type = Match(closed, overload, indices).Type(args[p]->UnwrapRef());
if (type) {
parameters.emplace_back(
BuiltinPrototype::Parameter{type, parameter.usage});
match_score += kScorePerMatchedParam;
} else {
overload_matched = false;
}
}
if (overload_matched) {
// Check all constrained open types matched
for (uint32_t ot = 0; ot < overload.num_open_types; ot++) {
auto& open_type = overload.open_types[ot];
if (open_type.matcher_index != kNoMatcher) {
auto* index = &open_type.matcher_index;
if (Match(closed, overload, index).Type(closed.Type(ot))) {
match_score += kScorePerMatchedOpenType;
} else {
overload_matched = false;
}
}
}
}
if (overload_matched) {
// Check all constrained open numbers matched
for (uint32_t on = 0; on < overload.num_open_numbers; on++) {
auto& open_number = overload.open_numbers[on];
if (open_number.matcher_index != kNoMatcher) {
auto* index = &open_number.matcher_index;
if (Match(closed, overload, index).Num(closed.Num(on)).IsValid()) {
match_score += kScorePerMatchedOpenNumber;
} else {
overload_matched = false;
}
}
}
}
if (!overload_matched) {
return nullptr;
}
// Build the return type
const sem::Type* return_type = nullptr;
if (auto* indices = overload.return_matcher_indices) {
Any any;
return_type = Match(closed, overload, indices).Type(&any);
if (!return_type) {
std::stringstream ss;
PrintOverload(ss, overload, builtin_type);
TINT_ICE(Resolver, builder.Diagnostics())
<< "MatchState.Match() returned null for " << ss.str();
return nullptr;
}
} else {
return_type = builder.create<sem::Void>();
}
BuiltinPrototype builtin;
builtin.type = builtin_type;
builtin.return_type = return_type;
builtin.parameters = std::move(parameters);
builtin.supported_stages = overload.supported_stages;
builtin.is_deprecated = overload.is_deprecated;
// De-duplicate builtins that are identical.
return utils::GetOrCreate(builtins, builtin, [&] {
std::vector<sem::Parameter*> params;
params.reserve(builtin.parameters.size());
for (auto& p : builtin.parameters) {
params.emplace_back(builder.create<sem::Parameter>(
nullptr, static_cast<uint32_t>(params.size()), p.type,
ast::StorageClass::kNone, ast::Access::kUndefined, p.usage));
}
return builder.create<sem::Builtin>(
builtin.type, builtin.return_type, std::move(params),
builtin.supported_stages, builtin.is_deprecated);
});
}
MatchState Impl::Match(ClosedState& closed,
const OverloadInfo& overload,
MatcherIndex const* matcher_indices) const {
return MatchState(builder, closed, matchers, overload, matcher_indices);
}
void Impl::PrintOverload(std::ostream& ss,
const OverloadInfo& overload,
sem::BuiltinType builtin_type) const {
ClosedState closed(builder);
ss << builtin_type << "(";
for (uint32_t p = 0; p < overload.num_parameters; p++) {
auto& parameter = overload.parameters[p];
if (p > 0) {
ss << ", ";
}
if (parameter.usage != ParameterUsage::kNone) {
ss << sem::str(parameter.usage) << ": ";
}
auto* indices = parameter.matcher_indices;
ss << Match(closed, overload, indices).TypeName();
}
ss << ")";
if (overload.return_matcher_indices) {
ss << " -> ";
auto* indices = overload.return_matcher_indices;
ss << Match(closed, overload, indices).TypeName();
}
bool first = true;
auto separator = [&] {
ss << (first ? " where: " : ", ");
first = false;
};
for (uint32_t i = 0; i < overload.num_open_types; i++) {
auto& open_type = overload.open_types[i];
if (open_type.matcher_index != kNoMatcher) {
separator();
ss << open_type.name;
auto* index = &open_type.matcher_index;
ss << " is " << Match(closed, overload, index).TypeName();
}
}
for (uint32_t i = 0; i < overload.num_open_numbers; i++) {
auto& open_number = overload.open_numbers[i];
if (open_number.matcher_index != kNoMatcher) {
separator();
ss << open_number.name;
auto* index = &open_number.matcher_index;
ss << " is " << Match(closed, overload, index).NumName();
}
}
}
const sem::Type* MatchState::Type(const sem::Type* ty) {
MatcherIndex matcher_index = *matcher_indices_++;
auto* matcher = matchers.type[matcher_index];
return matcher->Match(*this, ty);
}
Number MatchState::Num(Number number) {
MatcherIndex matcher_index = *matcher_indices_++;
auto* matcher = matchers.number[matcher_index];
return matcher->Match(*this, number);
}
std::string MatchState::TypeName() {
MatcherIndex matcher_index = *matcher_indices_++;
auto* matcher = matchers.type[matcher_index];
return matcher->String(*this);
}
std::string MatchState::NumName() {
MatcherIndex matcher_index = *matcher_indices_++;
auto* matcher = matchers.number[matcher_index];
return matcher->String(*this);
}
} // namespace
std::unique_ptr<BuiltinTable> BuiltinTable::Create(ProgramBuilder& builder) {
return std::make_unique<Impl>(builder);
}
BuiltinTable::~BuiltinTable() = default;
/// TypeInfo for the Any type declared in the anonymous namespace above
TINT_INSTANTIATE_TYPEINFO(Any);
} // namespace tint