| // 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(0u, type::Flags{}) {} |
| ~Any() override = default; |
| |
| // Stub implementations for type::Type conformance. |
| bool Equals(const type::UniqueNode&) 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); |