mirror of
https://github.com/encounter/dawn-cmake.git
synced 2025-06-13 18:13:47 +00:00
This CL updates TypeFlags and TypeFlag to drop the Type prefix. Bug: tint:1718 Change-Id: Ia197c867e39102582ba3314b7b3f24d8bec89712 Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/113801 Reviewed-by: Ben Clayton <bclayton@google.com> Kokoro: Kokoro <noreply+kokoro@google.com> Commit-Queue: Dan Sinclair <dsinclair@chromium.org>
1885 lines
73 KiB
C++
1885 lines
73 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/resolver/intrinsic_table.h"
|
|
|
|
#include <algorithm>
|
|
#include <limits>
|
|
#include <utility>
|
|
|
|
#include "src/tint/ast/binary_expression.h"
|
|
#include "src/tint/program_builder.h"
|
|
#include "src/tint/sem/evaluation_stage.h"
|
|
#include "src/tint/sem/pipeline_stage_set.h"
|
|
#include "src/tint/sem/type_conversion.h"
|
|
#include "src/tint/sem/type_initializer.h"
|
|
#include "src/tint/type/abstract_float.h"
|
|
#include "src/tint/type/abstract_int.h"
|
|
#include "src/tint/type/abstract_numeric.h"
|
|
#include "src/tint/type/atomic.h"
|
|
#include "src/tint/type/depth_multisampled_texture.h"
|
|
#include "src/tint/type/depth_texture.h"
|
|
#include "src/tint/type/external_texture.h"
|
|
#include "src/tint/type/multisampled_texture.h"
|
|
#include "src/tint/type/sampled_texture.h"
|
|
#include "src/tint/type/storage_texture.h"
|
|
#include "src/tint/utils/hash.h"
|
|
#include "src/tint/utils/hashmap.h"
|
|
#include "src/tint/utils/math.h"
|
|
#include "src/tint/utils/scoped_assignment.h"
|
|
|
|
namespace tint::resolver {
|
|
namespace {
|
|
|
|
// Forward declarations
|
|
struct OverloadInfo;
|
|
class Matchers;
|
|
class NumberMatcher;
|
|
class TypeMatcher;
|
|
|
|
/// The utils::Vector `N` template argument value for arrays of parameters.
|
|
constexpr static const size_t kNumFixedParams = 8;
|
|
|
|
/// The utils::Vector `N` template argument value for arrays of overload candidates.
|
|
constexpr static const size_t kNumFixedCandidates = 8;
|
|
|
|
/// A special type that matches all TypeMatchers
|
|
class Any final : public Castable<Any, type::Type> {
|
|
public:
|
|
Any() : Base(type::Flags{}) {}
|
|
~Any() override = default;
|
|
|
|
// Stub implementations for type::Type conformance.
|
|
size_t Hash() const override { return 0; }
|
|
bool Equals(const type::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};
|
|
|
|
/// TemplateState holds the state of the template numbers and types.
|
|
/// Used by the MatchState.
|
|
class TemplateState {
|
|
public:
|
|
/// If the template type with index `idx` is undefined, then it is defined with the `ty` and
|
|
/// Type() returns `ty`.
|
|
/// If the template type is defined, and `ty` can be converted to the template type then the
|
|
/// template type is returned.
|
|
/// If the template type is defined, and the template type can be converted to `ty`, then the
|
|
/// template type is replaced with `ty`, and `ty` is returned.
|
|
/// If none of the above applies, then `ty` is a type mismatch for the template type, and
|
|
/// nullptr is returned.
|
|
const type::Type* Type(size_t idx, const type::Type* ty) {
|
|
if (idx >= types_.Length()) {
|
|
types_.Resize(idx + 1);
|
|
}
|
|
auto& t = types_[idx];
|
|
if (t == nullptr) {
|
|
t = ty;
|
|
return ty;
|
|
}
|
|
ty = type::Type::Common(utils::Vector{t, ty});
|
|
if (ty) {
|
|
t = ty;
|
|
}
|
|
return ty;
|
|
}
|
|
|
|
/// If the number with index `idx` is undefined, then it is defined with the number `number` and
|
|
/// Num() returns true. If the number is defined, then `Num()` returns true iff it is equal to
|
|
/// `ty`.
|
|
bool Num(size_t idx, Number number) {
|
|
if (idx >= numbers_.Length()) {
|
|
numbers_.Resize(idx + 1, Number::invalid);
|
|
}
|
|
auto& n = numbers_[idx];
|
|
if (!n.IsValid()) {
|
|
n = number.Value();
|
|
return true;
|
|
}
|
|
return n.Value() == number.Value();
|
|
}
|
|
|
|
/// Type returns the template type with index `idx`, or nullptr if the type was not defined.
|
|
const type::Type* Type(size_t idx) const {
|
|
if (idx >= types_.Length()) {
|
|
return nullptr;
|
|
}
|
|
return types_[idx];
|
|
}
|
|
|
|
/// SetType replaces the template type with index `idx` with type `ty`.
|
|
void SetType(size_t idx, const type::Type* ty) {
|
|
if (idx >= types_.Length()) {
|
|
types_.Resize(idx + 1);
|
|
}
|
|
types_[idx] = ty;
|
|
}
|
|
|
|
/// Type returns the number type with index `idx`.
|
|
Number Num(size_t idx) const {
|
|
if (idx >= numbers_.Length()) {
|
|
return Number::invalid;
|
|
}
|
|
return numbers_[idx];
|
|
}
|
|
|
|
private:
|
|
utils::Vector<const type::Type*, 4> types_;
|
|
utils::Vector<Number, 2> numbers_;
|
|
};
|
|
|
|
/// Index type used for matcher indices
|
|
using MatcherIndex = uint8_t;
|
|
|
|
/// Index value used for template 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,
|
|
TemplateState& t,
|
|
const Matchers& m,
|
|
const OverloadInfo* o,
|
|
MatcherIndex const* matcher_indices,
|
|
sem::EvaluationStage s)
|
|
: builder(b),
|
|
templates(t),
|
|
matchers(m),
|
|
overload(o),
|
|
earliest_eval_stage(s),
|
|
matcher_indices_(matcher_indices) {}
|
|
|
|
/// The program builder
|
|
ProgramBuilder& builder;
|
|
/// The template types and numbers
|
|
TemplateState& templates;
|
|
/// The type and number matchers
|
|
Matchers const& matchers;
|
|
/// The current overload being evaluated
|
|
OverloadInfo const* overload;
|
|
/// The earliest evaluation stage of the builtin call
|
|
sem::EvaluationStage earliest_eval_stage;
|
|
|
|
/// 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 type::Type* Type(const type::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 define and refine the template types and numbers in state.
|
|
/// @param type the type to match
|
|
/// @returns the canonicalized type on match, otherwise nullptr
|
|
virtual const type::Type* Match(MatchState& state, const type::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 define template 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;
|
|
};
|
|
|
|
/// TemplateTypeMatcher is a Matcher for a template type.
|
|
/// The TemplateTypeMatcher will initially match against any type, and then will only be further
|
|
/// constrained based on the conversion rules defined at https://www.w3.org/TR/WGSL/#conversion-rank
|
|
class TemplateTypeMatcher : public TypeMatcher {
|
|
public:
|
|
/// Constructor
|
|
explicit TemplateTypeMatcher(size_t index) : index_(index) {}
|
|
|
|
const type::Type* Match(MatchState& state, const type::Type* type) const override {
|
|
if (type->Is<Any>()) {
|
|
return state.templates.Type(index_);
|
|
}
|
|
if (auto* templates = state.templates.Type(index_, type)) {
|
|
return templates;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
std::string String(MatchState* state) const override;
|
|
|
|
private:
|
|
size_t index_;
|
|
};
|
|
|
|
/// TemplateNumberMatcher is a Matcher for a template number.
|
|
/// The TemplateNumberMatcher will match against any number (so long as it is
|
|
/// consistent for all uses in the overload)
|
|
class TemplateNumberMatcher : public NumberMatcher {
|
|
public:
|
|
explicit TemplateNumberMatcher(size_t index) : index_(index) {}
|
|
|
|
Number Match(MatchState& state, Number number) const override {
|
|
if (number.IsAny()) {
|
|
return state.templates.Num(index_);
|
|
}
|
|
return state.templates.Num(index_, number) ? number : Number::invalid;
|
|
}
|
|
|
|
std::string String(MatchState* state) const override;
|
|
|
|
private:
|
|
size_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 AddressSpace = ast::AddressSpace;
|
|
using ParameterUsage = sem::ParameterUsage;
|
|
using PipelineStage = ast::PipelineStage;
|
|
|
|
/// Unique flag bits for overloads
|
|
enum class OverloadFlag {
|
|
kIsBuiltin, // The overload is a builtin ('fn')
|
|
kIsOperator, // The overload is an operator ('op')
|
|
kIsInitializer, // The overload is a type initializer ('ctor')
|
|
kIsConverter, // The overload is a type converter ('conv')
|
|
kSupportsVertexPipeline, // The overload can be used in vertex shaders
|
|
kSupportsFragmentPipeline, // The overload can be used in fragment shaders
|
|
kSupportsComputePipeline, // The overload can be used in compute shaders
|
|
kIsDeprecated, // The overload is deprecated
|
|
};
|
|
|
|
// An enum set of OverloadFlag, used by OperatorInfo
|
|
using OverloadFlags = utils::EnumSet<OverloadFlag>;
|
|
|
|
bool match_bool(MatchState&, const type::Type* ty) {
|
|
return ty->IsAnyOf<Any, type::Bool>();
|
|
}
|
|
|
|
const type::AbstractFloat* build_fa(MatchState& state) {
|
|
return state.builder.create<type::AbstractFloat>();
|
|
}
|
|
|
|
bool match_fa(MatchState& state, const type::Type* ty) {
|
|
return (state.earliest_eval_stage == sem::EvaluationStage::kConstant) &&
|
|
ty->IsAnyOf<Any, type::AbstractNumeric>();
|
|
}
|
|
|
|
const type::AbstractInt* build_ia(MatchState& state) {
|
|
return state.builder.create<type::AbstractInt>();
|
|
}
|
|
|
|
bool match_ia(MatchState& state, const type::Type* ty) {
|
|
return (state.earliest_eval_stage == sem::EvaluationStage::kConstant) &&
|
|
ty->IsAnyOf<Any, type::AbstractInt>();
|
|
}
|
|
|
|
const type::Bool* build_bool(MatchState& state) {
|
|
return state.builder.create<type::Bool>();
|
|
}
|
|
|
|
const type::F16* build_f16(MatchState& state) {
|
|
return state.builder.create<type::F16>();
|
|
}
|
|
|
|
bool match_f16(MatchState&, const type::Type* ty) {
|
|
return ty->IsAnyOf<Any, type::F16, type::AbstractNumeric>();
|
|
}
|
|
|
|
const type::F32* build_f32(MatchState& state) {
|
|
return state.builder.create<type::F32>();
|
|
}
|
|
|
|
bool match_f32(MatchState&, const type::Type* ty) {
|
|
return ty->IsAnyOf<Any, type::F32, type::AbstractNumeric>();
|
|
}
|
|
|
|
const type::I32* build_i32(MatchState& state) {
|
|
return state.builder.create<type::I32>();
|
|
}
|
|
|
|
bool match_i32(MatchState&, const type::Type* ty) {
|
|
return ty->IsAnyOf<Any, type::I32, type::AbstractInt>();
|
|
}
|
|
|
|
const type::U32* build_u32(MatchState& state) {
|
|
return state.builder.create<type::U32>();
|
|
}
|
|
|
|
bool match_u32(MatchState&, const type::Type* ty) {
|
|
return ty->IsAnyOf<Any, type::U32, type::AbstractInt>();
|
|
}
|
|
|
|
bool match_vec(MatchState&, const type::Type* ty, Number& N, const type::Type*& T) {
|
|
if (ty->Is<Any>()) {
|
|
N = Number::any;
|
|
T = ty;
|
|
return true;
|
|
}
|
|
|
|
if (auto* v = ty->As<type::Vector>()) {
|
|
N = v->Width();
|
|
T = v->type();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
template <uint32_t N>
|
|
bool match_vec(MatchState&, const type::Type* ty, const type::Type*& T) {
|
|
if (ty->Is<Any>()) {
|
|
T = ty;
|
|
return true;
|
|
}
|
|
|
|
if (auto* v = ty->As<type::Vector>()) {
|
|
if (v->Width() == N) {
|
|
T = v->type();
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
const type::Vector* build_vec(MatchState& state, Number N, const type::Type* el) {
|
|
return state.builder.create<type::Vector>(el, N.Value());
|
|
}
|
|
|
|
template <uint32_t N>
|
|
const type::Vector* build_vec(MatchState& state, const type::Type* el) {
|
|
return state.builder.create<type::Vector>(el, N);
|
|
}
|
|
|
|
constexpr auto match_vec2 = match_vec<2>;
|
|
constexpr auto match_vec3 = match_vec<3>;
|
|
constexpr auto match_vec4 = match_vec<4>;
|
|
|
|
constexpr auto build_vec2 = build_vec<2>;
|
|
constexpr auto build_vec3 = build_vec<3>;
|
|
constexpr auto build_vec4 = build_vec<4>;
|
|
|
|
bool match_mat(MatchState&, const type::Type* ty, Number& M, Number& N, const type::Type*& T) {
|
|
if (ty->Is<Any>()) {
|
|
M = Number::any;
|
|
N = Number::any;
|
|
T = ty;
|
|
return true;
|
|
}
|
|
if (auto* m = ty->As<type::Matrix>()) {
|
|
M = m->columns();
|
|
N = m->ColumnType()->Width();
|
|
T = m->type();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
template <uint32_t C, uint32_t R>
|
|
bool match_mat(MatchState&, const type::Type* ty, const type::Type*& T) {
|
|
if (ty->Is<Any>()) {
|
|
T = ty;
|
|
return true;
|
|
}
|
|
if (auto* m = ty->As<type::Matrix>()) {
|
|
if (m->columns() == C && m->rows() == R) {
|
|
T = m->type();
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
const type::Matrix* build_mat(MatchState& state, Number C, Number R, const type::Type* T) {
|
|
auto* column_type = state.builder.create<type::Vector>(T, R.Value());
|
|
return state.builder.create<type::Matrix>(column_type, C.Value());
|
|
}
|
|
|
|
template <uint32_t C, uint32_t R>
|
|
const type::Matrix* build_mat(MatchState& state, const type::Type* T) {
|
|
auto* column_type = state.builder.create<type::Vector>(T, R);
|
|
return state.builder.create<type::Matrix>(column_type, C);
|
|
}
|
|
|
|
constexpr auto build_mat2x2 = build_mat<2, 2>;
|
|
constexpr auto build_mat2x3 = build_mat<2, 3>;
|
|
constexpr auto build_mat2x4 = build_mat<2, 4>;
|
|
constexpr auto build_mat3x2 = build_mat<3, 2>;
|
|
constexpr auto build_mat3x3 = build_mat<3, 3>;
|
|
constexpr auto build_mat3x4 = build_mat<3, 4>;
|
|
constexpr auto build_mat4x2 = build_mat<4, 2>;
|
|
constexpr auto build_mat4x3 = build_mat<4, 3>;
|
|
constexpr auto build_mat4x4 = build_mat<4, 4>;
|
|
|
|
constexpr auto match_mat2x2 = match_mat<2, 2>;
|
|
constexpr auto match_mat2x3 = match_mat<2, 3>;
|
|
constexpr auto match_mat2x4 = match_mat<2, 4>;
|
|
constexpr auto match_mat3x2 = match_mat<3, 2>;
|
|
constexpr auto match_mat3x3 = match_mat<3, 3>;
|
|
constexpr auto match_mat3x4 = match_mat<3, 4>;
|
|
constexpr auto match_mat4x2 = match_mat<4, 2>;
|
|
constexpr auto match_mat4x3 = match_mat<4, 3>;
|
|
constexpr auto match_mat4x4 = match_mat<4, 4>;
|
|
|
|
bool match_array(MatchState&, const type::Type* ty, const type::Type*& T) {
|
|
if (ty->Is<Any>()) {
|
|
T = ty;
|
|
return true;
|
|
}
|
|
|
|
if (auto* a = ty->As<type::Array>()) {
|
|
if (a->Count()->Is<type::RuntimeArrayCount>()) {
|
|
T = a->ElemType();
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
const type::Array* build_array(MatchState& state, const type::Type* el) {
|
|
return state.builder.create<type::Array>(
|
|
el,
|
|
/* count */ state.builder.create<type::RuntimeArrayCount>(),
|
|
/* align */ 0u,
|
|
/* size */ 0u,
|
|
/* stride */ 0u,
|
|
/* stride_implicit */ 0u);
|
|
}
|
|
|
|
bool match_ptr(MatchState&, const type::Type* ty, Number& S, const type::Type*& T, Number& A) {
|
|
if (ty->Is<Any>()) {
|
|
S = Number::any;
|
|
T = ty;
|
|
A = Number::any;
|
|
return true;
|
|
}
|
|
|
|
if (auto* p = ty->As<type::Pointer>()) {
|
|
S = Number(static_cast<uint32_t>(p->AddressSpace()));
|
|
T = p->StoreType();
|
|
A = Number(static_cast<uint32_t>(p->Access()));
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
const type::Pointer* build_ptr(MatchState& state, Number S, const type::Type* T, Number& A) {
|
|
return state.builder.create<type::Pointer>(T, static_cast<ast::AddressSpace>(S.Value()),
|
|
static_cast<ast::Access>(A.Value()));
|
|
}
|
|
|
|
bool match_atomic(MatchState&, const type::Type* ty, const type::Type*& T) {
|
|
if (ty->Is<Any>()) {
|
|
T = ty;
|
|
return true;
|
|
}
|
|
|
|
if (auto* a = ty->As<type::Atomic>()) {
|
|
T = a->Type();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
const type::Atomic* build_atomic(MatchState& state, const type::Type* T) {
|
|
return state.builder.create<type::Atomic>(T);
|
|
}
|
|
|
|
bool match_sampler(MatchState&, const type::Type* ty) {
|
|
if (ty->Is<Any>()) {
|
|
return true;
|
|
}
|
|
return ty->Is([](const type::Sampler* s) { return s->kind() == ast::SamplerKind::kSampler; });
|
|
}
|
|
|
|
const type::Sampler* build_sampler(MatchState& state) {
|
|
return state.builder.create<type::Sampler>(ast::SamplerKind::kSampler);
|
|
}
|
|
|
|
bool match_sampler_comparison(MatchState&, const type::Type* ty) {
|
|
if (ty->Is<Any>()) {
|
|
return true;
|
|
}
|
|
return ty->Is(
|
|
[](const type::Sampler* s) { return s->kind() == ast::SamplerKind::kComparisonSampler; });
|
|
}
|
|
|
|
const type::Sampler* build_sampler_comparison(MatchState& state) {
|
|
return state.builder.create<type::Sampler>(ast::SamplerKind::kComparisonSampler);
|
|
}
|
|
|
|
bool match_texture(MatchState&,
|
|
const type::Type* ty,
|
|
ast::TextureDimension dim,
|
|
const type::Type*& T) {
|
|
if (ty->Is<Any>()) {
|
|
T = ty;
|
|
return true;
|
|
}
|
|
if (auto* v = ty->As<type::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)(MatchState & state, const type::Type* ty, \
|
|
const type::Type*& T) { \
|
|
return match_texture(state, ty, dim, T); \
|
|
} \
|
|
const type::SampledTexture* JOIN(build_texture_, suffix)(MatchState & state, \
|
|
const type::Type* T) { \
|
|
return state.builder.create<type::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(MatchState&,
|
|
const type::Type* ty,
|
|
ast::TextureDimension dim,
|
|
const type::Type*& T) {
|
|
if (ty->Is<Any>()) {
|
|
T = ty;
|
|
return true;
|
|
}
|
|
if (auto* v = ty->As<type::MultisampledTexture>()) {
|
|
if (v->dim() == dim) {
|
|
T = v->type();
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#define DECLARE_MULTISAMPLED_TEXTURE(suffix, dim) \
|
|
bool JOIN(match_texture_multisampled_, suffix)(MatchState & state, const type::Type* ty, \
|
|
const type::Type*& T) { \
|
|
return match_texture_multisampled(state, ty, dim, T); \
|
|
} \
|
|
const type::MultisampledTexture* JOIN(build_texture_multisampled_, suffix)( \
|
|
MatchState & state, const type::Type* T) { \
|
|
return state.builder.create<type::MultisampledTexture>(dim, T); \
|
|
}
|
|
|
|
DECLARE_MULTISAMPLED_TEXTURE(2d, ast::TextureDimension::k2d)
|
|
#undef DECLARE_MULTISAMPLED_TEXTURE
|
|
|
|
bool match_texture_depth(MatchState&, const type::Type* ty, ast::TextureDimension dim) {
|
|
if (ty->Is<Any>()) {
|
|
return true;
|
|
}
|
|
return ty->Is([&](const type::DepthTexture* t) { return t->dim() == dim; });
|
|
}
|
|
|
|
#define DECLARE_DEPTH_TEXTURE(suffix, dim) \
|
|
bool JOIN(match_texture_depth_, suffix)(MatchState & state, const type::Type* ty) { \
|
|
return match_texture_depth(state, ty, dim); \
|
|
} \
|
|
const type::DepthTexture* JOIN(build_texture_depth_, suffix)(MatchState & state) { \
|
|
return state.builder.create<type::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(MatchState&, const type::Type* ty) {
|
|
if (ty->Is<Any>()) {
|
|
return true;
|
|
}
|
|
return ty->Is([&](const type::DepthMultisampledTexture* t) {
|
|
return t->dim() == ast::TextureDimension::k2d;
|
|
});
|
|
}
|
|
|
|
type::DepthMultisampledTexture* build_texture_depth_multisampled_2d(MatchState& state) {
|
|
return state.builder.create<type::DepthMultisampledTexture>(ast::TextureDimension::k2d);
|
|
}
|
|
|
|
bool match_texture_storage(MatchState&,
|
|
const type::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<type::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)(MatchState & state, const type::Type* ty, Number& F, \
|
|
Number& A) { \
|
|
return match_texture_storage(state, ty, dim, F, A); \
|
|
} \
|
|
const type::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 = type::StorageTexture::SubtypeFor(format, state.builder.Types()); \
|
|
return state.builder.create<type::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(MatchState&, const type::Type* ty) {
|
|
return ty->IsAnyOf<Any, type::ExternalTexture>();
|
|
}
|
|
|
|
const type::ExternalTexture* build_texture_external(MatchState& state) {
|
|
return state.builder.create<type::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(MatchState&, const type::Type* ty, const type::Type*& T) {
|
|
if (!ty->Is<Any>()) {
|
|
return false;
|
|
}
|
|
T = ty;
|
|
return true;
|
|
}
|
|
bool match_modf_result_vec(MatchState&, const type::Type* ty, Number& N, const type::Type*& T) {
|
|
if (!ty->Is<Any>()) {
|
|
return false;
|
|
}
|
|
N = Number::any;
|
|
T = ty;
|
|
return true;
|
|
}
|
|
bool match_frexp_result(MatchState&, const type::Type* ty, const type::Type*& T) {
|
|
if (!ty->Is<Any>()) {
|
|
return false;
|
|
}
|
|
T = ty;
|
|
return true;
|
|
}
|
|
bool match_frexp_result_vec(MatchState&, const type::Type* ty, Number& N, const type::Type*& T) {
|
|
if (!ty->Is<Any>()) {
|
|
return false;
|
|
}
|
|
N = Number::any;
|
|
T = ty;
|
|
return true;
|
|
}
|
|
|
|
bool match_atomic_compare_exchange_result(MatchState&, const type::Type* ty, const type::Type*& T) {
|
|
if (ty->Is<Any>()) {
|
|
T = ty;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
struct NameAndType {
|
|
std::string name;
|
|
const type::Type* type;
|
|
};
|
|
sem::Struct* build_struct(ProgramBuilder& b,
|
|
std::string name,
|
|
std::initializer_list<NameAndType> member_names_and_types) {
|
|
uint32_t offset = 0;
|
|
uint32_t max_align = 0;
|
|
utils::Vector<const sem::StructMember*, 4> members;
|
|
for (auto& m : member_names_and_types) {
|
|
uint32_t align = std::max<uint32_t>(m.type->Align(), 1);
|
|
uint32_t size = m.type->Size();
|
|
offset = utils::RoundUp(align, offset);
|
|
max_align = std::max(max_align, align);
|
|
members.Push(b.create<sem::StructMember>(
|
|
/* declaration */ nullptr,
|
|
/* source */ Source{},
|
|
/* name */ b.Sym(m.name),
|
|
/* type */ m.type,
|
|
/* index */ static_cast<uint32_t>(members.Length()),
|
|
/* offset */ offset,
|
|
/* align */ align,
|
|
/* size */ size,
|
|
/* location */ std::nullopt));
|
|
offset += size;
|
|
}
|
|
uint32_t size_without_padding = offset;
|
|
uint32_t size_with_padding = utils::RoundUp(max_align, offset);
|
|
return b.create<sem::Struct>(
|
|
/* declaration */ nullptr,
|
|
/* source */ Source{},
|
|
/* name */ b.Sym(name),
|
|
/* members */ std::move(members),
|
|
/* align */ max_align,
|
|
/* size */ size_with_padding,
|
|
/* size_no_padding */ size_without_padding);
|
|
}
|
|
|
|
const sem::Struct* build_modf_result(MatchState& state, const type::Type* el) {
|
|
auto build_f32 = [&] {
|
|
auto* ty = state.builder.create<type::F32>();
|
|
return build_struct(state.builder, "__modf_result_f32", {{"fract", ty}, {"whole", ty}});
|
|
};
|
|
auto build_f16 = [&] {
|
|
auto* ty = state.builder.create<type::F16>();
|
|
return build_struct(state.builder, "__modf_result_f16", {{"fract", ty}, {"whole", ty}});
|
|
};
|
|
|
|
return Switch(
|
|
el, //
|
|
[&](const type::F32*) { return build_f32(); }, //
|
|
[&](const type::F16*) { return build_f16(); }, //
|
|
[&](const type::AbstractFloat*) {
|
|
auto* abstract = build_struct(state.builder, "__modf_result_abstract",
|
|
{{"fract", el}, {"whole", el}});
|
|
abstract->SetConcreteTypes(utils::Vector{build_f32(), build_f16()});
|
|
return abstract;
|
|
},
|
|
[&](Default) {
|
|
TINT_ICE(Resolver, state.builder.Diagnostics())
|
|
<< "unhandled modf type: " << state.builder.FriendlyName(el);
|
|
return nullptr;
|
|
});
|
|
}
|
|
|
|
const sem::Struct* build_modf_result_vec(MatchState& state, Number& n, const type::Type* el) {
|
|
auto prefix = "__modf_result_vec" + std::to_string(n.Value());
|
|
auto build_f32 = [&] {
|
|
auto* vec =
|
|
state.builder.create<type::Vector>(state.builder.create<type::F32>(), n.Value());
|
|
return build_struct(state.builder, prefix + "_f32", {{"fract", vec}, {"whole", vec}});
|
|
};
|
|
auto build_f16 = [&] {
|
|
auto* vec =
|
|
state.builder.create<type::Vector>(state.builder.create<type::F16>(), n.Value());
|
|
return build_struct(state.builder, prefix + "_f16", {{"fract", vec}, {"whole", vec}});
|
|
};
|
|
|
|
return Switch(
|
|
el, //
|
|
[&](const type::F32*) { return build_f32(); }, //
|
|
[&](const type::F16*) { return build_f16(); }, //
|
|
[&](const type::AbstractFloat*) {
|
|
auto* vec = state.builder.create<type::Vector>(el, n.Value());
|
|
auto* abstract =
|
|
build_struct(state.builder, prefix + "_abstract", {{"fract", vec}, {"whole", vec}});
|
|
abstract->SetConcreteTypes(utils::Vector{build_f32(), build_f16()});
|
|
return abstract;
|
|
},
|
|
[&](Default) {
|
|
TINT_ICE(Resolver, state.builder.Diagnostics())
|
|
<< "unhandled modf type: " << state.builder.FriendlyName(el);
|
|
return nullptr;
|
|
});
|
|
}
|
|
|
|
const sem::Struct* build_frexp_result(MatchState& state, const type::Type* el) {
|
|
auto build_f32 = [&] {
|
|
auto* f = state.builder.create<type::F32>();
|
|
auto* i = state.builder.create<type::I32>();
|
|
return build_struct(state.builder, "__frexp_result_f32", {{"fract", f}, {"exp", i}});
|
|
};
|
|
auto build_f16 = [&] {
|
|
auto* f = state.builder.create<type::F16>();
|
|
auto* i = state.builder.create<type::I32>();
|
|
return build_struct(state.builder, "__frexp_result_f16", {{"fract", f}, {"exp", i}});
|
|
};
|
|
|
|
return Switch(
|
|
el, //
|
|
[&](const type::F32*) { return build_f32(); }, //
|
|
[&](const type::F16*) { return build_f16(); }, //
|
|
[&](const type::AbstractFloat*) {
|
|
auto* i = state.builder.create<type::AbstractInt>();
|
|
auto* abstract =
|
|
build_struct(state.builder, "__frexp_result_abstract", {{"fract", el}, {"exp", i}});
|
|
abstract->SetConcreteTypes(utils::Vector{build_f32(), build_f16()});
|
|
return abstract;
|
|
},
|
|
[&](Default) {
|
|
TINT_ICE(Resolver, state.builder.Diagnostics())
|
|
<< "unhandled frexp type: " << state.builder.FriendlyName(el);
|
|
return nullptr;
|
|
});
|
|
}
|
|
|
|
const sem::Struct* build_frexp_result_vec(MatchState& state, Number& n, const type::Type* el) {
|
|
auto prefix = "__frexp_result_vec" + std::to_string(n.Value());
|
|
auto build_f32 = [&] {
|
|
auto* f = state.builder.create<type::Vector>(state.builder.create<type::F32>(), n.Value());
|
|
auto* e = state.builder.create<type::Vector>(state.builder.create<type::I32>(), n.Value());
|
|
return build_struct(state.builder, prefix + "_f32", {{"fract", f}, {"exp", e}});
|
|
};
|
|
auto build_f16 = [&] {
|
|
auto* f = state.builder.create<type::Vector>(state.builder.create<type::F16>(), n.Value());
|
|
auto* e = state.builder.create<type::Vector>(state.builder.create<type::I32>(), n.Value());
|
|
return build_struct(state.builder, prefix + "_f16", {{"fract", f}, {"exp", e}});
|
|
};
|
|
|
|
return Switch(
|
|
el, //
|
|
[&](const type::F32*) { return build_f32(); }, //
|
|
[&](const type::F16*) { return build_f16(); }, //
|
|
[&](const type::AbstractFloat*) {
|
|
auto* f = state.builder.create<type::Vector>(el, n.Value());
|
|
auto* e = state.builder.create<type::Vector>(state.builder.create<type::AbstractInt>(),
|
|
n.Value());
|
|
auto* abstract =
|
|
build_struct(state.builder, prefix + "_abstract", {{"fract", f}, {"exp", e}});
|
|
abstract->SetConcreteTypes(utils::Vector{build_f32(), build_f16()});
|
|
return abstract;
|
|
},
|
|
[&](Default) {
|
|
TINT_ICE(Resolver, state.builder.Diagnostics())
|
|
<< "unhandled frexp type: " << state.builder.FriendlyName(el);
|
|
return nullptr;
|
|
});
|
|
}
|
|
|
|
const sem::Struct* build_atomic_compare_exchange_result(MatchState& state, const type::Type* ty) {
|
|
return build_struct(
|
|
state.builder,
|
|
"__atomic_compare_exchange_result" + ty->FriendlyName(state.builder.Symbols()),
|
|
{{"old_value", const_cast<type::Type*>(ty)},
|
|
{"exchanged", state.builder.create<type::Bool>()}});
|
|
}
|
|
|
|
/// 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;
|
|
};
|
|
|
|
/// TemplateTypeInfo describes an template type
|
|
struct TemplateTypeInfo {
|
|
/// Name of the template type (e.g. 'T')
|
|
const char* name;
|
|
/// Optional type matcher constraint.
|
|
/// Either an index in Matchers::type, or kNoMatcher
|
|
const MatcherIndex matcher_index;
|
|
};
|
|
|
|
/// TemplateNumberInfo describes a template number
|
|
struct TemplateNumberInfo {
|
|
/// Name of the template 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 template types for the overload
|
|
const uint8_t num_template_types;
|
|
/// Total number of template numbers for the overload
|
|
const uint8_t num_template_numbers;
|
|
/// Pointer to the first template type
|
|
TemplateTypeInfo const* const template_types;
|
|
/// Pointer to the first template number
|
|
TemplateNumberInfo const* const template_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 flags for the overload
|
|
OverloadFlags flags;
|
|
/// The function used to evaluate the overload at shader-creation time.
|
|
ConstEval::Function const const_eval_fn;
|
|
};
|
|
|
|
/// IntrinsicInfo describes a builtin function or operator overload
|
|
struct IntrinsicInfo {
|
|
/// Number of overloads of the intrinsic
|
|
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.
|
|
struct IntrinsicPrototype {
|
|
/// Parameter describes a single parameter
|
|
struct Parameter {
|
|
/// Parameter type
|
|
const type::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.Length());
|
|
for (auto& p : i.parameters) {
|
|
hash = utils::HashCombine(hash, p.type, p.usage);
|
|
}
|
|
return utils::Hash(hash, i.overload, i.return_type);
|
|
}
|
|
};
|
|
|
|
const OverloadInfo* overload = nullptr;
|
|
type::Type const* return_type = nullptr;
|
|
utils::Vector<Parameter, kNumFixedParams> parameters;
|
|
};
|
|
|
|
/// Equality operator for IntrinsicPrototype
|
|
bool operator==(const IntrinsicPrototype& a, const IntrinsicPrototype& b) {
|
|
if (a.overload != b.overload || a.return_type != b.return_type ||
|
|
a.parameters.Length() != b.parameters.Length()) {
|
|
return false;
|
|
}
|
|
for (size_t i = 0; i < a.parameters.Length(); 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);
|
|
|
|
Builtin Lookup(sem::BuiltinType builtin_type,
|
|
utils::VectorRef<const type::Type*> args,
|
|
sem::EvaluationStage earliest_eval_stage,
|
|
const Source& source) override;
|
|
|
|
UnaryOperator Lookup(ast::UnaryOp op,
|
|
const type::Type* arg,
|
|
sem::EvaluationStage earliest_eval_stage,
|
|
const Source& source) override;
|
|
|
|
BinaryOperator Lookup(ast::BinaryOp op,
|
|
const type::Type* lhs,
|
|
const type::Type* rhs,
|
|
sem::EvaluationStage earliest_eval_stage,
|
|
const Source& source,
|
|
bool is_compound) override;
|
|
|
|
InitOrConv Lookup(InitConvIntrinsic type,
|
|
const type::Type* template_arg,
|
|
utils::VectorRef<const type::Type*> args,
|
|
sem::EvaluationStage earliest_eval_stage,
|
|
const Source& source) override;
|
|
|
|
private:
|
|
/// Candidate holds information about an overload evaluated for resolution.
|
|
struct Candidate {
|
|
/// The candidate overload
|
|
const OverloadInfo* overload;
|
|
/// The template types and numbers
|
|
TemplateState templates;
|
|
/// The parameter types for the candidate overload
|
|
utils::Vector<IntrinsicPrototype::Parameter, kNumFixedParams> parameters;
|
|
/// The match-score of the candidate overload.
|
|
/// A score of zero indicates an exact match.
|
|
/// Non-zero scores are used for diagnostics when no overload matches.
|
|
/// Lower scores are displayed first (top-most).
|
|
size_t score;
|
|
};
|
|
|
|
/// A list of candidates
|
|
using Candidates = utils::Vector<Candidate, kNumFixedCandidates>;
|
|
|
|
/// Callback function when no overloads match.
|
|
using OnNoMatch = std::function<void(utils::VectorRef<Candidate>)>;
|
|
|
|
/// Sorts the candidates based on their score, with the lowest (best-ranking) scores first.
|
|
static inline void SortCandidates(Candidates& candidates) {
|
|
std::stable_sort(candidates.begin(), candidates.end(),
|
|
[&](const Candidate& a, const Candidate& b) { return a.score < b.score; });
|
|
}
|
|
|
|
/// Attempts to find a single intrinsic overload that matches the provided argument types.
|
|
/// @param intrinsic the intrinsic being called
|
|
/// @param intrinsic_name the name of the intrinsic
|
|
/// @param args the argument types
|
|
/// @param templates initial template state. This may contain explicitly specified template
|
|
/// arguments. For example `vec3<f32>()` would have the first template-type
|
|
/// defined as `f32`.
|
|
/// @param on_no_match an error callback when no intrinsic overloads matched the provided
|
|
/// arguments.
|
|
/// @returns the matched intrinsic. If no intrinsic could be matched then IntrinsicPrototype
|
|
/// will hold nullptrs for IntrinsicPrototype::overload and
|
|
/// IntrinsicPrototype::return_type.
|
|
IntrinsicPrototype MatchIntrinsic(const IntrinsicInfo& intrinsic,
|
|
const char* intrinsic_name,
|
|
utils::VectorRef<const type::Type*> args,
|
|
sem::EvaluationStage earliest_eval_stage,
|
|
TemplateState templates,
|
|
OnNoMatch on_no_match) const;
|
|
|
|
/// Evaluates the single overload for the provided argument types.
|
|
/// @param overload the overload being considered
|
|
/// @param args the argument types
|
|
/// @param templates initial template state. This may contain explicitly specified template
|
|
/// arguments. For example `vec3<f32>()` would have the first template-type
|
|
/// template as `f32`.
|
|
/// @returns the evaluated Candidate information.
|
|
Candidate ScoreOverload(const OverloadInfo* overload,
|
|
utils::VectorRef<const type::Type*> args,
|
|
sem::EvaluationStage earliest_eval_stage,
|
|
TemplateState templates) const;
|
|
|
|
/// Performs overload resolution given the list of candidates, by ranking the conversions of
|
|
/// arguments to the each of the candidate's parameter types.
|
|
/// @param candidates the list of candidate overloads
|
|
/// @param intrinsic_name the name of the intrinsic
|
|
/// @param args the argument types
|
|
/// @param templates initial template state. This may contain explicitly specified template
|
|
/// arguments. For example `vec3<f32>()` would have the first template-type
|
|
/// template as `f32`.
|
|
/// @see https://www.w3.org/TR/WGSL/#overload-resolution-section
|
|
/// @returns the resolved Candidate.
|
|
Candidate ResolveCandidate(Candidates&& candidates,
|
|
const char* intrinsic_name,
|
|
utils::VectorRef<const type::Type*> args,
|
|
TemplateState templates) const;
|
|
|
|
/// Match constructs a new MatchState
|
|
/// @param templates the template state used for matcher evaluation
|
|
/// @param overload the overload being evaluated
|
|
/// @param matcher_indices pointer to a list of matcher indices
|
|
MatchState Match(TemplateState& templates,
|
|
const OverloadInfo* overload,
|
|
MatcherIndex const* matcher_indices,
|
|
sem::EvaluationStage earliest_eval_stage) const;
|
|
|
|
// Prints the overload for emitting diagnostics
|
|
void PrintOverload(std::ostream& ss,
|
|
const OverloadInfo* overload,
|
|
const char* intrinsic_name) const;
|
|
|
|
// Prints the list of candidates for emitting diagnostics
|
|
void PrintCandidates(std::ostream& ss,
|
|
utils::VectorRef<Candidate> candidates,
|
|
const char* intrinsic_name) const;
|
|
|
|
/// Raises an error when no overload is a clear winner of overload resolution
|
|
void ErrAmbiguousOverload(const char* intrinsic_name,
|
|
utils::VectorRef<const type::Type*> args,
|
|
TemplateState templates,
|
|
utils::VectorRef<Candidate> candidates) const;
|
|
|
|
ProgramBuilder& builder;
|
|
Matchers matchers;
|
|
utils::Hashmap<IntrinsicPrototype, sem::Builtin*, 64, IntrinsicPrototype::Hasher> builtins;
|
|
utils::Hashmap<IntrinsicPrototype, sem::TypeInitializer*, 16, IntrinsicPrototype::Hasher>
|
|
initializers;
|
|
utils::Hashmap<IntrinsicPrototype, sem::TypeConversion*, 16, IntrinsicPrototype::Hasher>
|
|
converters;
|
|
};
|
|
|
|
/// @return a string representing a call to a builtin with the given argument
|
|
/// types.
|
|
std::string CallSignature(ProgramBuilder& builder,
|
|
const char* intrinsic_name,
|
|
utils::VectorRef<const type::Type*> args,
|
|
const type::Type* template_arg = nullptr) {
|
|
std::stringstream ss;
|
|
ss << intrinsic_name;
|
|
if (template_arg) {
|
|
ss << "<" << template_arg->FriendlyName(builder.Symbols()) << ">";
|
|
}
|
|
ss << "(";
|
|
{
|
|
bool first = true;
|
|
for (auto* arg : args) {
|
|
if (!first) {
|
|
ss << ", ";
|
|
}
|
|
first = false;
|
|
ss << arg->UnwrapRef()->FriendlyName(builder.Symbols());
|
|
}
|
|
}
|
|
ss << ")";
|
|
|
|
return ss.str();
|
|
}
|
|
|
|
std::string TemplateTypeMatcher::String(MatchState* state) const {
|
|
return state->overload->template_types[index_].name;
|
|
}
|
|
|
|
std::string TemplateNumberMatcher::String(MatchState* state) const {
|
|
return state->overload->template_numbers[index_].name;
|
|
}
|
|
|
|
Impl::Impl(ProgramBuilder& b) : builder(b) {}
|
|
|
|
Impl::Builtin Impl::Lookup(sem::BuiltinType builtin_type,
|
|
utils::VectorRef<const type::Type*> args,
|
|
sem::EvaluationStage earliest_eval_stage,
|
|
const Source& source) {
|
|
const char* intrinsic_name = sem::str(builtin_type);
|
|
|
|
// Generates an error when no overloads match the provided arguments
|
|
auto on_no_match = [&](utils::VectorRef<Candidate> candidates) {
|
|
std::stringstream ss;
|
|
ss << "no matching call to " << CallSignature(builder, intrinsic_name, args) << std::endl;
|
|
if (!candidates.IsEmpty()) {
|
|
ss << std::endl
|
|
<< candidates.Length() << " candidate function"
|
|
<< (candidates.Length() > 1 ? "s:" : ":") << std::endl;
|
|
PrintCandidates(ss, candidates, intrinsic_name);
|
|
}
|
|
builder.Diagnostics().add_error(diag::System::Resolver, ss.str(), source);
|
|
};
|
|
|
|
// Resolve the intrinsic overload
|
|
auto match = MatchIntrinsic(kBuiltins[static_cast<size_t>(builtin_type)], intrinsic_name, args,
|
|
earliest_eval_stage, TemplateState{}, on_no_match);
|
|
if (!match.overload) {
|
|
return {};
|
|
}
|
|
|
|
// De-duplicate builtins that are identical.
|
|
auto* sem = builtins.GetOrCreate(match, [&] {
|
|
utils::Vector<sem::Parameter*, kNumFixedParams> params;
|
|
params.Reserve(match.parameters.Length());
|
|
for (auto& p : match.parameters) {
|
|
params.Push(builder.create<sem::Parameter>(
|
|
nullptr, static_cast<uint32_t>(params.Length()), p.type, ast::AddressSpace::kNone,
|
|
ast::Access::kUndefined, p.usage));
|
|
}
|
|
sem::PipelineStageSet supported_stages;
|
|
if (match.overload->flags.Contains(OverloadFlag::kSupportsVertexPipeline)) {
|
|
supported_stages.Add(ast::PipelineStage::kVertex);
|
|
}
|
|
if (match.overload->flags.Contains(OverloadFlag::kSupportsFragmentPipeline)) {
|
|
supported_stages.Add(ast::PipelineStage::kFragment);
|
|
}
|
|
if (match.overload->flags.Contains(OverloadFlag::kSupportsComputePipeline)) {
|
|
supported_stages.Add(ast::PipelineStage::kCompute);
|
|
}
|
|
auto eval_stage = match.overload->const_eval_fn ? sem::EvaluationStage::kConstant
|
|
: sem::EvaluationStage::kRuntime;
|
|
return builder.create<sem::Builtin>(
|
|
builtin_type, match.return_type, std::move(params), eval_stage, supported_stages,
|
|
match.overload->flags.Contains(OverloadFlag::kIsDeprecated));
|
|
});
|
|
return Builtin{sem, match.overload->const_eval_fn};
|
|
}
|
|
|
|
IntrinsicTable::UnaryOperator Impl::Lookup(ast::UnaryOp op,
|
|
const type::Type* arg,
|
|
sem::EvaluationStage earliest_eval_stage,
|
|
const Source& source) {
|
|
auto [intrinsic_index, intrinsic_name] = [&]() -> std::pair<size_t, const char*> {
|
|
switch (op) {
|
|
case ast::UnaryOp::kComplement:
|
|
return {kUnaryOperatorComplement, "operator ~ "};
|
|
case ast::UnaryOp::kNegation:
|
|
return {kUnaryOperatorMinus, "operator - "};
|
|
case ast::UnaryOp::kNot:
|
|
return {kUnaryOperatorNot, "operator ! "};
|
|
default:
|
|
return {0, "<unknown>"};
|
|
}
|
|
}();
|
|
|
|
utils::Vector args{arg};
|
|
|
|
// Generates an error when no overloads match the provided arguments
|
|
auto on_no_match = [&, name = intrinsic_name](utils::VectorRef<Candidate> candidates) {
|
|
std::stringstream ss;
|
|
ss << "no matching overload for " << CallSignature(builder, name, args) << std::endl;
|
|
if (!candidates.IsEmpty()) {
|
|
ss << std::endl
|
|
<< candidates.Length() << " candidate operator"
|
|
<< (candidates.Length() > 1 ? "s:" : ":") << std::endl;
|
|
PrintCandidates(ss, candidates, name);
|
|
}
|
|
builder.Diagnostics().add_error(diag::System::Resolver, ss.str(), source);
|
|
};
|
|
|
|
// Resolve the intrinsic overload
|
|
auto match = MatchIntrinsic(kUnaryOperators[intrinsic_index], intrinsic_name, args,
|
|
earliest_eval_stage, TemplateState{}, on_no_match);
|
|
if (!match.overload) {
|
|
return {};
|
|
}
|
|
|
|
return UnaryOperator{
|
|
match.return_type,
|
|
match.parameters[0].type,
|
|
match.overload->const_eval_fn,
|
|
};
|
|
}
|
|
|
|
IntrinsicTable::BinaryOperator Impl::Lookup(ast::BinaryOp op,
|
|
const type::Type* lhs,
|
|
const type::Type* rhs,
|
|
sem::EvaluationStage earliest_eval_stage,
|
|
const Source& source,
|
|
bool is_compound) {
|
|
auto [intrinsic_index, intrinsic_name] = [&]() -> std::pair<size_t, const char*> {
|
|
switch (op) {
|
|
case ast::BinaryOp::kAnd:
|
|
return {kBinaryOperatorAnd, is_compound ? "operator &= " : "operator & "};
|
|
case ast::BinaryOp::kOr:
|
|
return {kBinaryOperatorOr, is_compound ? "operator |= " : "operator | "};
|
|
case ast::BinaryOp::kXor:
|
|
return {kBinaryOperatorXor, is_compound ? "operator ^= " : "operator ^ "};
|
|
case ast::BinaryOp::kLogicalAnd:
|
|
return {kBinaryOperatorLogicalAnd, "operator && "};
|
|
case ast::BinaryOp::kLogicalOr:
|
|
return {kBinaryOperatorLogicalOr, "operator || "};
|
|
case ast::BinaryOp::kEqual:
|
|
return {kBinaryOperatorEqual, "operator == "};
|
|
case ast::BinaryOp::kNotEqual:
|
|
return {kBinaryOperatorNotEqual, "operator != "};
|
|
case ast::BinaryOp::kLessThan:
|
|
return {kBinaryOperatorLessThan, "operator < "};
|
|
case ast::BinaryOp::kGreaterThan:
|
|
return {kBinaryOperatorGreaterThan, "operator > "};
|
|
case ast::BinaryOp::kLessThanEqual:
|
|
return {kBinaryOperatorLessThanEqual, "operator <= "};
|
|
case ast::BinaryOp::kGreaterThanEqual:
|
|
return {kBinaryOperatorGreaterThanEqual, "operator >= "};
|
|
case ast::BinaryOp::kShiftLeft:
|
|
return {kBinaryOperatorShiftLeft, is_compound ? "operator <<= " : "operator << "};
|
|
case ast::BinaryOp::kShiftRight:
|
|
return {kBinaryOperatorShiftRight, is_compound ? "operator >>= " : "operator >> "};
|
|
case ast::BinaryOp::kAdd:
|
|
return {kBinaryOperatorPlus, is_compound ? "operator += " : "operator + "};
|
|
case ast::BinaryOp::kSubtract:
|
|
return {kBinaryOperatorMinus, is_compound ? "operator -= " : "operator - "};
|
|
case ast::BinaryOp::kMultiply:
|
|
return {kBinaryOperatorStar, is_compound ? "operator *= " : "operator * "};
|
|
case ast::BinaryOp::kDivide:
|
|
return {kBinaryOperatorDivide, is_compound ? "operator /= " : "operator / "};
|
|
case ast::BinaryOp::kModulo:
|
|
return {kBinaryOperatorModulo, is_compound ? "operator %= " : "operator % "};
|
|
default:
|
|
return {0, "<unknown>"};
|
|
}
|
|
}();
|
|
|
|
utils::Vector args{lhs, rhs};
|
|
|
|
// Generates an error when no overloads match the provided arguments
|
|
auto on_no_match = [&, name = intrinsic_name](utils::VectorRef<Candidate> candidates) {
|
|
std::stringstream ss;
|
|
ss << "no matching overload for " << CallSignature(builder, name, args) << std::endl;
|
|
if (!candidates.IsEmpty()) {
|
|
ss << std::endl
|
|
<< candidates.Length() << " candidate operator"
|
|
<< (candidates.Length() > 1 ? "s:" : ":") << std::endl;
|
|
PrintCandidates(ss, candidates, name);
|
|
}
|
|
builder.Diagnostics().add_error(diag::System::Resolver, ss.str(), source);
|
|
};
|
|
|
|
// Resolve the intrinsic overload
|
|
auto match = MatchIntrinsic(kBinaryOperators[intrinsic_index], intrinsic_name, args,
|
|
earliest_eval_stage, TemplateState{}, on_no_match);
|
|
if (!match.overload) {
|
|
return {};
|
|
}
|
|
|
|
return BinaryOperator{
|
|
match.return_type,
|
|
match.parameters[0].type,
|
|
match.parameters[1].type,
|
|
match.overload->const_eval_fn,
|
|
};
|
|
}
|
|
|
|
IntrinsicTable::InitOrConv Impl::Lookup(InitConvIntrinsic type,
|
|
const type::Type* template_arg,
|
|
utils::VectorRef<const type::Type*> args,
|
|
sem::EvaluationStage earliest_eval_stage,
|
|
const Source& source) {
|
|
auto name = str(type);
|
|
|
|
// Generates an error when no overloads match the provided arguments
|
|
auto on_no_match = [&](utils::VectorRef<Candidate> candidates) {
|
|
std::stringstream ss;
|
|
ss << "no matching initializer for " << CallSignature(builder, name, args, template_arg)
|
|
<< std::endl;
|
|
Candidates ctor, conv;
|
|
for (auto candidate : candidates) {
|
|
if (candidate.overload->flags.Contains(OverloadFlag::kIsInitializer)) {
|
|
ctor.Push(candidate);
|
|
} else {
|
|
conv.Push(candidate);
|
|
}
|
|
}
|
|
if (!ctor.IsEmpty()) {
|
|
ss << std::endl
|
|
<< ctor.Length() << " candidate initializer" << (ctor.Length() > 1 ? "s:" : ":")
|
|
<< std::endl;
|
|
PrintCandidates(ss, ctor, name);
|
|
}
|
|
if (!conv.IsEmpty()) {
|
|
ss << std::endl
|
|
<< conv.Length() << " candidate conversion" << (conv.Length() > 1 ? "s:" : ":")
|
|
<< std::endl;
|
|
PrintCandidates(ss, conv, name);
|
|
}
|
|
builder.Diagnostics().add_error(diag::System::Resolver, ss.str(), source);
|
|
};
|
|
|
|
// If a template type was provided, then close the 0'th type with this.
|
|
TemplateState templates;
|
|
if (template_arg) {
|
|
templates.Type(0, template_arg);
|
|
}
|
|
|
|
// Resolve the intrinsic overload
|
|
auto match = MatchIntrinsic(kInitializersAndConverters[static_cast<size_t>(type)], name, args,
|
|
earliest_eval_stage, templates, on_no_match);
|
|
if (!match.overload) {
|
|
return {};
|
|
}
|
|
|
|
// Was this overload a initializer or conversion?
|
|
if (match.overload->flags.Contains(OverloadFlag::kIsInitializer)) {
|
|
utils::Vector<const sem::Parameter*, 8> params;
|
|
params.Reserve(match.parameters.Length());
|
|
for (auto& p : match.parameters) {
|
|
params.Push(builder.create<sem::Parameter>(
|
|
nullptr, static_cast<uint32_t>(params.Length()), p.type, ast::AddressSpace::kNone,
|
|
ast::Access::kUndefined, p.usage));
|
|
}
|
|
auto eval_stage = match.overload->const_eval_fn ? sem::EvaluationStage::kConstant
|
|
: sem::EvaluationStage::kRuntime;
|
|
auto* target = initializers.GetOrCreate(match, [&]() {
|
|
return builder.create<sem::TypeInitializer>(match.return_type, std::move(params),
|
|
eval_stage);
|
|
});
|
|
return InitOrConv{target, match.overload->const_eval_fn};
|
|
}
|
|
|
|
// Conversion.
|
|
auto* target = converters.GetOrCreate(match, [&]() {
|
|
auto param = builder.create<sem::Parameter>(
|
|
nullptr, 0u, match.parameters[0].type, ast::AddressSpace::kNone,
|
|
ast::Access::kUndefined, match.parameters[0].usage);
|
|
auto eval_stage = match.overload->const_eval_fn ? sem::EvaluationStage::kConstant
|
|
: sem::EvaluationStage::kRuntime;
|
|
return builder.create<sem::TypeConversion>(match.return_type, param, eval_stage);
|
|
});
|
|
return InitOrConv{target, match.overload->const_eval_fn};
|
|
}
|
|
|
|
IntrinsicPrototype Impl::MatchIntrinsic(const IntrinsicInfo& intrinsic,
|
|
const char* intrinsic_name,
|
|
utils::VectorRef<const type::Type*> args,
|
|
sem::EvaluationStage earliest_eval_stage,
|
|
TemplateState templates,
|
|
OnNoMatch on_no_match) const {
|
|
size_t num_matched = 0;
|
|
size_t match_idx = 0;
|
|
utils::Vector<Candidate, kNumFixedCandidates> candidates;
|
|
candidates.Reserve(intrinsic.num_overloads);
|
|
for (size_t overload_idx = 0; overload_idx < static_cast<size_t>(intrinsic.num_overloads);
|
|
overload_idx++) {
|
|
auto candidate =
|
|
ScoreOverload(&intrinsic.overloads[overload_idx], args, earliest_eval_stage, templates);
|
|
if (candidate.score == 0) {
|
|
match_idx = overload_idx;
|
|
num_matched++;
|
|
}
|
|
candidates.Push(std::move(candidate));
|
|
}
|
|
|
|
// How many candidates matched?
|
|
if (num_matched == 0) {
|
|
// Sort the candidates with the most promising first
|
|
SortCandidates(candidates);
|
|
on_no_match(std::move(candidates));
|
|
return {};
|
|
}
|
|
|
|
Candidate match;
|
|
|
|
if (num_matched == 1) {
|
|
match = std::move(candidates[match_idx]);
|
|
} else {
|
|
match = ResolveCandidate(std::move(candidates), intrinsic_name, args, std::move(templates));
|
|
if (!match.overload) {
|
|
// Ambiguous overload. ResolveCandidate() will have already raised an error diagnostic.
|
|
return {};
|
|
}
|
|
}
|
|
|
|
// Build the return type
|
|
const type::Type* return_type = nullptr;
|
|
if (auto* indices = match.overload->return_matcher_indices) {
|
|
Any any;
|
|
return_type =
|
|
Match(match.templates, match.overload, indices, earliest_eval_stage).Type(&any);
|
|
if (!return_type) {
|
|
TINT_ICE(Resolver, builder.Diagnostics()) << "MatchState.Match() returned null";
|
|
return {};
|
|
}
|
|
} else {
|
|
return_type = builder.create<type::Void>();
|
|
}
|
|
|
|
return IntrinsicPrototype{match.overload, return_type, std::move(match.parameters)};
|
|
}
|
|
|
|
Impl::Candidate Impl::ScoreOverload(const OverloadInfo* overload,
|
|
utils::VectorRef<const type::Type*> args,
|
|
sem::EvaluationStage earliest_eval_stage,
|
|
TemplateState templates) const {
|
|
// Penalty weights for overload mismatching.
|
|
// This scoring is used to order the suggested overloads in diagnostic on overload mismatch, and
|
|
// has no impact for a correct program.
|
|
// The overloads with the lowest score will be displayed first (top-most).
|
|
constexpr int kMismatchedParamCountPenalty = 3;
|
|
constexpr int kMismatchedParamTypePenalty = 2;
|
|
constexpr int kMismatchedTemplateTypePenalty = 1;
|
|
constexpr int kMismatchedTemplateNumberPenalty = 1;
|
|
|
|
size_t num_parameters = static_cast<size_t>(overload->num_parameters);
|
|
size_t num_arguments = static_cast<size_t>(args.Length());
|
|
|
|
size_t score = 0;
|
|
|
|
if (num_parameters != num_arguments) {
|
|
score += kMismatchedParamCountPenalty * (std::max(num_parameters, num_arguments) -
|
|
std::min(num_parameters, num_arguments));
|
|
}
|
|
|
|
// Invoke the matchers for each parameter <-> argument pair.
|
|
// If any arguments cannot be matched, then `score` will be increased.
|
|
// If the overload has any template types or numbers then these will be set based on the
|
|
// argument types. Template types may be refined by constraining with later argument types. For
|
|
// example calling `F<T>(T, T)` with the argument types (abstract-int, i32) will first set T to
|
|
// abstract-int when matching the first argument, and then constrained down to i32 when matching
|
|
// the second argument.
|
|
// Note that inferred template types are not tested against their matchers at this point.
|
|
auto num_params = std::min(num_parameters, num_arguments);
|
|
for (size_t p = 0; p < num_params; p++) {
|
|
auto& parameter = overload->parameters[p];
|
|
auto* indices = parameter.matcher_indices;
|
|
if (!Match(templates, overload, indices, earliest_eval_stage).Type(args[p]->UnwrapRef())) {
|
|
score += kMismatchedParamTypePenalty;
|
|
}
|
|
}
|
|
|
|
if (score == 0) {
|
|
// Check all constrained template types matched their constraint matchers.
|
|
// If the template type *does not* match any of the types in the constraint matcher, then
|
|
// `score` is incremented. If the template type *does* match a type, then the template type
|
|
// is replaced with the first matching type. The order of types in the template matcher is
|
|
// important here, which can be controlled with the [[precedence(N)]] decorations on the
|
|
// types in intrinsics.def.
|
|
for (size_t ot = 0; ot < overload->num_template_types; ot++) {
|
|
auto* matcher_index = &overload->template_types[ot].matcher_index;
|
|
if (*matcher_index != kNoMatcher) {
|
|
if (auto* template_type = templates.Type(ot)) {
|
|
if (auto* ty = Match(templates, overload, matcher_index, earliest_eval_stage)
|
|
.Type(template_type)) {
|
|
// Template type matched one of the types in the template type's matcher.
|
|
// Replace the template type with this type.
|
|
templates.SetType(ot, ty);
|
|
continue;
|
|
}
|
|
}
|
|
score += kMismatchedTemplateTypePenalty;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (score == 0) {
|
|
// Check all constrained open numbers matched.
|
|
// Unlike template types, numbers are not constrained, so we're just checking that the
|
|
// inferred number matches the constraints on the overload. Increments `score` if the
|
|
// template numbers do not match their constraint matchers.
|
|
for (size_t on = 0; on < overload->num_template_numbers; on++) {
|
|
auto* matcher_index = &overload->template_numbers[on].matcher_index;
|
|
if (*matcher_index != kNoMatcher) {
|
|
auto template_num = templates.Num(on);
|
|
if (!template_num.IsValid() ||
|
|
!Match(templates, overload, matcher_index, earliest_eval_stage)
|
|
.Num(template_num)
|
|
.IsValid()) {
|
|
score += kMismatchedTemplateNumberPenalty;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Now that all the template types have been finalized, we can construct the parameters.
|
|
utils::Vector<IntrinsicPrototype::Parameter, kNumFixedParams> parameters;
|
|
if (score == 0) {
|
|
parameters.Reserve(num_params);
|
|
for (size_t p = 0; p < num_params; p++) {
|
|
auto& parameter = overload->parameters[p];
|
|
auto* indices = parameter.matcher_indices;
|
|
auto* ty =
|
|
Match(templates, overload, indices, earliest_eval_stage).Type(args[p]->UnwrapRef());
|
|
parameters.Emplace(ty, parameter.usage);
|
|
}
|
|
}
|
|
|
|
return Candidate{overload, templates, parameters, score};
|
|
}
|
|
|
|
Impl::Candidate Impl::ResolveCandidate(Impl::Candidates&& candidates,
|
|
const char* intrinsic_name,
|
|
utils::VectorRef<const type::Type*> args,
|
|
TemplateState templates) const {
|
|
utils::Vector<uint32_t, kNumFixedParams> best_ranks;
|
|
best_ranks.Resize(args.Length(), 0xffffffff);
|
|
size_t num_matched = 0;
|
|
Candidate* best = nullptr;
|
|
for (auto& candidate : candidates) {
|
|
if (candidate.score > 0) {
|
|
continue; // Candidate has already been ruled out.
|
|
}
|
|
bool some_won = false; // An argument ranked less than the 'best' overload's argument
|
|
bool some_lost = false; // An argument ranked more than the 'best' overload's argument
|
|
for (size_t i = 0; i < args.Length(); i++) {
|
|
auto rank = type::Type::ConversionRank(args[i], candidate.parameters[i].type);
|
|
if (best_ranks[i] > rank) {
|
|
best_ranks[i] = rank;
|
|
some_won = true;
|
|
} else if (best_ranks[i] < rank) {
|
|
some_lost = true;
|
|
}
|
|
}
|
|
// If no arguments of this candidate ranked worse than the previous best candidate, then
|
|
// this candidate becomes the new best candidate.
|
|
// If no arguments of this candidate ranked better than the previous best candidate, then
|
|
// this candidate is removed from the list of matches.
|
|
// If neither of the above apply, then we have two candidates with no clear winner, which
|
|
// results in an ambiguous overload error. In this situation the loop ends with
|
|
// `num_matched > 1`.
|
|
if (some_won) {
|
|
// One or more arguments of this candidate ranked better than the previous best
|
|
// candidate's argument(s).
|
|
num_matched++;
|
|
if (!some_lost) {
|
|
// All arguments were at as-good or better than the previous best.
|
|
if (best) {
|
|
// Mark the previous best candidate as no longer being in the running, by
|
|
// setting its score to a non-zero value. We pick 1 as this is the closest to 0
|
|
// (match) as we can get.
|
|
best->score = 1;
|
|
num_matched--;
|
|
}
|
|
// This candidate is the new best.
|
|
best = &candidate;
|
|
}
|
|
} else {
|
|
// No arguments ranked better than the current best.
|
|
// Change the score of this candidate to a non-zero value, so that it's not considered a
|
|
// match.
|
|
candidate.score = 1;
|
|
}
|
|
}
|
|
|
|
if (num_matched > 1) {
|
|
// Re-sort the candidates with the most promising first
|
|
SortCandidates(candidates);
|
|
// Raise an error
|
|
ErrAmbiguousOverload(intrinsic_name, args, templates, candidates);
|
|
return {};
|
|
}
|
|
|
|
return std::move(*best);
|
|
}
|
|
|
|
MatchState Impl::Match(TemplateState& templates,
|
|
const OverloadInfo* overload,
|
|
MatcherIndex const* matcher_indices,
|
|
sem::EvaluationStage earliest_eval_stage) const {
|
|
return MatchState(builder, templates, matchers, overload, matcher_indices, earliest_eval_stage);
|
|
}
|
|
|
|
void Impl::PrintOverload(std::ostream& ss,
|
|
const OverloadInfo* overload,
|
|
const char* intrinsic_name) const {
|
|
TemplateState templates;
|
|
|
|
// TODO(crbug.com/tint/1730): Use input evaluation stage to output only relevant overloads.
|
|
auto earliest_eval_stage = sem::EvaluationStage::kConstant;
|
|
|
|
ss << intrinsic_name;
|
|
|
|
bool print_template_type = false;
|
|
if (overload->num_template_types > 0) {
|
|
if (overload->flags.Contains(OverloadFlag::kIsConverter)) {
|
|
// Print for conversions
|
|
// e.g. vec3<T>(vec3<U>) -> vec3<f32>
|
|
print_template_type = true;
|
|
} else if ((overload->num_parameters == 0) &&
|
|
overload->flags.Contains(OverloadFlag::kIsInitializer)) {
|
|
// Print for initializers with no params
|
|
// e.g. vec2<T>() -> vec2<T>
|
|
print_template_type = true;
|
|
}
|
|
}
|
|
if (print_template_type) {
|
|
ss << "<";
|
|
ss << overload->template_types[0].name;
|
|
ss << ">";
|
|
}
|
|
ss << "(";
|
|
for (size_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(templates, overload, indices, earliest_eval_stage).TypeName();
|
|
}
|
|
ss << ")";
|
|
if (overload->return_matcher_indices) {
|
|
ss << " -> ";
|
|
auto* indices = overload->return_matcher_indices;
|
|
ss << Match(templates, overload, indices, earliest_eval_stage).TypeName();
|
|
}
|
|
|
|
bool first = true;
|
|
auto separator = [&] {
|
|
ss << (first ? " where: " : ", ");
|
|
first = false;
|
|
};
|
|
for (size_t i = 0; i < overload->num_template_types; i++) {
|
|
auto& template_type = overload->template_types[i];
|
|
if (template_type.matcher_index != kNoMatcher) {
|
|
separator();
|
|
ss << template_type.name;
|
|
auto* index = &template_type.matcher_index;
|
|
ss << " is " << Match(templates, overload, index, earliest_eval_stage).TypeName();
|
|
}
|
|
}
|
|
for (size_t i = 0; i < overload->num_template_numbers; i++) {
|
|
auto& template_number = overload->template_numbers[i];
|
|
if (template_number.matcher_index != kNoMatcher) {
|
|
separator();
|
|
ss << template_number.name;
|
|
auto* index = &template_number.matcher_index;
|
|
ss << " is " << Match(templates, overload, index, earliest_eval_stage).NumName();
|
|
}
|
|
}
|
|
}
|
|
|
|
void Impl::PrintCandidates(std::ostream& ss,
|
|
utils::VectorRef<Candidate> candidates,
|
|
const char* intrinsic_name) const {
|
|
for (auto& candidate : candidates) {
|
|
ss << " ";
|
|
PrintOverload(ss, candidate.overload, intrinsic_name);
|
|
ss << std::endl;
|
|
}
|
|
}
|
|
|
|
const type::Type* MatchState::Type(const type::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);
|
|
}
|
|
|
|
void Impl::ErrAmbiguousOverload(const char* intrinsic_name,
|
|
utils::VectorRef<const type::Type*> args,
|
|
TemplateState templates,
|
|
utils::VectorRef<Candidate> candidates) const {
|
|
std::stringstream ss;
|
|
ss << "ambiguous overload while attempting to match " << intrinsic_name;
|
|
for (size_t i = 0; i < std::numeric_limits<size_t>::max(); i++) {
|
|
if (auto* ty = templates.Type(i)) {
|
|
ss << ((i == 0) ? "<" : ", ") << ty->FriendlyName(builder.Symbols());
|
|
} else {
|
|
if (i > 0) {
|
|
ss << ">";
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
ss << "(";
|
|
bool first = true;
|
|
for (auto* arg : args) {
|
|
if (!first) {
|
|
ss << ", ";
|
|
}
|
|
first = false;
|
|
ss << arg->FriendlyName(builder.Symbols());
|
|
}
|
|
ss << "):\n";
|
|
for (auto& candidate : candidates) {
|
|
if (candidate.score == 0) {
|
|
ss << " ";
|
|
PrintOverload(ss, candidate.overload, intrinsic_name);
|
|
ss << std::endl;
|
|
}
|
|
}
|
|
TINT_ICE(Resolver, builder.Diagnostics()) << ss.str();
|
|
}
|
|
|
|
} // namespace
|
|
|
|
std::unique_ptr<IntrinsicTable> IntrinsicTable::Create(ProgramBuilder& builder) {
|
|
return std::make_unique<Impl>(builder);
|
|
}
|
|
|
|
IntrinsicTable::~IntrinsicTable() = default;
|
|
|
|
} // namespace tint::resolver
|
|
|
|
/// TypeInfo for the Any type declared in the anonymous namespace above
|
|
TINT_INSTANTIATE_TYPEINFO(tint::resolver::Any);
|