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