blob: 7c0f13d93389cf6e05dd3e8af8a2075de395cb37 [file] [log] [blame]
// 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/intrinsic_table.h"
#include <algorithm>
#include <limits>
#include <unordered_map>
#include <utility>
#include "src/program_builder.h"
#include "src/sem/atomic_type.h"
#include "src/sem/depth_multisampled_texture_type.h"
#include "src/sem/depth_texture_type.h"
#include "src/sem/external_texture_type.h"
#include "src/sem/multisampled_texture_type.h"
#include "src/sem/pipeline_stage_set.h"
#include "src/sem/sampled_texture_type.h"
#include "src/sem/storage_texture_type.h"
#include "src/utils/hash.h"
#include "src/utils/map.h"
#include "src/utils/math.h"
#include "src/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 : public Castable<Any, sem::Type> {
public:
Any() = default;
~Any() override = default;
std::string type_name() const override { return "<any>"; }
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 intrinsic_table.inl
// TODO(bclayton): See if we can move more of this hand-rolled code to the
// template
////////////////////////////////////////////////////////////////////////////////
using TexelFormat = ast::ImageFormat;
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 */ 0,
/* align */ 0,
/* size */ 0,
/* stride */ 0,
/* stride_implicit */ 0);
}
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->image_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;
};
/// IntrinsicInfo describes an intrinsic function
struct IntrinsicInfo {
/// Number of overloads of the intrinsic function
const uint8_t num_overloads;
/// Pointer to the start of the overloads for the function
OverloadInfo const* const overloads;
};
#include "intrinsic_table.inl"
/// IntrinsicPrototype describes a fully matched intrinsic function, which is
/// used as a lookup for building unique sem::Intrinsic instances.
struct IntrinsicPrototype {
/// 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 IntrinsicPrototype
struct Hasher {
/// @param i the IntrinsicPrototype to create a hash for
/// @return the hash value
inline std::size_t operator()(const IntrinsicPrototype& 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::IntrinsicType type = sem::IntrinsicType::kNone;
std::vector<Parameter> parameters;
sem::Type const* return_type = nullptr;
PipelineStageSet supported_stages;
bool is_deprecated = false;
};
/// Equality operator for IntrinsicPrototype
bool operator==(const IntrinsicPrototype& a, const IntrinsicPrototype& 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 IntrinsicTable interface.
class Impl : public IntrinsicTable {
public:
explicit Impl(ProgramBuilder& builder);
const sem::Intrinsic* Lookup(sem::IntrinsicType intrinsic_type,
const std::vector<const sem::Type*>& args,
const Source& source) override;
private:
const sem::Intrinsic* Match(sem::IntrinsicType intrinsic_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::IntrinsicType intrinsic_type) const;
ProgramBuilder& builder;
Matchers matchers;
std::unordered_map<IntrinsicPrototype,
sem::Intrinsic*,
IntrinsicPrototype::Hasher>
intrinsics;
};
/// @return a string representing a call to an intrinsic with the given argument
/// types.
std::string CallSignature(ProgramBuilder& builder,
sem::IntrinsicType intrinsic_type,
const std::vector<const sem::Type*>& args) {
std::stringstream ss;
ss << sem::str(intrinsic_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::Intrinsic* Impl::Lookup(sem::IntrinsicType intrinsic_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& intrinsic = kIntrinsics[static_cast<uint32_t>(intrinsic_type)];
for (uint32_t o = 0; o < intrinsic.num_overloads; o++) {
int match_score = 1000;
auto& overload = intrinsic.overloads[o];
if (auto* match = Match(intrinsic_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, intrinsic_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, intrinsic_type);
ss << std::endl;
}
}
builder.Diagnostics().add_error(diag::System::Resolver, ss.str(), source);
return nullptr;
}
const sem::Intrinsic* Impl::Match(sem::IntrinsicType intrinsic_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<IntrinsicPrototype::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(
IntrinsicPrototype::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, intrinsic_type);
TINT_ICE(Resolver, builder.Diagnostics())
<< "MatchState.Match() returned null for " << ss.str();
return nullptr;
}
} else {
return_type = builder.create<sem::Void>();
}
IntrinsicPrototype intrinsic;
intrinsic.type = intrinsic_type;
intrinsic.return_type = return_type;
intrinsic.parameters = std::move(parameters);
intrinsic.supported_stages = overload.supported_stages;
intrinsic.is_deprecated = overload.is_deprecated;
// De-duplicate intrinsics that are identical.
return utils::GetOrCreate(intrinsics, intrinsic, [&] {
std::vector<sem::Parameter*> params;
params.reserve(intrinsic.parameters.size());
for (auto& p : intrinsic.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::Intrinsic>(
intrinsic.type, intrinsic.return_type, std::move(params),
intrinsic.supported_stages, intrinsic.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::IntrinsicType intrinsic_type) const {
ClosedState closed(builder);
ss << intrinsic_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<IntrinsicTable> IntrinsicTable::Create(
ProgramBuilder& builder) {
return std::make_unique<Impl>(builder);
}
IntrinsicTable::~IntrinsicTable() = default;
/// TypeInfo for the Any type declared in the anonymous namespace above
TINT_INSTANTIATE_TYPEINFO(Any);
} // namespace tint