1170 lines
37 KiB
C++
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/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
|