[tint] Begin decoupling intrinsic table data from table.cc

Change-Id: I123733c34808957f37da7b7dd964727d2f069c46
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/144128
Reviewed-by: Dan Sinclair <dsinclair@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
diff --git a/src/tint/BUILD.gn b/src/tint/BUILD.gn
index 794d8ab..b1e6daa 100644
--- a/src/tint/BUILD.gn
+++ b/src/tint/BUILD.gn
@@ -289,11 +289,12 @@
   sources = [
     "lang/core/constant/eval.cc",
     "lang/core/constant/eval.h",
+    "lang/core/intrinsic/core_table_data.cc",
+    "lang/core/intrinsic/core_table_data.h",
     "lang/core/intrinsic/ctor_conv.cc",
     "lang/core/intrinsic/ctor_conv.h",
     "lang/core/intrinsic/table.cc",
     "lang/core/intrinsic/table.h",
-    "lang/core/intrinsic/table.inl",
     "lang/wgsl/program/clone_context.cc",
     "lang/wgsl/program/program.cc",
     "lang/wgsl/program/program_builder.cc",
diff --git a/src/tint/CMakeLists.txt b/src/tint/CMakeLists.txt
index 52f39e1..157a51e 100644
--- a/src/tint/CMakeLists.txt
+++ b/src/tint/CMakeLists.txt
@@ -518,9 +518,12 @@
   lang/core/constant/eval.h
   lang/wgsl/resolver/dependency_graph.cc
   lang/wgsl/resolver/dependency_graph.h
+  lang/core/intrinsic/core_table_data.cc
+  lang/core/intrinsic/core_table_data.h
+  lang/core/intrinsic/ctor_conv.cc
+  lang/core/intrinsic/ctor_conv.h
   lang/core/intrinsic/table.cc
   lang/core/intrinsic/table.h
-  lang/core/intrinsic/table.inl
   lang/wgsl/resolver/resolve.cc
   lang/wgsl/resolver/resolve.h
   lang/wgsl/resolver/resolver.cc
diff --git a/src/tint/lang/core/intrinsic/table.inl b/src/tint/lang/core/intrinsic/core_table_data.cc
similarity index 83%
rename from src/tint/lang/core/intrinsic/table.inl
rename to src/tint/lang/core/intrinsic/core_table_data.cc
index 43a8477..c46680b 100644
--- a/src/tint/lang/core/intrinsic/table.inl
+++ b/src/tint/lang/core/intrinsic/core_table_data.cc
@@ -1,4 +1,4 @@
-// Copyright 2021 The Tint Authors.
+// Copyright 2023 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.
@@ -15,2828 +15,1788 @@
 ////////////////////////////////////////////////////////////////////////////////
 // File generated by tools/src/cmd/gen
 // using the template:
-//   src/tint/lang/core/intrinsic/table.inl.tmpl
+//   src/tint/lang/core/intrinsic/core_table_data.cc.tmpl
 //
 // Do not modify this file directly
 ////////////////////////////////////////////////////////////////////////////////
 
+#include <limits>
+#include <string>
+
+#include "src/tint/lang/core/intrinsic/core_table_data.h"
+#include "src/tint/lang/core/intrinsic/core_type_matchers.h"
+#include "src/tint/utils/text/string_stream.h"
+
+namespace tint::core::intrinsic {
+namespace {
+
+using IntrinsicInfo = tint::core::intrinsic::TableData::IntrinsicInfo;
+using MatcherIndex = tint::core::intrinsic::TableData::MatcherIndex;
+using MatchState = tint::core::intrinsic::TableData::MatchState;
+using Number = tint::core::intrinsic::TableData::Number;
+using NumberMatcher = tint::core::intrinsic::TableData::NumberMatcher;
+using OverloadFlag = tint::core::intrinsic::TableData::OverloadFlag;
+using OverloadFlags = tint::core::intrinsic::TableData::OverloadFlags;
+using OverloadInfo = tint::core::intrinsic::TableData::OverloadInfo;
+using ParameterInfo = tint::core::intrinsic::TableData::ParameterInfo;
+using StringStream = tint::StringStream;
+using TemplateNumberInfo = tint::core::intrinsic::TableData::TemplateNumberInfo;
+using TemplateTypeInfo = tint::core::intrinsic::TableData::TemplateTypeInfo;
+using Type = tint::type::Type;
+using TypeMatcher = tint::core::intrinsic::TableData::TypeMatcher;
+
+template <size_t N>
+using TemplateNumberMatcher = tint::core::intrinsic::TableData::TemplateNumberMatcher<N>;
+
+template <size_t N>
+using TemplateTypeMatcher = tint::core::intrinsic::TableData::TemplateTypeMatcher<N>;
+
+static constexpr auto kNoMatcher = tint::core::intrinsic::TableData::kNoMatcher;
+
 // clang-format off
 
 /// TypeMatcher for 'type bool'
-class Bool : public TypeMatcher {
- public:
-  /// Checks whether the given type matches the matcher rules.
-  /// Match may define and refine the template types and numbers in state.
-  /// @param state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+constexpr TypeMatcher kBoolMatcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+    if (!match_bool(state, ty)) {
+      return nullptr;
+    }
+    return build_bool(state);
+  },
+/* string */ [](MatchState*) -> std::string {
+    return "bool";
+  }
 };
 
-const type::Type* Bool::Match(MatchState& state, const type::Type* ty) const {
-  if (!match_bool(state, ty)) {
-    return nullptr;
-  }
-  return build_bool(state);
-}
-
-std::string Bool::String(MatchState*) const {
-  return "bool";
-}
 
 /// TypeMatcher for 'type ia'
-class Ia : public TypeMatcher {
- public:
-  /// Checks whether the given type matches the matcher rules.
-  /// Match may define and refine the template types and numbers in state.
-  /// @param state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+constexpr TypeMatcher kIaMatcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+    if (!match_ia(state, ty)) {
+      return nullptr;
+    }
+    return build_ia(state);
+  },
+/* string */ [](MatchState*) -> std::string {
+    StringStream ss;
+    ss << "abstract-int";
+    return ss.str();
+  }
 };
 
-const type::Type* Ia::Match(MatchState& state, const type::Type* ty) const {
-  if (!match_ia(state, ty)) {
-    return nullptr;
-  }
-  return build_ia(state);
-}
-
-std::string Ia::String(MatchState*) const {
-  StringStream ss;
-  ss << "abstract-int";
-  return ss.str();
-}
 
 /// TypeMatcher for 'type fa'
-class Fa : public TypeMatcher {
- public:
-  /// Checks whether the given type matches the matcher rules.
-  /// Match may define and refine the template types and numbers in state.
-  /// @param state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+constexpr TypeMatcher kFaMatcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+    if (!match_fa(state, ty)) {
+      return nullptr;
+    }
+    return build_fa(state);
+  },
+/* string */ [](MatchState*) -> std::string {
+    StringStream ss;
+    ss << "abstract-float";
+    return ss.str();
+  }
 };
 
-const type::Type* Fa::Match(MatchState& state, const type::Type* ty) const {
-  if (!match_fa(state, ty)) {
-    return nullptr;
-  }
-  return build_fa(state);
-}
-
-std::string Fa::String(MatchState*) const {
-  StringStream ss;
-  ss << "abstract-float";
-  return ss.str();
-}
 
 /// TypeMatcher for 'type i32'
-class I32 : public TypeMatcher {
- public:
-  /// Checks whether the given type matches the matcher rules.
-  /// Match may define and refine the template types and numbers in state.
-  /// @param state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+constexpr TypeMatcher kI32Matcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+    if (!match_i32(state, ty)) {
+      return nullptr;
+    }
+    return build_i32(state);
+  },
+/* string */ [](MatchState*) -> std::string {
+    return "i32";
+  }
 };
 
-const type::Type* I32::Match(MatchState& state, const type::Type* ty) const {
-  if (!match_i32(state, ty)) {
-    return nullptr;
-  }
-  return build_i32(state);
-}
-
-std::string I32::String(MatchState*) const {
-  return "i32";
-}
 
 /// TypeMatcher for 'type u32'
-class U32 : public TypeMatcher {
- public:
-  /// Checks whether the given type matches the matcher rules.
-  /// Match may define and refine the template types and numbers in state.
-  /// @param state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+constexpr TypeMatcher kU32Matcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+    if (!match_u32(state, ty)) {
+      return nullptr;
+    }
+    return build_u32(state);
+  },
+/* string */ [](MatchState*) -> std::string {
+    return "u32";
+  }
 };
 
-const type::Type* U32::Match(MatchState& state, const type::Type* ty) const {
-  if (!match_u32(state, ty)) {
-    return nullptr;
-  }
-  return build_u32(state);
-}
-
-std::string U32::String(MatchState*) const {
-  return "u32";
-}
 
 /// TypeMatcher for 'type f32'
-class F32 : public TypeMatcher {
- public:
-  /// Checks whether the given type matches the matcher rules.
-  /// Match may define and refine the template types and numbers in state.
-  /// @param state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+constexpr TypeMatcher kF32Matcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+    if (!match_f32(state, ty)) {
+      return nullptr;
+    }
+    return build_f32(state);
+  },
+/* string */ [](MatchState*) -> std::string {
+    return "f32";
+  }
 };
 
-const type::Type* F32::Match(MatchState& state, const type::Type* ty) const {
-  if (!match_f32(state, ty)) {
-    return nullptr;
-  }
-  return build_f32(state);
-}
-
-std::string F32::String(MatchState*) const {
-  return "f32";
-}
 
 /// TypeMatcher for 'type f16'
-class F16 : public TypeMatcher {
- public:
-  /// Checks whether the given type matches the matcher rules.
-  /// Match may define and refine the template types and numbers in state.
-  /// @param state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+constexpr TypeMatcher kF16Matcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+    if (!match_f16(state, ty)) {
+      return nullptr;
+    }
+    return build_f16(state);
+  },
+/* string */ [](MatchState*) -> std::string {
+    return "f16";
+  }
 };
 
-const type::Type* F16::Match(MatchState& state, const type::Type* ty) const {
-  if (!match_f16(state, ty)) {
-    return nullptr;
-  }
-  return build_f16(state);
-}
-
-std::string F16::String(MatchState*) const {
-  return "f16";
-}
 
 /// TypeMatcher for 'type vec2'
-class Vec2 : public TypeMatcher {
- public:
-  /// Checks whether the given type matches the matcher rules.
-  /// Match may define and refine the template types and numbers in state.
-  /// @param state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+constexpr TypeMatcher kVec2Matcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+  const Type* T = nullptr;
+    if (!match_vec2(state, ty, T)) {
+      return nullptr;
+    }
+    T = state.Type(T);
+    if (T == nullptr) {
+      return nullptr;
+    }
+    return build_vec2(state, T);
+  },
+/* string */ [](MatchState* state) -> std::string {
+  const std::string T = state->TypeName();
+    return "vec2<" + T + ">";
+  }
 };
 
-const type::Type* Vec2::Match(MatchState& state, const type::Type* ty) const {
-  const type::Type* T = nullptr;
-  if (!match_vec2(state, ty, T)) {
-    return nullptr;
-  }
-  T = state.Type(T);
-  if (T == nullptr) {
-    return nullptr;
-  }
-  return build_vec2(state, T);
-}
-
-std::string Vec2::String(MatchState* state) const {
-  const std::string T = state->TypeName();
-  return "vec2<" + T + ">";
-}
 
 /// TypeMatcher for 'type vec3'
-class Vec3 : public TypeMatcher {
- public:
-  /// Checks whether the given type matches the matcher rules.
-  /// Match may define and refine the template types and numbers in state.
-  /// @param state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+constexpr TypeMatcher kVec3Matcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+  const Type* T = nullptr;
+    if (!match_vec3(state, ty, T)) {
+      return nullptr;
+    }
+    T = state.Type(T);
+    if (T == nullptr) {
+      return nullptr;
+    }
+    return build_vec3(state, T);
+  },
+/* string */ [](MatchState* state) -> std::string {
+  const std::string T = state->TypeName();
+    return "vec3<" + T + ">";
+  }
 };
 
-const type::Type* Vec3::Match(MatchState& state, const type::Type* ty) const {
-  const type::Type* T = nullptr;
-  if (!match_vec3(state, ty, T)) {
-    return nullptr;
-  }
-  T = state.Type(T);
-  if (T == nullptr) {
-    return nullptr;
-  }
-  return build_vec3(state, T);
-}
-
-std::string Vec3::String(MatchState* state) const {
-  const std::string T = state->TypeName();
-  return "vec3<" + T + ">";
-}
 
 /// TypeMatcher for 'type vec4'
-class Vec4 : public TypeMatcher {
- public:
-  /// Checks whether the given type matches the matcher rules.
-  /// Match may define and refine the template types and numbers in state.
-  /// @param state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+constexpr TypeMatcher kVec4Matcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+  const Type* T = nullptr;
+    if (!match_vec4(state, ty, T)) {
+      return nullptr;
+    }
+    T = state.Type(T);
+    if (T == nullptr) {
+      return nullptr;
+    }
+    return build_vec4(state, T);
+  },
+/* string */ [](MatchState* state) -> std::string {
+  const std::string T = state->TypeName();
+    return "vec4<" + T + ">";
+  }
 };
 
-const type::Type* Vec4::Match(MatchState& state, const type::Type* ty) const {
-  const type::Type* T = nullptr;
-  if (!match_vec4(state, ty, T)) {
-    return nullptr;
-  }
-  T = state.Type(T);
-  if (T == nullptr) {
-    return nullptr;
-  }
-  return build_vec4(state, T);
-}
-
-std::string Vec4::String(MatchState* state) const {
-  const std::string T = state->TypeName();
-  return "vec4<" + T + ">";
-}
 
 /// TypeMatcher for 'type mat2x2'
-class Mat2X2 : public TypeMatcher {
- public:
-  /// Checks whether the given type matches the matcher rules.
-  /// Match may define and refine the template types and numbers in state.
-  /// @param state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+constexpr TypeMatcher kMat2X2Matcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+  const Type* T = nullptr;
+    if (!match_mat2x2(state, ty, T)) {
+      return nullptr;
+    }
+    T = state.Type(T);
+    if (T == nullptr) {
+      return nullptr;
+    }
+    return build_mat2x2(state, T);
+  },
+/* string */ [](MatchState* state) -> std::string {
+  const std::string T = state->TypeName();
+    return "mat2x2<" + T + ">";
+  }
 };
 
-const type::Type* Mat2X2::Match(MatchState& state, const type::Type* ty) const {
-  const type::Type* T = nullptr;
-  if (!match_mat2x2(state, ty, T)) {
-    return nullptr;
-  }
-  T = state.Type(T);
-  if (T == nullptr) {
-    return nullptr;
-  }
-  return build_mat2x2(state, T);
-}
-
-std::string Mat2X2::String(MatchState* state) const {
-  const std::string T = state->TypeName();
-  return "mat2x2<" + T + ">";
-}
 
 /// TypeMatcher for 'type mat2x3'
-class Mat2X3 : public TypeMatcher {
- public:
-  /// Checks whether the given type matches the matcher rules.
-  /// Match may define and refine the template types and numbers in state.
-  /// @param state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+constexpr TypeMatcher kMat2X3Matcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+  const Type* T = nullptr;
+    if (!match_mat2x3(state, ty, T)) {
+      return nullptr;
+    }
+    T = state.Type(T);
+    if (T == nullptr) {
+      return nullptr;
+    }
+    return build_mat2x3(state, T);
+  },
+/* string */ [](MatchState* state) -> std::string {
+  const std::string T = state->TypeName();
+    return "mat2x3<" + T + ">";
+  }
 };
 
-const type::Type* Mat2X3::Match(MatchState& state, const type::Type* ty) const {
-  const type::Type* T = nullptr;
-  if (!match_mat2x3(state, ty, T)) {
-    return nullptr;
-  }
-  T = state.Type(T);
-  if (T == nullptr) {
-    return nullptr;
-  }
-  return build_mat2x3(state, T);
-}
-
-std::string Mat2X3::String(MatchState* state) const {
-  const std::string T = state->TypeName();
-  return "mat2x3<" + T + ">";
-}
 
 /// TypeMatcher for 'type mat2x4'
-class Mat2X4 : public TypeMatcher {
- public:
-  /// Checks whether the given type matches the matcher rules.
-  /// Match may define and refine the template types and numbers in state.
-  /// @param state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+constexpr TypeMatcher kMat2X4Matcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+  const Type* T = nullptr;
+    if (!match_mat2x4(state, ty, T)) {
+      return nullptr;
+    }
+    T = state.Type(T);
+    if (T == nullptr) {
+      return nullptr;
+    }
+    return build_mat2x4(state, T);
+  },
+/* string */ [](MatchState* state) -> std::string {
+  const std::string T = state->TypeName();
+    return "mat2x4<" + T + ">";
+  }
 };
 
-const type::Type* Mat2X4::Match(MatchState& state, const type::Type* ty) const {
-  const type::Type* T = nullptr;
-  if (!match_mat2x4(state, ty, T)) {
-    return nullptr;
-  }
-  T = state.Type(T);
-  if (T == nullptr) {
-    return nullptr;
-  }
-  return build_mat2x4(state, T);
-}
-
-std::string Mat2X4::String(MatchState* state) const {
-  const std::string T = state->TypeName();
-  return "mat2x4<" + T + ">";
-}
 
 /// TypeMatcher for 'type mat3x2'
-class Mat3X2 : public TypeMatcher {
- public:
-  /// Checks whether the given type matches the matcher rules.
-  /// Match may define and refine the template types and numbers in state.
-  /// @param state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+constexpr TypeMatcher kMat3X2Matcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+  const Type* T = nullptr;
+    if (!match_mat3x2(state, ty, T)) {
+      return nullptr;
+    }
+    T = state.Type(T);
+    if (T == nullptr) {
+      return nullptr;
+    }
+    return build_mat3x2(state, T);
+  },
+/* string */ [](MatchState* state) -> std::string {
+  const std::string T = state->TypeName();
+    return "mat3x2<" + T + ">";
+  }
 };
 
-const type::Type* Mat3X2::Match(MatchState& state, const type::Type* ty) const {
-  const type::Type* T = nullptr;
-  if (!match_mat3x2(state, ty, T)) {
-    return nullptr;
-  }
-  T = state.Type(T);
-  if (T == nullptr) {
-    return nullptr;
-  }
-  return build_mat3x2(state, T);
-}
-
-std::string Mat3X2::String(MatchState* state) const {
-  const std::string T = state->TypeName();
-  return "mat3x2<" + T + ">";
-}
 
 /// TypeMatcher for 'type mat3x3'
-class Mat3X3 : public TypeMatcher {
- public:
-  /// Checks whether the given type matches the matcher rules.
-  /// Match may define and refine the template types and numbers in state.
-  /// @param state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+constexpr TypeMatcher kMat3X3Matcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+  const Type* T = nullptr;
+    if (!match_mat3x3(state, ty, T)) {
+      return nullptr;
+    }
+    T = state.Type(T);
+    if (T == nullptr) {
+      return nullptr;
+    }
+    return build_mat3x3(state, T);
+  },
+/* string */ [](MatchState* state) -> std::string {
+  const std::string T = state->TypeName();
+    return "mat3x3<" + T + ">";
+  }
 };
 
-const type::Type* Mat3X3::Match(MatchState& state, const type::Type* ty) const {
-  const type::Type* T = nullptr;
-  if (!match_mat3x3(state, ty, T)) {
-    return nullptr;
-  }
-  T = state.Type(T);
-  if (T == nullptr) {
-    return nullptr;
-  }
-  return build_mat3x3(state, T);
-}
-
-std::string Mat3X3::String(MatchState* state) const {
-  const std::string T = state->TypeName();
-  return "mat3x3<" + T + ">";
-}
 
 /// TypeMatcher for 'type mat3x4'
-class Mat3X4 : public TypeMatcher {
- public:
-  /// Checks whether the given type matches the matcher rules.
-  /// Match may define and refine the template types and numbers in state.
-  /// @param state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+constexpr TypeMatcher kMat3X4Matcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+  const Type* T = nullptr;
+    if (!match_mat3x4(state, ty, T)) {
+      return nullptr;
+    }
+    T = state.Type(T);
+    if (T == nullptr) {
+      return nullptr;
+    }
+    return build_mat3x4(state, T);
+  },
+/* string */ [](MatchState* state) -> std::string {
+  const std::string T = state->TypeName();
+    return "mat3x4<" + T + ">";
+  }
 };
 
-const type::Type* Mat3X4::Match(MatchState& state, const type::Type* ty) const {
-  const type::Type* T = nullptr;
-  if (!match_mat3x4(state, ty, T)) {
-    return nullptr;
-  }
-  T = state.Type(T);
-  if (T == nullptr) {
-    return nullptr;
-  }
-  return build_mat3x4(state, T);
-}
-
-std::string Mat3X4::String(MatchState* state) const {
-  const std::string T = state->TypeName();
-  return "mat3x4<" + T + ">";
-}
 
 /// TypeMatcher for 'type mat4x2'
-class Mat4X2 : public TypeMatcher {
- public:
-  /// Checks whether the given type matches the matcher rules.
-  /// Match may define and refine the template types and numbers in state.
-  /// @param state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+constexpr TypeMatcher kMat4X2Matcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+  const Type* T = nullptr;
+    if (!match_mat4x2(state, ty, T)) {
+      return nullptr;
+    }
+    T = state.Type(T);
+    if (T == nullptr) {
+      return nullptr;
+    }
+    return build_mat4x2(state, T);
+  },
+/* string */ [](MatchState* state) -> std::string {
+  const std::string T = state->TypeName();
+    return "mat4x2<" + T + ">";
+  }
 };
 
-const type::Type* Mat4X2::Match(MatchState& state, const type::Type* ty) const {
-  const type::Type* T = nullptr;
-  if (!match_mat4x2(state, ty, T)) {
-    return nullptr;
-  }
-  T = state.Type(T);
-  if (T == nullptr) {
-    return nullptr;
-  }
-  return build_mat4x2(state, T);
-}
-
-std::string Mat4X2::String(MatchState* state) const {
-  const std::string T = state->TypeName();
-  return "mat4x2<" + T + ">";
-}
 
 /// TypeMatcher for 'type mat4x3'
-class Mat4X3 : public TypeMatcher {
- public:
-  /// Checks whether the given type matches the matcher rules.
-  /// Match may define and refine the template types and numbers in state.
-  /// @param state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+constexpr TypeMatcher kMat4X3Matcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+  const Type* T = nullptr;
+    if (!match_mat4x3(state, ty, T)) {
+      return nullptr;
+    }
+    T = state.Type(T);
+    if (T == nullptr) {
+      return nullptr;
+    }
+    return build_mat4x3(state, T);
+  },
+/* string */ [](MatchState* state) -> std::string {
+  const std::string T = state->TypeName();
+    return "mat4x3<" + T + ">";
+  }
 };
 
-const type::Type* Mat4X3::Match(MatchState& state, const type::Type* ty) const {
-  const type::Type* T = nullptr;
-  if (!match_mat4x3(state, ty, T)) {
-    return nullptr;
-  }
-  T = state.Type(T);
-  if (T == nullptr) {
-    return nullptr;
-  }
-  return build_mat4x3(state, T);
-}
-
-std::string Mat4X3::String(MatchState* state) const {
-  const std::string T = state->TypeName();
-  return "mat4x3<" + T + ">";
-}
 
 /// TypeMatcher for 'type mat4x4'
-class Mat4X4 : public TypeMatcher {
- public:
-  /// Checks whether the given type matches the matcher rules.
-  /// Match may define and refine the template types and numbers in state.
-  /// @param state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+constexpr TypeMatcher kMat4X4Matcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+  const Type* T = nullptr;
+    if (!match_mat4x4(state, ty, T)) {
+      return nullptr;
+    }
+    T = state.Type(T);
+    if (T == nullptr) {
+      return nullptr;
+    }
+    return build_mat4x4(state, T);
+  },
+/* string */ [](MatchState* state) -> std::string {
+  const std::string T = state->TypeName();
+    return "mat4x4<" + T + ">";
+  }
 };
 
-const type::Type* Mat4X4::Match(MatchState& state, const type::Type* ty) const {
-  const type::Type* T = nullptr;
-  if (!match_mat4x4(state, ty, T)) {
-    return nullptr;
-  }
-  T = state.Type(T);
-  if (T == nullptr) {
-    return nullptr;
-  }
-  return build_mat4x4(state, T);
-}
-
-std::string Mat4X4::String(MatchState* state) const {
-  const std::string T = state->TypeName();
-  return "mat4x4<" + T + ">";
-}
 
 /// TypeMatcher for 'type vec'
-class Vec : public TypeMatcher {
- public:
-  /// Checks whether the given type matches the matcher rules.
-  /// Match may define and refine the template types and numbers in state.
-  /// @param state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
-};
-
-const type::Type* Vec::Match(MatchState& state, const type::Type* ty) const {
+constexpr TypeMatcher kVecMatcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
   Number N = Number::invalid;
-  const type::Type* T = nullptr;
-  if (!match_vec(state, ty, N, T)) {
-    return nullptr;
-  }
-  N = state.Num(N);
-  if (!N.IsValid()) {
-    return nullptr;
-  }
-  T = state.Type(T);
-  if (T == nullptr) {
-    return nullptr;
-  }
-  return build_vec(state, N, T);
-}
-
-std::string Vec::String(MatchState* state) const {
+  const Type* T = nullptr;
+    if (!match_vec(state, ty, N, T)) {
+      return nullptr;
+    }
+    N = state.Num(N);
+    if (!N.IsValid()) {
+      return nullptr;
+    }
+    T = state.Type(T);
+    if (T == nullptr) {
+      return nullptr;
+    }
+    return build_vec(state, N, T);
+  },
+/* string */ [](MatchState* state) -> std::string {
   const std::string N = state->NumName();
   const std::string T = state->TypeName();
-  StringStream ss;
-  ss << "vec" << N << "<" << T << ">";
-  return ss.str();
-}
-
-/// TypeMatcher for 'type mat'
-class Mat : public TypeMatcher {
- public:
-  /// Checks whether the given type matches the matcher rules.
-  /// Match may define and refine the template types and numbers in state.
-  /// @param state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+    StringStream ss;
+    ss << "vec" << N << "<" << T << ">";
+    return ss.str();
+  }
 };
 
-const type::Type* Mat::Match(MatchState& state, const type::Type* ty) const {
+
+/// TypeMatcher for 'type mat'
+constexpr TypeMatcher kMatMatcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
   Number N = Number::invalid;
   Number M = Number::invalid;
-  const type::Type* T = nullptr;
-  if (!match_mat(state, ty, N, M, T)) {
-    return nullptr;
-  }
-  N = state.Num(N);
-  if (!N.IsValid()) {
-    return nullptr;
-  }
-  M = state.Num(M);
-  if (!M.IsValid()) {
-    return nullptr;
-  }
-  T = state.Type(T);
-  if (T == nullptr) {
-    return nullptr;
-  }
-  return build_mat(state, N, M, T);
-}
-
-std::string Mat::String(MatchState* state) const {
+  const Type* T = nullptr;
+    if (!match_mat(state, ty, N, M, T)) {
+      return nullptr;
+    }
+    N = state.Num(N);
+    if (!N.IsValid()) {
+      return nullptr;
+    }
+    M = state.Num(M);
+    if (!M.IsValid()) {
+      return nullptr;
+    }
+    T = state.Type(T);
+    if (T == nullptr) {
+      return nullptr;
+    }
+    return build_mat(state, N, M, T);
+  },
+/* string */ [](MatchState* state) -> std::string {
   const std::string N = state->NumName();
   const std::string M = state->NumName();
   const std::string T = state->TypeName();
-  StringStream ss;
-  ss << "mat" << N << "x" << M << "<" << T << ">";
-  return ss.str();
-}
-
-/// TypeMatcher for 'type ptr'
-class Ptr : public TypeMatcher {
- public:
-  /// Checks whether the given type matches the matcher rules.
-  /// Match may define and refine the template types and numbers in state.
-  /// @param state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+    StringStream ss;
+    ss << "mat" << N << "x" << M << "<" << T << ">";
+    return ss.str();
+  }
 };
 
-const type::Type* Ptr::Match(MatchState& state, const type::Type* ty) const {
-  Number S = Number::invalid;
-  const type::Type* T = nullptr;
-  Number A = Number::invalid;
-  if (!match_ptr(state, ty, S, T, A)) {
-    return nullptr;
-  }
-  S = state.Num(S);
-  if (!S.IsValid()) {
-    return nullptr;
-  }
-  T = state.Type(T);
-  if (T == nullptr) {
-    return nullptr;
-  }
-  A = state.Num(A);
-  if (!A.IsValid()) {
-    return nullptr;
-  }
-  return build_ptr(state, S, T, A);
-}
 
-std::string Ptr::String(MatchState* state) const {
+/// TypeMatcher for 'type ptr'
+constexpr TypeMatcher kPtrMatcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+  Number S = Number::invalid;
+  const Type* T = nullptr;
+  Number A = Number::invalid;
+    if (!match_ptr(state, ty, S, T, A)) {
+      return nullptr;
+    }
+    S = state.Num(S);
+    if (!S.IsValid()) {
+      return nullptr;
+    }
+    T = state.Type(T);
+    if (T == nullptr) {
+      return nullptr;
+    }
+    A = state.Num(A);
+    if (!A.IsValid()) {
+      return nullptr;
+    }
+    return build_ptr(state, S, T, A);
+  },
+/* string */ [](MatchState* state) -> std::string {
   const std::string S = state->NumName();
   const std::string T = state->TypeName();
   const std::string A = state->NumName();
-  return "ptr<" + S + ", " + T + ", " + A + ">";
-}
+    return "ptr<" + S + ", " + T + ", " + A + ">";
+  }
+};
+
 
 /// TypeMatcher for 'type atomic'
-class Atomic : public TypeMatcher {
- public:
-  /// Checks whether the given type matches the matcher rules.
-  /// Match may define and refine the template types and numbers in state.
-  /// @param state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+constexpr TypeMatcher kAtomicMatcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+  const Type* T = nullptr;
+    if (!match_atomic(state, ty, T)) {
+      return nullptr;
+    }
+    T = state.Type(T);
+    if (T == nullptr) {
+      return nullptr;
+    }
+    return build_atomic(state, T);
+  },
+/* string */ [](MatchState* state) -> std::string {
+  const std::string T = state->TypeName();
+    return "atomic<" + T + ">";
+  }
 };
 
-const type::Type* Atomic::Match(MatchState& state, const type::Type* ty) const {
-  const type::Type* T = nullptr;
-  if (!match_atomic(state, ty, T)) {
-    return nullptr;
-  }
-  T = state.Type(T);
-  if (T == nullptr) {
-    return nullptr;
-  }
-  return build_atomic(state, T);
-}
-
-std::string Atomic::String(MatchState* state) const {
-  const std::string T = state->TypeName();
-  return "atomic<" + T + ">";
-}
 
 /// TypeMatcher for 'type array'
-class Array : public TypeMatcher {
- public:
-  /// Checks whether the given type matches the matcher rules.
-  /// Match may define and refine the template types and numbers in state.
-  /// @param state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+constexpr TypeMatcher kArrayMatcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+  const Type* T = nullptr;
+    if (!match_array(state, ty, T)) {
+      return nullptr;
+    }
+    T = state.Type(T);
+    if (T == nullptr) {
+      return nullptr;
+    }
+    return build_array(state, T);
+  },
+/* string */ [](MatchState* state) -> std::string {
+  const std::string T = state->TypeName();
+    return "array<" + T + ">";
+  }
 };
 
-const type::Type* Array::Match(MatchState& state, const type::Type* ty) const {
-  const type::Type* T = nullptr;
-  if (!match_array(state, ty, T)) {
-    return nullptr;
-  }
-  T = state.Type(T);
-  if (T == nullptr) {
-    return nullptr;
-  }
-  return build_array(state, T);
-}
-
-std::string Array::String(MatchState* state) const {
-  const std::string T = state->TypeName();
-  return "array<" + T + ">";
-}
 
 /// TypeMatcher for 'type sampler'
-class Sampler : public TypeMatcher {
- public:
-  /// Checks whether the given type matches the matcher rules.
-  /// Match may define and refine the template types and numbers in state.
-  /// @param state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+constexpr TypeMatcher kSamplerMatcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+    if (!match_sampler(state, ty)) {
+      return nullptr;
+    }
+    return build_sampler(state);
+  },
+/* string */ [](MatchState*) -> std::string {
+    return "sampler";
+  }
 };
 
-const type::Type* Sampler::Match(MatchState& state, const type::Type* ty) const {
-  if (!match_sampler(state, ty)) {
-    return nullptr;
-  }
-  return build_sampler(state);
-}
-
-std::string Sampler::String(MatchState*) const {
-  return "sampler";
-}
 
 /// TypeMatcher for 'type sampler_comparison'
-class SamplerComparison : public TypeMatcher {
- public:
-  /// Checks whether the given type matches the matcher rules.
-  /// Match may define and refine the template types and numbers in state.
-  /// @param state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+constexpr TypeMatcher kSamplerComparisonMatcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+    if (!match_sampler_comparison(state, ty)) {
+      return nullptr;
+    }
+    return build_sampler_comparison(state);
+  },
+/* string */ [](MatchState*) -> std::string {
+    return "sampler_comparison";
+  }
 };
 
-const type::Type* SamplerComparison::Match(MatchState& state, const type::Type* ty) const {
-  if (!match_sampler_comparison(state, ty)) {
-    return nullptr;
-  }
-  return build_sampler_comparison(state);
-}
-
-std::string SamplerComparison::String(MatchState*) const {
-  return "sampler_comparison";
-}
 
 /// TypeMatcher for 'type texture_1d'
-class Texture1D : public TypeMatcher {
- public:
-  /// Checks whether the given type matches the matcher rules.
-  /// Match may define and refine the template types and numbers in state.
-  /// @param state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+constexpr TypeMatcher kTexture1DMatcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+  const Type* T = nullptr;
+    if (!match_texture_1d(state, ty, T)) {
+      return nullptr;
+    }
+    T = state.Type(T);
+    if (T == nullptr) {
+      return nullptr;
+    }
+    return build_texture_1d(state, T);
+  },
+/* string */ [](MatchState* state) -> std::string {
+  const std::string T = state->TypeName();
+    return "texture_1d<" + T + ">";
+  }
 };
 
-const type::Type* Texture1D::Match(MatchState& state, const type::Type* ty) const {
-  const type::Type* T = nullptr;
-  if (!match_texture_1d(state, ty, T)) {
-    return nullptr;
-  }
-  T = state.Type(T);
-  if (T == nullptr) {
-    return nullptr;
-  }
-  return build_texture_1d(state, T);
-}
-
-std::string Texture1D::String(MatchState* state) const {
-  const std::string T = state->TypeName();
-  return "texture_1d<" + T + ">";
-}
 
 /// TypeMatcher for 'type texture_2d'
-class Texture2D : public TypeMatcher {
- public:
-  /// Checks whether the given type matches the matcher rules.
-  /// Match may define and refine the template types and numbers in state.
-  /// @param state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+constexpr TypeMatcher kTexture2DMatcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+  const Type* T = nullptr;
+    if (!match_texture_2d(state, ty, T)) {
+      return nullptr;
+    }
+    T = state.Type(T);
+    if (T == nullptr) {
+      return nullptr;
+    }
+    return build_texture_2d(state, T);
+  },
+/* string */ [](MatchState* state) -> std::string {
+  const std::string T = state->TypeName();
+    return "texture_2d<" + T + ">";
+  }
 };
 
-const type::Type* Texture2D::Match(MatchState& state, const type::Type* ty) const {
-  const type::Type* T = nullptr;
-  if (!match_texture_2d(state, ty, T)) {
-    return nullptr;
-  }
-  T = state.Type(T);
-  if (T == nullptr) {
-    return nullptr;
-  }
-  return build_texture_2d(state, T);
-}
-
-std::string Texture2D::String(MatchState* state) const {
-  const std::string T = state->TypeName();
-  return "texture_2d<" + T + ">";
-}
 
 /// TypeMatcher for 'type texture_2d_array'
-class Texture2DArray : public TypeMatcher {
- public:
-  /// Checks whether the given type matches the matcher rules.
-  /// Match may define and refine the template types and numbers in state.
-  /// @param state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+constexpr TypeMatcher kTexture2DArrayMatcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+  const Type* T = nullptr;
+    if (!match_texture_2d_array(state, ty, T)) {
+      return nullptr;
+    }
+    T = state.Type(T);
+    if (T == nullptr) {
+      return nullptr;
+    }
+    return build_texture_2d_array(state, T);
+  },
+/* string */ [](MatchState* state) -> std::string {
+  const std::string T = state->TypeName();
+    return "texture_2d_array<" + T + ">";
+  }
 };
 
-const type::Type* Texture2DArray::Match(MatchState& state, const type::Type* ty) const {
-  const type::Type* T = nullptr;
-  if (!match_texture_2d_array(state, ty, T)) {
-    return nullptr;
-  }
-  T = state.Type(T);
-  if (T == nullptr) {
-    return nullptr;
-  }
-  return build_texture_2d_array(state, T);
-}
-
-std::string Texture2DArray::String(MatchState* state) const {
-  const std::string T = state->TypeName();
-  return "texture_2d_array<" + T + ">";
-}
 
 /// TypeMatcher for 'type texture_3d'
-class Texture3D : public TypeMatcher {
- public:
-  /// Checks whether the given type matches the matcher rules.
-  /// Match may define and refine the template types and numbers in state.
-  /// @param state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+constexpr TypeMatcher kTexture3DMatcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+  const Type* T = nullptr;
+    if (!match_texture_3d(state, ty, T)) {
+      return nullptr;
+    }
+    T = state.Type(T);
+    if (T == nullptr) {
+      return nullptr;
+    }
+    return build_texture_3d(state, T);
+  },
+/* string */ [](MatchState* state) -> std::string {
+  const std::string T = state->TypeName();
+    return "texture_3d<" + T + ">";
+  }
 };
 
-const type::Type* Texture3D::Match(MatchState& state, const type::Type* ty) const {
-  const type::Type* T = nullptr;
-  if (!match_texture_3d(state, ty, T)) {
-    return nullptr;
-  }
-  T = state.Type(T);
-  if (T == nullptr) {
-    return nullptr;
-  }
-  return build_texture_3d(state, T);
-}
-
-std::string Texture3D::String(MatchState* state) const {
-  const std::string T = state->TypeName();
-  return "texture_3d<" + T + ">";
-}
 
 /// TypeMatcher for 'type texture_cube'
-class TextureCube : public TypeMatcher {
- public:
-  /// Checks whether the given type matches the matcher rules.
-  /// Match may define and refine the template types and numbers in state.
-  /// @param state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+constexpr TypeMatcher kTextureCubeMatcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+  const Type* T = nullptr;
+    if (!match_texture_cube(state, ty, T)) {
+      return nullptr;
+    }
+    T = state.Type(T);
+    if (T == nullptr) {
+      return nullptr;
+    }
+    return build_texture_cube(state, T);
+  },
+/* string */ [](MatchState* state) -> std::string {
+  const std::string T = state->TypeName();
+    return "texture_cube<" + T + ">";
+  }
 };
 
-const type::Type* TextureCube::Match(MatchState& state, const type::Type* ty) const {
-  const type::Type* T = nullptr;
-  if (!match_texture_cube(state, ty, T)) {
-    return nullptr;
-  }
-  T = state.Type(T);
-  if (T == nullptr) {
-    return nullptr;
-  }
-  return build_texture_cube(state, T);
-}
-
-std::string TextureCube::String(MatchState* state) const {
-  const std::string T = state->TypeName();
-  return "texture_cube<" + T + ">";
-}
 
 /// TypeMatcher for 'type texture_cube_array'
-class TextureCubeArray : public TypeMatcher {
- public:
-  /// Checks whether the given type matches the matcher rules.
-  /// Match may define and refine the template types and numbers in state.
-  /// @param state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+constexpr TypeMatcher kTextureCubeArrayMatcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+  const Type* T = nullptr;
+    if (!match_texture_cube_array(state, ty, T)) {
+      return nullptr;
+    }
+    T = state.Type(T);
+    if (T == nullptr) {
+      return nullptr;
+    }
+    return build_texture_cube_array(state, T);
+  },
+/* string */ [](MatchState* state) -> std::string {
+  const std::string T = state->TypeName();
+    return "texture_cube_array<" + T + ">";
+  }
 };
 
-const type::Type* TextureCubeArray::Match(MatchState& state, const type::Type* ty) const {
-  const type::Type* T = nullptr;
-  if (!match_texture_cube_array(state, ty, T)) {
-    return nullptr;
-  }
-  T = state.Type(T);
-  if (T == nullptr) {
-    return nullptr;
-  }
-  return build_texture_cube_array(state, T);
-}
-
-std::string TextureCubeArray::String(MatchState* state) const {
-  const std::string T = state->TypeName();
-  return "texture_cube_array<" + T + ">";
-}
 
 /// TypeMatcher for 'type texture_multisampled_2d'
-class TextureMultisampled2D : public TypeMatcher {
- public:
-  /// Checks whether the given type matches the matcher rules.
-  /// Match may define and refine the template types and numbers in state.
-  /// @param state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+constexpr TypeMatcher kTextureMultisampled2DMatcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+  const Type* T = nullptr;
+    if (!match_texture_multisampled_2d(state, ty, T)) {
+      return nullptr;
+    }
+    T = state.Type(T);
+    if (T == nullptr) {
+      return nullptr;
+    }
+    return build_texture_multisampled_2d(state, T);
+  },
+/* string */ [](MatchState* state) -> std::string {
+  const std::string T = state->TypeName();
+    return "texture_multisampled_2d<" + T + ">";
+  }
 };
 
-const type::Type* TextureMultisampled2D::Match(MatchState& state, const type::Type* ty) const {
-  const type::Type* T = nullptr;
-  if (!match_texture_multisampled_2d(state, ty, T)) {
-    return nullptr;
-  }
-  T = state.Type(T);
-  if (T == nullptr) {
-    return nullptr;
-  }
-  return build_texture_multisampled_2d(state, T);
-}
-
-std::string TextureMultisampled2D::String(MatchState* state) const {
-  const std::string T = state->TypeName();
-  return "texture_multisampled_2d<" + T + ">";
-}
 
 /// TypeMatcher for 'type texture_depth_2d'
-class TextureDepth2D : public TypeMatcher {
- public:
-  /// Checks whether the given type matches the matcher rules.
-  /// Match may define and refine the template types and numbers in state.
-  /// @param state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+constexpr TypeMatcher kTextureDepth2DMatcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+    if (!match_texture_depth_2d(state, ty)) {
+      return nullptr;
+    }
+    return build_texture_depth_2d(state);
+  },
+/* string */ [](MatchState*) -> std::string {
+    return "texture_depth_2d";
+  }
 };
 
-const type::Type* TextureDepth2D::Match(MatchState& state, const type::Type* ty) const {
-  if (!match_texture_depth_2d(state, ty)) {
-    return nullptr;
-  }
-  return build_texture_depth_2d(state);
-}
-
-std::string TextureDepth2D::String(MatchState*) const {
-  return "texture_depth_2d";
-}
 
 /// TypeMatcher for 'type texture_depth_2d_array'
-class TextureDepth2DArray : public TypeMatcher {
- public:
-  /// Checks whether the given type matches the matcher rules.
-  /// Match may define and refine the template types and numbers in state.
-  /// @param state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+constexpr TypeMatcher kTextureDepth2DArrayMatcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+    if (!match_texture_depth_2d_array(state, ty)) {
+      return nullptr;
+    }
+    return build_texture_depth_2d_array(state);
+  },
+/* string */ [](MatchState*) -> std::string {
+    return "texture_depth_2d_array";
+  }
 };
 
-const type::Type* TextureDepth2DArray::Match(MatchState& state, const type::Type* ty) const {
-  if (!match_texture_depth_2d_array(state, ty)) {
-    return nullptr;
-  }
-  return build_texture_depth_2d_array(state);
-}
-
-std::string TextureDepth2DArray::String(MatchState*) const {
-  return "texture_depth_2d_array";
-}
 
 /// TypeMatcher for 'type texture_depth_cube'
-class TextureDepthCube : public TypeMatcher {
- public:
-  /// Checks whether the given type matches the matcher rules.
-  /// Match may define and refine the template types and numbers in state.
-  /// @param state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+constexpr TypeMatcher kTextureDepthCubeMatcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+    if (!match_texture_depth_cube(state, ty)) {
+      return nullptr;
+    }
+    return build_texture_depth_cube(state);
+  },
+/* string */ [](MatchState*) -> std::string {
+    return "texture_depth_cube";
+  }
 };
 
-const type::Type* TextureDepthCube::Match(MatchState& state, const type::Type* ty) const {
-  if (!match_texture_depth_cube(state, ty)) {
-    return nullptr;
-  }
-  return build_texture_depth_cube(state);
-}
-
-std::string TextureDepthCube::String(MatchState*) const {
-  return "texture_depth_cube";
-}
 
 /// TypeMatcher for 'type texture_depth_cube_array'
-class TextureDepthCubeArray : public TypeMatcher {
- public:
-  /// Checks whether the given type matches the matcher rules.
-  /// Match may define and refine the template types and numbers in state.
-  /// @param state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+constexpr TypeMatcher kTextureDepthCubeArrayMatcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+    if (!match_texture_depth_cube_array(state, ty)) {
+      return nullptr;
+    }
+    return build_texture_depth_cube_array(state);
+  },
+/* string */ [](MatchState*) -> std::string {
+    return "texture_depth_cube_array";
+  }
 };
 
-const type::Type* TextureDepthCubeArray::Match(MatchState& state, const type::Type* ty) const {
-  if (!match_texture_depth_cube_array(state, ty)) {
-    return nullptr;
-  }
-  return build_texture_depth_cube_array(state);
-}
-
-std::string TextureDepthCubeArray::String(MatchState*) const {
-  return "texture_depth_cube_array";
-}
 
 /// TypeMatcher for 'type texture_depth_multisampled_2d'
-class TextureDepthMultisampled2D : public TypeMatcher {
- public:
-  /// Checks whether the given type matches the matcher rules.
-  /// Match may define and refine the template types and numbers in state.
-  /// @param state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+constexpr TypeMatcher kTextureDepthMultisampled2DMatcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+    if (!match_texture_depth_multisampled_2d(state, ty)) {
+      return nullptr;
+    }
+    return build_texture_depth_multisampled_2d(state);
+  },
+/* string */ [](MatchState*) -> std::string {
+    return "texture_depth_multisampled_2d";
+  }
 };
 
-const type::Type* TextureDepthMultisampled2D::Match(MatchState& state, const type::Type* ty) const {
-  if (!match_texture_depth_multisampled_2d(state, ty)) {
-    return nullptr;
-  }
-  return build_texture_depth_multisampled_2d(state);
-}
-
-std::string TextureDepthMultisampled2D::String(MatchState*) const {
-  return "texture_depth_multisampled_2d";
-}
 
 /// TypeMatcher for 'type texture_storage_1d'
-class TextureStorage1D : public TypeMatcher {
- public:
-  /// Checks whether the given type matches the matcher rules.
-  /// Match may define and refine the template types and numbers in state.
-  /// @param state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
-};
-
-const type::Type* TextureStorage1D::Match(MatchState& state, const type::Type* ty) const {
+constexpr TypeMatcher kTextureStorage1DMatcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
   Number F = Number::invalid;
   Number A = Number::invalid;
-  if (!match_texture_storage_1d(state, ty, F, A)) {
-    return nullptr;
-  }
-  F = state.Num(F);
-  if (!F.IsValid()) {
-    return nullptr;
-  }
-  A = state.Num(A);
-  if (!A.IsValid()) {
-    return nullptr;
-  }
-  return build_texture_storage_1d(state, F, A);
-}
-
-std::string TextureStorage1D::String(MatchState* state) const {
+    if (!match_texture_storage_1d(state, ty, F, A)) {
+      return nullptr;
+    }
+    F = state.Num(F);
+    if (!F.IsValid()) {
+      return nullptr;
+    }
+    A = state.Num(A);
+    if (!A.IsValid()) {
+      return nullptr;
+    }
+    return build_texture_storage_1d(state, F, A);
+  },
+/* string */ [](MatchState* state) -> std::string {
   const std::string F = state->NumName();
   const std::string A = state->NumName();
-  return "texture_storage_1d<" + F + ", " + A + ">";
-}
+    return "texture_storage_1d<" + F + ", " + A + ">";
+  }
+};
+
 
 /// TypeMatcher for 'type texture_storage_2d'
-class TextureStorage2D : public TypeMatcher {
- public:
-  /// Checks whether the given type matches the matcher rules.
-  /// Match may define and refine the template types and numbers in state.
-  /// @param state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
-};
-
-const type::Type* TextureStorage2D::Match(MatchState& state, const type::Type* ty) const {
+constexpr TypeMatcher kTextureStorage2DMatcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
   Number F = Number::invalid;
   Number A = Number::invalid;
-  if (!match_texture_storage_2d(state, ty, F, A)) {
-    return nullptr;
-  }
-  F = state.Num(F);
-  if (!F.IsValid()) {
-    return nullptr;
-  }
-  A = state.Num(A);
-  if (!A.IsValid()) {
-    return nullptr;
-  }
-  return build_texture_storage_2d(state, F, A);
-}
-
-std::string TextureStorage2D::String(MatchState* state) const {
+    if (!match_texture_storage_2d(state, ty, F, A)) {
+      return nullptr;
+    }
+    F = state.Num(F);
+    if (!F.IsValid()) {
+      return nullptr;
+    }
+    A = state.Num(A);
+    if (!A.IsValid()) {
+      return nullptr;
+    }
+    return build_texture_storage_2d(state, F, A);
+  },
+/* string */ [](MatchState* state) -> std::string {
   const std::string F = state->NumName();
   const std::string A = state->NumName();
-  return "texture_storage_2d<" + F + ", " + A + ">";
-}
+    return "texture_storage_2d<" + F + ", " + A + ">";
+  }
+};
+
 
 /// TypeMatcher for 'type texture_storage_2d_array'
-class TextureStorage2DArray : public TypeMatcher {
- public:
-  /// Checks whether the given type matches the matcher rules.
-  /// Match may define and refine the template types and numbers in state.
-  /// @param state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
-};
-
-const type::Type* TextureStorage2DArray::Match(MatchState& state, const type::Type* ty) const {
+constexpr TypeMatcher kTextureStorage2DArrayMatcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
   Number F = Number::invalid;
   Number A = Number::invalid;
-  if (!match_texture_storage_2d_array(state, ty, F, A)) {
-    return nullptr;
-  }
-  F = state.Num(F);
-  if (!F.IsValid()) {
-    return nullptr;
-  }
-  A = state.Num(A);
-  if (!A.IsValid()) {
-    return nullptr;
-  }
-  return build_texture_storage_2d_array(state, F, A);
-}
-
-std::string TextureStorage2DArray::String(MatchState* state) const {
+    if (!match_texture_storage_2d_array(state, ty, F, A)) {
+      return nullptr;
+    }
+    F = state.Num(F);
+    if (!F.IsValid()) {
+      return nullptr;
+    }
+    A = state.Num(A);
+    if (!A.IsValid()) {
+      return nullptr;
+    }
+    return build_texture_storage_2d_array(state, F, A);
+  },
+/* string */ [](MatchState* state) -> std::string {
   const std::string F = state->NumName();
   const std::string A = state->NumName();
-  return "texture_storage_2d_array<" + F + ", " + A + ">";
-}
+    return "texture_storage_2d_array<" + F + ", " + A + ">";
+  }
+};
+
 
 /// TypeMatcher for 'type texture_storage_3d'
-class TextureStorage3D : public TypeMatcher {
- public:
-  /// Checks whether the given type matches the matcher rules.
-  /// Match may define and refine the template types and numbers in state.
-  /// @param state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
-};
-
-const type::Type* TextureStorage3D::Match(MatchState& state, const type::Type* ty) const {
+constexpr TypeMatcher kTextureStorage3DMatcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
   Number F = Number::invalid;
   Number A = Number::invalid;
-  if (!match_texture_storage_3d(state, ty, F, A)) {
-    return nullptr;
-  }
-  F = state.Num(F);
-  if (!F.IsValid()) {
-    return nullptr;
-  }
-  A = state.Num(A);
-  if (!A.IsValid()) {
-    return nullptr;
-  }
-  return build_texture_storage_3d(state, F, A);
-}
-
-std::string TextureStorage3D::String(MatchState* state) const {
+    if (!match_texture_storage_3d(state, ty, F, A)) {
+      return nullptr;
+    }
+    F = state.Num(F);
+    if (!F.IsValid()) {
+      return nullptr;
+    }
+    A = state.Num(A);
+    if (!A.IsValid()) {
+      return nullptr;
+    }
+    return build_texture_storage_3d(state, F, A);
+  },
+/* string */ [](MatchState* state) -> std::string {
   const std::string F = state->NumName();
   const std::string A = state->NumName();
-  return "texture_storage_3d<" + F + ", " + A + ">";
-}
+    return "texture_storage_3d<" + F + ", " + A + ">";
+  }
+};
+
 
 /// TypeMatcher for 'type texture_external'
-class TextureExternal : public TypeMatcher {
- public:
-  /// Checks whether the given type matches the matcher rules.
-  /// Match may define and refine the template types and numbers in state.
-  /// @param state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+constexpr TypeMatcher kTextureExternalMatcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+    if (!match_texture_external(state, ty)) {
+      return nullptr;
+    }
+    return build_texture_external(state);
+  },
+/* string */ [](MatchState*) -> std::string {
+    return "texture_external";
+  }
 };
 
-const type::Type* TextureExternal::Match(MatchState& state, const type::Type* ty) const {
-  if (!match_texture_external(state, ty)) {
-    return nullptr;
-  }
-  return build_texture_external(state);
-}
-
-std::string TextureExternal::String(MatchState*) const {
-  return "texture_external";
-}
 
 /// TypeMatcher for 'type packedVec3'
-class PackedVec3 : public TypeMatcher {
- public:
-  /// Checks whether the given type matches the matcher rules.
-  /// Match may define and refine the template types and numbers in state.
-  /// @param state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+constexpr TypeMatcher kPackedVec3Matcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+  const Type* T = nullptr;
+    if (!match_packedVec3(state, ty, T)) {
+      return nullptr;
+    }
+    T = state.Type(T);
+    if (T == nullptr) {
+      return nullptr;
+    }
+    return build_packedVec3(state, T);
+  },
+/* string */ [](MatchState* state) -> std::string {
+  const std::string T = state->TypeName();
+    return "packedVec3<" + T + ">";
+  }
 };
 
-const type::Type* PackedVec3::Match(MatchState& state, const type::Type* ty) const {
-  const type::Type* T = nullptr;
-  if (!match_packedVec3(state, ty, T)) {
-    return nullptr;
-  }
-  T = state.Type(T);
-  if (T == nullptr) {
-    return nullptr;
-  }
-  return build_packedVec3(state, T);
-}
-
-std::string PackedVec3::String(MatchState* state) const {
-  const std::string T = state->TypeName();
-  return "packedVec3<" + T + ">";
-}
 
 /// TypeMatcher for 'type __modf_result'
-class ModfResult : public TypeMatcher {
- public:
-  /// Checks whether the given type matches the matcher rules.
-  /// Match may define and refine the template types and numbers in state.
-  /// @param state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+constexpr TypeMatcher kModfResultMatcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+  const Type* T = nullptr;
+    if (!match_modf_result(state, ty, T)) {
+      return nullptr;
+    }
+    T = state.Type(T);
+    if (T == nullptr) {
+      return nullptr;
+    }
+    return build_modf_result(state, T);
+  },
+/* string */ [](MatchState* state) -> std::string {
+  const std::string T = state->TypeName();
+    StringStream ss;
+    ss << "__modf_result_" << T;
+    return ss.str();
+  }
 };
 
-const type::Type* ModfResult::Match(MatchState& state, const type::Type* ty) const {
-  const type::Type* T = nullptr;
-  if (!match_modf_result(state, ty, T)) {
-    return nullptr;
-  }
-  T = state.Type(T);
-  if (T == nullptr) {
-    return nullptr;
-  }
-  return build_modf_result(state, T);
-}
-
-std::string ModfResult::String(MatchState* state) const {
-  const std::string T = state->TypeName();
-  StringStream ss;
-  ss << "__modf_result_" << T;
-  return ss.str();
-}
 
 /// TypeMatcher for 'type __modf_result_vec'
-class ModfResultVec : public TypeMatcher {
- public:
-  /// Checks whether the given type matches the matcher rules.
-  /// Match may define and refine the template types and numbers in state.
-  /// @param state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
-};
-
-const type::Type* ModfResultVec::Match(MatchState& state, const type::Type* ty) const {
+constexpr TypeMatcher kModfResultVecMatcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
   Number N = Number::invalid;
-  const type::Type* T = nullptr;
-  if (!match_modf_result_vec(state, ty, N, T)) {
-    return nullptr;
-  }
-  N = state.Num(N);
-  if (!N.IsValid()) {
-    return nullptr;
-  }
-  T = state.Type(T);
-  if (T == nullptr) {
-    return nullptr;
-  }
-  return build_modf_result_vec(state, N, T);
-}
-
-std::string ModfResultVec::String(MatchState* state) const {
+  const Type* T = nullptr;
+    if (!match_modf_result_vec(state, ty, N, T)) {
+      return nullptr;
+    }
+    N = state.Num(N);
+    if (!N.IsValid()) {
+      return nullptr;
+    }
+    T = state.Type(T);
+    if (T == nullptr) {
+      return nullptr;
+    }
+    return build_modf_result_vec(state, N, T);
+  },
+/* string */ [](MatchState* state) -> std::string {
   const std::string N = state->NumName();
   const std::string T = state->TypeName();
-  StringStream ss;
-  ss << "__modf_result_vec" << N << "_" << T;
-  return ss.str();
-}
+    StringStream ss;
+    ss << "__modf_result_vec" << N << "_" << T;
+    return ss.str();
+  }
+};
+
 
 /// TypeMatcher for 'type __frexp_result'
-class FrexpResult : public TypeMatcher {
- public:
-  /// Checks whether the given type matches the matcher rules.
-  /// Match may define and refine the template types and numbers in state.
-  /// @param state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+constexpr TypeMatcher kFrexpResultMatcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+  const Type* T = nullptr;
+    if (!match_frexp_result(state, ty, T)) {
+      return nullptr;
+    }
+    T = state.Type(T);
+    if (T == nullptr) {
+      return nullptr;
+    }
+    return build_frexp_result(state, T);
+  },
+/* string */ [](MatchState* state) -> std::string {
+  const std::string T = state->TypeName();
+    StringStream ss;
+    ss << "__frexp_result_" << T;
+    return ss.str();
+  }
 };
 
-const type::Type* FrexpResult::Match(MatchState& state, const type::Type* ty) const {
-  const type::Type* T = nullptr;
-  if (!match_frexp_result(state, ty, T)) {
-    return nullptr;
-  }
-  T = state.Type(T);
-  if (T == nullptr) {
-    return nullptr;
-  }
-  return build_frexp_result(state, T);
-}
-
-std::string FrexpResult::String(MatchState* state) const {
-  const std::string T = state->TypeName();
-  StringStream ss;
-  ss << "__frexp_result_" << T;
-  return ss.str();
-}
 
 /// TypeMatcher for 'type __frexp_result_vec'
-class FrexpResultVec : public TypeMatcher {
- public:
-  /// Checks whether the given type matches the matcher rules.
-  /// Match may define and refine the template types and numbers in state.
-  /// @param state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
-};
-
-const type::Type* FrexpResultVec::Match(MatchState& state, const type::Type* ty) const {
+constexpr TypeMatcher kFrexpResultVecMatcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
   Number N = Number::invalid;
-  const type::Type* T = nullptr;
-  if (!match_frexp_result_vec(state, ty, N, T)) {
-    return nullptr;
-  }
-  N = state.Num(N);
-  if (!N.IsValid()) {
-    return nullptr;
-  }
-  T = state.Type(T);
-  if (T == nullptr) {
-    return nullptr;
-  }
-  return build_frexp_result_vec(state, N, T);
-}
-
-std::string FrexpResultVec::String(MatchState* state) const {
+  const Type* T = nullptr;
+    if (!match_frexp_result_vec(state, ty, N, T)) {
+      return nullptr;
+    }
+    N = state.Num(N);
+    if (!N.IsValid()) {
+      return nullptr;
+    }
+    T = state.Type(T);
+    if (T == nullptr) {
+      return nullptr;
+    }
+    return build_frexp_result_vec(state, N, T);
+  },
+/* string */ [](MatchState* state) -> std::string {
   const std::string N = state->NumName();
   const std::string T = state->TypeName();
-  StringStream ss;
-  ss << "__frexp_result_vec" << N << "_" << T;
-  return ss.str();
-}
+    StringStream ss;
+    ss << "__frexp_result_vec" << N << "_" << T;
+    return ss.str();
+  }
+};
+
 
 /// TypeMatcher for 'type __atomic_compare_exchange_result'
-class AtomicCompareExchangeResult : public TypeMatcher {
- public:
-  /// Checks whether the given type matches the matcher rules.
-  /// Match may define and refine the template types and numbers in state.
-  /// @param state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+constexpr TypeMatcher kAtomicCompareExchangeResultMatcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+  const Type* T = nullptr;
+    if (!match_atomic_compare_exchange_result(state, ty, T)) {
+      return nullptr;
+    }
+    T = state.Type(T);
+    if (T == nullptr) {
+      return nullptr;
+    }
+    return build_atomic_compare_exchange_result(state, T);
+  },
+/* string */ [](MatchState* state) -> std::string {
+  const std::string T = state->TypeName();
+    return "__atomic_compare_exchange_result<" + T + ">";
+  }
 };
 
-const type::Type* AtomicCompareExchangeResult::Match(MatchState& state, const type::Type* ty) const {
-  const type::Type* T = nullptr;
-  if (!match_atomic_compare_exchange_result(state, ty, T)) {
-    return nullptr;
-  }
-  T = state.Type(T);
-  if (T == nullptr) {
-    return nullptr;
-  }
-  return build_atomic_compare_exchange_result(state, T);
-}
-
-std::string AtomicCompareExchangeResult::String(MatchState* state) const {
-  const std::string T = state->TypeName();
-  return "__atomic_compare_exchange_result<" + T + ">";
-}
 
 /// TypeMatcher for 'match scalar'
-class Scalar : public TypeMatcher {
- public:
-  /// 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 state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+constexpr TypeMatcher kScalarMatcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+    if (match_ia(state, ty)) {
+      return build_ia(state);
+    }
+    if (match_fa(state, ty)) {
+      return build_fa(state);
+    }
+    if (match_i32(state, ty)) {
+      return build_i32(state);
+    }
+    if (match_u32(state, ty)) {
+      return build_u32(state);
+    }
+    if (match_f32(state, ty)) {
+      return build_f32(state);
+    }
+    if (match_f16(state, ty)) {
+      return build_f16(state);
+    }
+    if (match_bool(state, ty)) {
+      return build_bool(state);
+    }
+    return nullptr;
+  },
+/* string */ [](MatchState*) -> std::string {
+    StringStream ss;
+    // Note: We pass nullptr to the TypeMatcher::String() functions, as 'matcher's do not support
+    // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
+    ss << kIaMatcher.string(nullptr) << ", " << kFaMatcher.string(nullptr) << ", " << kF32Matcher.string(nullptr) << ", " << kF16Matcher.string(nullptr) << ", " << kI32Matcher.string(nullptr) << ", " << kU32Matcher.string(nullptr) << " or " << kBoolMatcher.string(nullptr);
+    return ss.str();
+  }
 };
 
-const type::Type* Scalar::Match(MatchState& state, const type::Type* ty) const {
-  if (match_ia(state, ty)) {
-    return build_ia(state);
-  }
-  if (match_fa(state, ty)) {
-    return build_fa(state);
-  }
-  if (match_i32(state, ty)) {
-    return build_i32(state);
-  }
-  if (match_u32(state, ty)) {
-    return build_u32(state);
-  }
-  if (match_f32(state, ty)) {
-    return build_f32(state);
-  }
-  if (match_f16(state, ty)) {
-    return build_f16(state);
-  }
-  if (match_bool(state, ty)) {
-    return build_bool(state);
-  }
-  return nullptr;
-}
-
-std::string Scalar::String(MatchState*) const {
-  StringStream ss;
-  // Note: We pass nullptr to the TypeMatcher::String() functions, as 'matcher's do not support
-  // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
-  ss << Ia().String(nullptr) << ", " << Fa().String(nullptr) << ", " << F32().String(nullptr) << ", " << F16().String(nullptr) << ", " << I32().String(nullptr) << ", " << U32().String(nullptr) << " or " << Bool().String(nullptr);
-  return ss.str();
-}
-
 /// TypeMatcher for 'match concrete_scalar'
-class ConcreteScalar : public TypeMatcher {
- public:
-  /// 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 state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+constexpr TypeMatcher kConcreteScalarMatcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+    if (match_i32(state, ty)) {
+      return build_i32(state);
+    }
+    if (match_u32(state, ty)) {
+      return build_u32(state);
+    }
+    if (match_f32(state, ty)) {
+      return build_f32(state);
+    }
+    if (match_f16(state, ty)) {
+      return build_f16(state);
+    }
+    if (match_bool(state, ty)) {
+      return build_bool(state);
+    }
+    return nullptr;
+  },
+/* string */ [](MatchState*) -> std::string {
+    StringStream ss;
+    // Note: We pass nullptr to the TypeMatcher::String() functions, as 'matcher's do not support
+    // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
+    ss << kF32Matcher.string(nullptr) << ", " << kF16Matcher.string(nullptr) << ", " << kI32Matcher.string(nullptr) << ", " << kU32Matcher.string(nullptr) << " or " << kBoolMatcher.string(nullptr);
+    return ss.str();
+  }
 };
 
-const type::Type* ConcreteScalar::Match(MatchState& state, const type::Type* ty) const {
-  if (match_i32(state, ty)) {
-    return build_i32(state);
-  }
-  if (match_u32(state, ty)) {
-    return build_u32(state);
-  }
-  if (match_f32(state, ty)) {
-    return build_f32(state);
-  }
-  if (match_f16(state, ty)) {
-    return build_f16(state);
-  }
-  if (match_bool(state, ty)) {
-    return build_bool(state);
-  }
-  return nullptr;
-}
-
-std::string ConcreteScalar::String(MatchState*) const {
-  StringStream ss;
-  // Note: We pass nullptr to the TypeMatcher::String() functions, as 'matcher's do not support
-  // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
-  ss << F32().String(nullptr) << ", " << F16().String(nullptr) << ", " << I32().String(nullptr) << ", " << U32().String(nullptr) << " or " << Bool().String(nullptr);
-  return ss.str();
-}
-
 /// TypeMatcher for 'match scalar_no_f32'
-class ScalarNoF32 : public TypeMatcher {
- public:
-  /// 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 state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+constexpr TypeMatcher kScalarNoF32Matcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+    if (match_ia(state, ty)) {
+      return build_ia(state);
+    }
+    if (match_fa(state, ty)) {
+      return build_fa(state);
+    }
+    if (match_i32(state, ty)) {
+      return build_i32(state);
+    }
+    if (match_u32(state, ty)) {
+      return build_u32(state);
+    }
+    if (match_f16(state, ty)) {
+      return build_f16(state);
+    }
+    if (match_bool(state, ty)) {
+      return build_bool(state);
+    }
+    return nullptr;
+  },
+/* string */ [](MatchState*) -> std::string {
+    StringStream ss;
+    // Note: We pass nullptr to the TypeMatcher::String() functions, as 'matcher's do not support
+    // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
+    ss << kIaMatcher.string(nullptr) << ", " << kFaMatcher.string(nullptr) << ", " << kI32Matcher.string(nullptr) << ", " << kF16Matcher.string(nullptr) << ", " << kU32Matcher.string(nullptr) << " or " << kBoolMatcher.string(nullptr);
+    return ss.str();
+  }
 };
 
-const type::Type* ScalarNoF32::Match(MatchState& state, const type::Type* ty) const {
-  if (match_ia(state, ty)) {
-    return build_ia(state);
-  }
-  if (match_fa(state, ty)) {
-    return build_fa(state);
-  }
-  if (match_i32(state, ty)) {
-    return build_i32(state);
-  }
-  if (match_u32(state, ty)) {
-    return build_u32(state);
-  }
-  if (match_f16(state, ty)) {
-    return build_f16(state);
-  }
-  if (match_bool(state, ty)) {
-    return build_bool(state);
-  }
-  return nullptr;
-}
-
-std::string ScalarNoF32::String(MatchState*) const {
-  StringStream ss;
-  // Note: We pass nullptr to the TypeMatcher::String() functions, as 'matcher's do not support
-  // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
-  ss << Ia().String(nullptr) << ", " << Fa().String(nullptr) << ", " << I32().String(nullptr) << ", " << F16().String(nullptr) << ", " << U32().String(nullptr) << " or " << Bool().String(nullptr);
-  return ss.str();
-}
-
 /// TypeMatcher for 'match scalar_no_f16'
-class ScalarNoF16 : public TypeMatcher {
- public:
-  /// 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 state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+constexpr TypeMatcher kScalarNoF16Matcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+    if (match_ia(state, ty)) {
+      return build_ia(state);
+    }
+    if (match_fa(state, ty)) {
+      return build_fa(state);
+    }
+    if (match_i32(state, ty)) {
+      return build_i32(state);
+    }
+    if (match_u32(state, ty)) {
+      return build_u32(state);
+    }
+    if (match_f32(state, ty)) {
+      return build_f32(state);
+    }
+    if (match_bool(state, ty)) {
+      return build_bool(state);
+    }
+    return nullptr;
+  },
+/* string */ [](MatchState*) -> std::string {
+    StringStream ss;
+    // Note: We pass nullptr to the TypeMatcher::String() functions, as 'matcher's do not support
+    // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
+    ss << kIaMatcher.string(nullptr) << ", " << kFaMatcher.string(nullptr) << ", " << kF32Matcher.string(nullptr) << ", " << kI32Matcher.string(nullptr) << ", " << kU32Matcher.string(nullptr) << " or " << kBoolMatcher.string(nullptr);
+    return ss.str();
+  }
 };
 
-const type::Type* ScalarNoF16::Match(MatchState& state, const type::Type* ty) const {
-  if (match_ia(state, ty)) {
-    return build_ia(state);
-  }
-  if (match_fa(state, ty)) {
-    return build_fa(state);
-  }
-  if (match_i32(state, ty)) {
-    return build_i32(state);
-  }
-  if (match_u32(state, ty)) {
-    return build_u32(state);
-  }
-  if (match_f32(state, ty)) {
-    return build_f32(state);
-  }
-  if (match_bool(state, ty)) {
-    return build_bool(state);
-  }
-  return nullptr;
-}
-
-std::string ScalarNoF16::String(MatchState*) const {
-  StringStream ss;
-  // Note: We pass nullptr to the TypeMatcher::String() functions, as 'matcher's do not support
-  // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
-  ss << Ia().String(nullptr) << ", " << Fa().String(nullptr) << ", " << F32().String(nullptr) << ", " << I32().String(nullptr) << ", " << U32().String(nullptr) << " or " << Bool().String(nullptr);
-  return ss.str();
-}
-
 /// TypeMatcher for 'match scalar_no_i32'
-class ScalarNoI32 : public TypeMatcher {
- public:
-  /// 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 state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+constexpr TypeMatcher kScalarNoI32Matcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+    if (match_ia(state, ty)) {
+      return build_ia(state);
+    }
+    if (match_fa(state, ty)) {
+      return build_fa(state);
+    }
+    if (match_u32(state, ty)) {
+      return build_u32(state);
+    }
+    if (match_f32(state, ty)) {
+      return build_f32(state);
+    }
+    if (match_f16(state, ty)) {
+      return build_f16(state);
+    }
+    if (match_bool(state, ty)) {
+      return build_bool(state);
+    }
+    return nullptr;
+  },
+/* string */ [](MatchState*) -> std::string {
+    StringStream ss;
+    // Note: We pass nullptr to the TypeMatcher::String() functions, as 'matcher's do not support
+    // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
+    ss << kIaMatcher.string(nullptr) << ", " << kFaMatcher.string(nullptr) << ", " << kF32Matcher.string(nullptr) << ", " << kF16Matcher.string(nullptr) << ", " << kU32Matcher.string(nullptr) << " or " << kBoolMatcher.string(nullptr);
+    return ss.str();
+  }
 };
 
-const type::Type* ScalarNoI32::Match(MatchState& state, const type::Type* ty) const {
-  if (match_ia(state, ty)) {
-    return build_ia(state);
-  }
-  if (match_fa(state, ty)) {
-    return build_fa(state);
-  }
-  if (match_u32(state, ty)) {
-    return build_u32(state);
-  }
-  if (match_f32(state, ty)) {
-    return build_f32(state);
-  }
-  if (match_f16(state, ty)) {
-    return build_f16(state);
-  }
-  if (match_bool(state, ty)) {
-    return build_bool(state);
-  }
-  return nullptr;
-}
-
-std::string ScalarNoI32::String(MatchState*) const {
-  StringStream ss;
-  // Note: We pass nullptr to the TypeMatcher::String() functions, as 'matcher's do not support
-  // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
-  ss << Ia().String(nullptr) << ", " << Fa().String(nullptr) << ", " << F32().String(nullptr) << ", " << F16().String(nullptr) << ", " << U32().String(nullptr) << " or " << Bool().String(nullptr);
-  return ss.str();
-}
-
 /// TypeMatcher for 'match scalar_no_u32'
-class ScalarNoU32 : public TypeMatcher {
- public:
-  /// 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 state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+constexpr TypeMatcher kScalarNoU32Matcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+    if (match_ia(state, ty)) {
+      return build_ia(state);
+    }
+    if (match_fa(state, ty)) {
+      return build_fa(state);
+    }
+    if (match_i32(state, ty)) {
+      return build_i32(state);
+    }
+    if (match_f32(state, ty)) {
+      return build_f32(state);
+    }
+    if (match_f16(state, ty)) {
+      return build_f16(state);
+    }
+    if (match_bool(state, ty)) {
+      return build_bool(state);
+    }
+    return nullptr;
+  },
+/* string */ [](MatchState*) -> std::string {
+    StringStream ss;
+    // Note: We pass nullptr to the TypeMatcher::String() functions, as 'matcher's do not support
+    // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
+    ss << kIaMatcher.string(nullptr) << ", " << kFaMatcher.string(nullptr) << ", " << kF32Matcher.string(nullptr) << ", " << kF16Matcher.string(nullptr) << ", " << kI32Matcher.string(nullptr) << " or " << kBoolMatcher.string(nullptr);
+    return ss.str();
+  }
 };
 
-const type::Type* ScalarNoU32::Match(MatchState& state, const type::Type* ty) const {
-  if (match_ia(state, ty)) {
-    return build_ia(state);
-  }
-  if (match_fa(state, ty)) {
-    return build_fa(state);
-  }
-  if (match_i32(state, ty)) {
-    return build_i32(state);
-  }
-  if (match_f32(state, ty)) {
-    return build_f32(state);
-  }
-  if (match_f16(state, ty)) {
-    return build_f16(state);
-  }
-  if (match_bool(state, ty)) {
-    return build_bool(state);
-  }
-  return nullptr;
-}
-
-std::string ScalarNoU32::String(MatchState*) const {
-  StringStream ss;
-  // Note: We pass nullptr to the TypeMatcher::String() functions, as 'matcher's do not support
-  // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
-  ss << Ia().String(nullptr) << ", " << Fa().String(nullptr) << ", " << F32().String(nullptr) << ", " << F16().String(nullptr) << ", " << I32().String(nullptr) << " or " << Bool().String(nullptr);
-  return ss.str();
-}
-
 /// TypeMatcher for 'match scalar_no_bool'
-class ScalarNoBool : public TypeMatcher {
- public:
-  /// 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 state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+constexpr TypeMatcher kScalarNoBoolMatcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+    if (match_ia(state, ty)) {
+      return build_ia(state);
+    }
+    if (match_fa(state, ty)) {
+      return build_fa(state);
+    }
+    if (match_i32(state, ty)) {
+      return build_i32(state);
+    }
+    if (match_u32(state, ty)) {
+      return build_u32(state);
+    }
+    if (match_f32(state, ty)) {
+      return build_f32(state);
+    }
+    if (match_f16(state, ty)) {
+      return build_f16(state);
+    }
+    return nullptr;
+  },
+/* string */ [](MatchState*) -> std::string {
+    StringStream ss;
+    // Note: We pass nullptr to the TypeMatcher::String() functions, as 'matcher's do not support
+    // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
+    ss << kIaMatcher.string(nullptr) << ", " << kFaMatcher.string(nullptr) << ", " << kF32Matcher.string(nullptr) << ", " << kF16Matcher.string(nullptr) << ", " << kI32Matcher.string(nullptr) << " or " << kU32Matcher.string(nullptr);
+    return ss.str();
+  }
 };
 
-const type::Type* ScalarNoBool::Match(MatchState& state, const type::Type* ty) const {
-  if (match_ia(state, ty)) {
-    return build_ia(state);
-  }
-  if (match_fa(state, ty)) {
-    return build_fa(state);
-  }
-  if (match_i32(state, ty)) {
-    return build_i32(state);
-  }
-  if (match_u32(state, ty)) {
-    return build_u32(state);
-  }
-  if (match_f32(state, ty)) {
-    return build_f32(state);
-  }
-  if (match_f16(state, ty)) {
-    return build_f16(state);
-  }
-  return nullptr;
-}
-
-std::string ScalarNoBool::String(MatchState*) const {
-  StringStream ss;
-  // Note: We pass nullptr to the TypeMatcher::String() functions, as 'matcher's do not support
-  // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
-  ss << Ia().String(nullptr) << ", " << Fa().String(nullptr) << ", " << F32().String(nullptr) << ", " << F16().String(nullptr) << ", " << I32().String(nullptr) << " or " << U32().String(nullptr);
-  return ss.str();
-}
-
 /// TypeMatcher for 'match fia_fiu32_f16'
-class FiaFiu32F16 : public TypeMatcher {
- public:
-  /// 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 state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+constexpr TypeMatcher kFiaFiu32F16Matcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+    if (match_ia(state, ty)) {
+      return build_ia(state);
+    }
+    if (match_fa(state, ty)) {
+      return build_fa(state);
+    }
+    if (match_i32(state, ty)) {
+      return build_i32(state);
+    }
+    if (match_u32(state, ty)) {
+      return build_u32(state);
+    }
+    if (match_f32(state, ty)) {
+      return build_f32(state);
+    }
+    if (match_f16(state, ty)) {
+      return build_f16(state);
+    }
+    return nullptr;
+  },
+/* string */ [](MatchState*) -> std::string {
+    StringStream ss;
+    // Note: We pass nullptr to the TypeMatcher::String() functions, as 'matcher's do not support
+    // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
+    ss << kFaMatcher.string(nullptr) << ", " << kIaMatcher.string(nullptr) << ", " << kF32Matcher.string(nullptr) << ", " << kI32Matcher.string(nullptr) << ", " << kU32Matcher.string(nullptr) << " or " << kF16Matcher.string(nullptr);
+    return ss.str();
+  }
 };
 
-const type::Type* FiaFiu32F16::Match(MatchState& state, const type::Type* ty) const {
-  if (match_ia(state, ty)) {
-    return build_ia(state);
-  }
-  if (match_fa(state, ty)) {
-    return build_fa(state);
-  }
-  if (match_i32(state, ty)) {
-    return build_i32(state);
-  }
-  if (match_u32(state, ty)) {
-    return build_u32(state);
-  }
-  if (match_f32(state, ty)) {
-    return build_f32(state);
-  }
-  if (match_f16(state, ty)) {
-    return build_f16(state);
-  }
-  return nullptr;
-}
-
-std::string FiaFiu32F16::String(MatchState*) const {
-  StringStream ss;
-  // Note: We pass nullptr to the TypeMatcher::String() functions, as 'matcher's do not support
-  // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
-  ss << Fa().String(nullptr) << ", " << Ia().String(nullptr) << ", " << F32().String(nullptr) << ", " << I32().String(nullptr) << ", " << U32().String(nullptr) << " or " << F16().String(nullptr);
-  return ss.str();
-}
-
 /// TypeMatcher for 'match fia_fi32_f16'
-class FiaFi32F16 : public TypeMatcher {
- public:
-  /// 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 state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+constexpr TypeMatcher kFiaFi32F16Matcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+    if (match_ia(state, ty)) {
+      return build_ia(state);
+    }
+    if (match_fa(state, ty)) {
+      return build_fa(state);
+    }
+    if (match_i32(state, ty)) {
+      return build_i32(state);
+    }
+    if (match_f32(state, ty)) {
+      return build_f32(state);
+    }
+    if (match_f16(state, ty)) {
+      return build_f16(state);
+    }
+    return nullptr;
+  },
+/* string */ [](MatchState*) -> std::string {
+    StringStream ss;
+    // Note: We pass nullptr to the TypeMatcher::String() functions, as 'matcher's do not support
+    // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
+    ss << kFaMatcher.string(nullptr) << ", " << kIaMatcher.string(nullptr) << ", " << kF32Matcher.string(nullptr) << ", " << kI32Matcher.string(nullptr) << " or " << kF16Matcher.string(nullptr);
+    return ss.str();
+  }
 };
 
-const type::Type* FiaFi32F16::Match(MatchState& state, const type::Type* ty) const {
-  if (match_ia(state, ty)) {
-    return build_ia(state);
-  }
-  if (match_fa(state, ty)) {
-    return build_fa(state);
-  }
-  if (match_i32(state, ty)) {
-    return build_i32(state);
-  }
-  if (match_f32(state, ty)) {
-    return build_f32(state);
-  }
-  if (match_f16(state, ty)) {
-    return build_f16(state);
-  }
-  return nullptr;
-}
-
-std::string FiaFi32F16::String(MatchState*) const {
-  StringStream ss;
-  // Note: We pass nullptr to the TypeMatcher::String() functions, as 'matcher's do not support
-  // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
-  ss << Fa().String(nullptr) << ", " << Ia().String(nullptr) << ", " << F32().String(nullptr) << ", " << I32().String(nullptr) << " or " << F16().String(nullptr);
-  return ss.str();
-}
-
 /// TypeMatcher for 'match fia_fiu32'
-class FiaFiu32 : public TypeMatcher {
- public:
-  /// 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 state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+constexpr TypeMatcher kFiaFiu32Matcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+    if (match_ia(state, ty)) {
+      return build_ia(state);
+    }
+    if (match_fa(state, ty)) {
+      return build_fa(state);
+    }
+    if (match_i32(state, ty)) {
+      return build_i32(state);
+    }
+    if (match_u32(state, ty)) {
+      return build_u32(state);
+    }
+    if (match_f32(state, ty)) {
+      return build_f32(state);
+    }
+    return nullptr;
+  },
+/* string */ [](MatchState*) -> std::string {
+    StringStream ss;
+    // Note: We pass nullptr to the TypeMatcher::String() functions, as 'matcher's do not support
+    // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
+    ss << kFaMatcher.string(nullptr) << ", " << kIaMatcher.string(nullptr) << ", " << kF32Matcher.string(nullptr) << ", " << kI32Matcher.string(nullptr) << " or " << kU32Matcher.string(nullptr);
+    return ss.str();
+  }
 };
 
-const type::Type* FiaFiu32::Match(MatchState& state, const type::Type* ty) const {
-  if (match_ia(state, ty)) {
-    return build_ia(state);
-  }
-  if (match_fa(state, ty)) {
-    return build_fa(state);
-  }
-  if (match_i32(state, ty)) {
-    return build_i32(state);
-  }
-  if (match_u32(state, ty)) {
-    return build_u32(state);
-  }
-  if (match_f32(state, ty)) {
-    return build_f32(state);
-  }
-  return nullptr;
-}
-
-std::string FiaFiu32::String(MatchState*) const {
-  StringStream ss;
-  // Note: We pass nullptr to the TypeMatcher::String() functions, as 'matcher's do not support
-  // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
-  ss << Fa().String(nullptr) << ", " << Ia().String(nullptr) << ", " << F32().String(nullptr) << ", " << I32().String(nullptr) << " or " << U32().String(nullptr);
-  return ss.str();
-}
-
 /// TypeMatcher for 'match fa_f32'
-class FaF32 : public TypeMatcher {
- public:
-  /// 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 state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+constexpr TypeMatcher kFaF32Matcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+    if (match_fa(state, ty)) {
+      return build_fa(state);
+    }
+    if (match_f32(state, ty)) {
+      return build_f32(state);
+    }
+    return nullptr;
+  },
+/* string */ [](MatchState*) -> std::string {
+    StringStream ss;
+    // Note: We pass nullptr to the TypeMatcher::String() functions, as 'matcher's do not support
+    // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
+    ss << kFaMatcher.string(nullptr) << " or " << kF32Matcher.string(nullptr);
+    return ss.str();
+  }
 };
 
-const type::Type* FaF32::Match(MatchState& state, const type::Type* ty) const {
-  if (match_fa(state, ty)) {
-    return build_fa(state);
-  }
-  if (match_f32(state, ty)) {
-    return build_f32(state);
-  }
-  return nullptr;
-}
-
-std::string FaF32::String(MatchState*) const {
-  StringStream ss;
-  // Note: We pass nullptr to the TypeMatcher::String() functions, as 'matcher's do not support
-  // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
-  ss << Fa().String(nullptr) << " or " << F32().String(nullptr);
-  return ss.str();
-}
-
 /// TypeMatcher for 'match fa_f32_f16'
-class FaF32F16 : public TypeMatcher {
- public:
-  /// 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 state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+constexpr TypeMatcher kFaF32F16Matcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+    if (match_fa(state, ty)) {
+      return build_fa(state);
+    }
+    if (match_f32(state, ty)) {
+      return build_f32(state);
+    }
+    if (match_f16(state, ty)) {
+      return build_f16(state);
+    }
+    return nullptr;
+  },
+/* string */ [](MatchState*) -> std::string {
+    StringStream ss;
+    // Note: We pass nullptr to the TypeMatcher::String() functions, as 'matcher's do not support
+    // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
+    ss << kFaMatcher.string(nullptr) << ", " << kF32Matcher.string(nullptr) << " or " << kF16Matcher.string(nullptr);
+    return ss.str();
+  }
 };
 
-const type::Type* FaF32F16::Match(MatchState& state, const type::Type* ty) const {
-  if (match_fa(state, ty)) {
-    return build_fa(state);
-  }
-  if (match_f32(state, ty)) {
-    return build_f32(state);
-  }
-  if (match_f16(state, ty)) {
-    return build_f16(state);
-  }
-  return nullptr;
-}
-
-std::string FaF32F16::String(MatchState*) const {
-  StringStream ss;
-  // Note: We pass nullptr to the TypeMatcher::String() functions, as 'matcher's do not support
-  // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
-  ss << Fa().String(nullptr) << ", " << F32().String(nullptr) << " or " << F16().String(nullptr);
-  return ss.str();
-}
-
 /// TypeMatcher for 'match ia_iu32'
-class IaIu32 : public TypeMatcher {
- public:
-  /// 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 state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+constexpr TypeMatcher kIaIu32Matcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+    if (match_ia(state, ty)) {
+      return build_ia(state);
+    }
+    if (match_i32(state, ty)) {
+      return build_i32(state);
+    }
+    if (match_u32(state, ty)) {
+      return build_u32(state);
+    }
+    return nullptr;
+  },
+/* string */ [](MatchState*) -> std::string {
+    StringStream ss;
+    // Note: We pass nullptr to the TypeMatcher::String() functions, as 'matcher's do not support
+    // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
+    ss << kIaMatcher.string(nullptr) << ", " << kI32Matcher.string(nullptr) << " or " << kU32Matcher.string(nullptr);
+    return ss.str();
+  }
 };
 
-const type::Type* IaIu32::Match(MatchState& state, const type::Type* ty) const {
-  if (match_ia(state, ty)) {
-    return build_ia(state);
-  }
-  if (match_i32(state, ty)) {
-    return build_i32(state);
-  }
-  if (match_u32(state, ty)) {
-    return build_u32(state);
-  }
-  return nullptr;
-}
-
-std::string IaIu32::String(MatchState*) const {
-  StringStream ss;
-  // Note: We pass nullptr to the TypeMatcher::String() functions, as 'matcher's do not support
-  // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
-  ss << Ia().String(nullptr) << ", " << I32().String(nullptr) << " or " << U32().String(nullptr);
-  return ss.str();
-}
-
 /// TypeMatcher for 'match ia_i32'
-class IaI32 : public TypeMatcher {
- public:
-  /// 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 state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+constexpr TypeMatcher kIaI32Matcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+    if (match_ia(state, ty)) {
+      return build_ia(state);
+    }
+    if (match_i32(state, ty)) {
+      return build_i32(state);
+    }
+    return nullptr;
+  },
+/* string */ [](MatchState*) -> std::string {
+    StringStream ss;
+    // Note: We pass nullptr to the TypeMatcher::String() functions, as 'matcher's do not support
+    // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
+    ss << kIaMatcher.string(nullptr) << " or " << kI32Matcher.string(nullptr);
+    return ss.str();
+  }
 };
 
-const type::Type* IaI32::Match(MatchState& state, const type::Type* ty) const {
-  if (match_ia(state, ty)) {
-    return build_ia(state);
-  }
-  if (match_i32(state, ty)) {
-    return build_i32(state);
-  }
-  return nullptr;
-}
-
-std::string IaI32::String(MatchState*) const {
-  StringStream ss;
-  // Note: We pass nullptr to the TypeMatcher::String() functions, as 'matcher's do not support
-  // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
-  ss << Ia().String(nullptr) << " or " << I32().String(nullptr);
-  return ss.str();
-}
-
 /// TypeMatcher for 'match fiu32_f16'
-class Fiu32F16 : public TypeMatcher {
- public:
-  /// 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 state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+constexpr TypeMatcher kFiu32F16Matcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+    if (match_i32(state, ty)) {
+      return build_i32(state);
+    }
+    if (match_u32(state, ty)) {
+      return build_u32(state);
+    }
+    if (match_f32(state, ty)) {
+      return build_f32(state);
+    }
+    if (match_f16(state, ty)) {
+      return build_f16(state);
+    }
+    return nullptr;
+  },
+/* string */ [](MatchState*) -> std::string {
+    StringStream ss;
+    // Note: We pass nullptr to the TypeMatcher::String() functions, as 'matcher's do not support
+    // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
+    ss << kF32Matcher.string(nullptr) << ", " << kI32Matcher.string(nullptr) << ", " << kU32Matcher.string(nullptr) << " or " << kF16Matcher.string(nullptr);
+    return ss.str();
+  }
 };
 
-const type::Type* Fiu32F16::Match(MatchState& state, const type::Type* ty) const {
-  if (match_i32(state, ty)) {
-    return build_i32(state);
-  }
-  if (match_u32(state, ty)) {
-    return build_u32(state);
-  }
-  if (match_f32(state, ty)) {
-    return build_f32(state);
-  }
-  if (match_f16(state, ty)) {
-    return build_f16(state);
-  }
-  return nullptr;
-}
-
-std::string Fiu32F16::String(MatchState*) const {
-  StringStream ss;
-  // Note: We pass nullptr to the TypeMatcher::String() functions, as 'matcher's do not support
-  // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
-  ss << F32().String(nullptr) << ", " << I32().String(nullptr) << ", " << U32().String(nullptr) << " or " << F16().String(nullptr);
-  return ss.str();
-}
-
 /// TypeMatcher for 'match fiu32'
-class Fiu32 : public TypeMatcher {
- public:
-  /// 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 state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+constexpr TypeMatcher kFiu32Matcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+    if (match_i32(state, ty)) {
+      return build_i32(state);
+    }
+    if (match_u32(state, ty)) {
+      return build_u32(state);
+    }
+    if (match_f32(state, ty)) {
+      return build_f32(state);
+    }
+    return nullptr;
+  },
+/* string */ [](MatchState*) -> std::string {
+    StringStream ss;
+    // Note: We pass nullptr to the TypeMatcher::String() functions, as 'matcher's do not support
+    // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
+    ss << kF32Matcher.string(nullptr) << ", " << kI32Matcher.string(nullptr) << " or " << kU32Matcher.string(nullptr);
+    return ss.str();
+  }
 };
 
-const type::Type* Fiu32::Match(MatchState& state, const type::Type* ty) const {
-  if (match_i32(state, ty)) {
-    return build_i32(state);
-  }
-  if (match_u32(state, ty)) {
-    return build_u32(state);
-  }
-  if (match_f32(state, ty)) {
-    return build_f32(state);
-  }
-  return nullptr;
-}
-
-std::string Fiu32::String(MatchState*) const {
-  StringStream ss;
-  // Note: We pass nullptr to the TypeMatcher::String() functions, as 'matcher's do not support
-  // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
-  ss << F32().String(nullptr) << ", " << I32().String(nullptr) << " or " << U32().String(nullptr);
-  return ss.str();
-}
-
 /// TypeMatcher for 'match fi32_f16'
-class Fi32F16 : public TypeMatcher {
- public:
-  /// 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 state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+constexpr TypeMatcher kFi32F16Matcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+    if (match_i32(state, ty)) {
+      return build_i32(state);
+    }
+    if (match_f32(state, ty)) {
+      return build_f32(state);
+    }
+    if (match_f16(state, ty)) {
+      return build_f16(state);
+    }
+    return nullptr;
+  },
+/* string */ [](MatchState*) -> std::string {
+    StringStream ss;
+    // Note: We pass nullptr to the TypeMatcher::String() functions, as 'matcher's do not support
+    // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
+    ss << kF32Matcher.string(nullptr) << ", " << kI32Matcher.string(nullptr) << " or " << kF16Matcher.string(nullptr);
+    return ss.str();
+  }
 };
 
-const type::Type* Fi32F16::Match(MatchState& state, const type::Type* ty) const {
-  if (match_i32(state, ty)) {
-    return build_i32(state);
-  }
-  if (match_f32(state, ty)) {
-    return build_f32(state);
-  }
-  if (match_f16(state, ty)) {
-    return build_f16(state);
-  }
-  return nullptr;
-}
-
-std::string Fi32F16::String(MatchState*) const {
-  StringStream ss;
-  // Note: We pass nullptr to the TypeMatcher::String() functions, as 'matcher's do not support
-  // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
-  ss << F32().String(nullptr) << ", " << I32().String(nullptr) << " or " << F16().String(nullptr);
-  return ss.str();
-}
-
 /// TypeMatcher for 'match fi32'
-class Fi32 : public TypeMatcher {
- public:
-  /// 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 state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+constexpr TypeMatcher kFi32Matcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+    if (match_i32(state, ty)) {
+      return build_i32(state);
+    }
+    if (match_f32(state, ty)) {
+      return build_f32(state);
+    }
+    return nullptr;
+  },
+/* string */ [](MatchState*) -> std::string {
+    StringStream ss;
+    // Note: We pass nullptr to the TypeMatcher::String() functions, as 'matcher's do not support
+    // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
+    ss << kF32Matcher.string(nullptr) << " or " << kI32Matcher.string(nullptr);
+    return ss.str();
+  }
 };
 
-const type::Type* Fi32::Match(MatchState& state, const type::Type* ty) const {
-  if (match_i32(state, ty)) {
-    return build_i32(state);
-  }
-  if (match_f32(state, ty)) {
-    return build_f32(state);
-  }
-  return nullptr;
-}
-
-std::string Fi32::String(MatchState*) const {
-  StringStream ss;
-  // Note: We pass nullptr to the TypeMatcher::String() functions, as 'matcher's do not support
-  // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
-  ss << F32().String(nullptr) << " or " << I32().String(nullptr);
-  return ss.str();
-}
-
 /// TypeMatcher for 'match f32_f16'
-class F32F16 : public TypeMatcher {
- public:
-  /// 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 state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+constexpr TypeMatcher kF32F16Matcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+    if (match_f32(state, ty)) {
+      return build_f32(state);
+    }
+    if (match_f16(state, ty)) {
+      return build_f16(state);
+    }
+    return nullptr;
+  },
+/* string */ [](MatchState*) -> std::string {
+    StringStream ss;
+    // Note: We pass nullptr to the TypeMatcher::String() functions, as 'matcher's do not support
+    // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
+    ss << kF32Matcher.string(nullptr) << " or " << kF16Matcher.string(nullptr);
+    return ss.str();
+  }
 };
 
-const type::Type* F32F16::Match(MatchState& state, const type::Type* ty) const {
-  if (match_f32(state, ty)) {
-    return build_f32(state);
-  }
-  if (match_f16(state, ty)) {
-    return build_f16(state);
-  }
-  return nullptr;
-}
-
-std::string F32F16::String(MatchState*) const {
-  StringStream ss;
-  // Note: We pass nullptr to the TypeMatcher::String() functions, as 'matcher's do not support
-  // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
-  ss << F32().String(nullptr) << " or " << F16().String(nullptr);
-  return ss.str();
-}
-
 /// TypeMatcher for 'match iu32'
-class Iu32 : public TypeMatcher {
- public:
-  /// 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 state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
+constexpr TypeMatcher kIu32Matcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+    if (match_i32(state, ty)) {
+      return build_i32(state);
+    }
+    if (match_u32(state, ty)) {
+      return build_u32(state);
+    }
+    return nullptr;
+  },
+/* string */ [](MatchState*) -> std::string {
+    StringStream ss;
+    // Note: We pass nullptr to the TypeMatcher::String() functions, as 'matcher's do not support
+    // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
+    ss << kI32Matcher.string(nullptr) << " or " << kU32Matcher.string(nullptr);
+    return ss.str();
+  }
 };
 
-const type::Type* Iu32::Match(MatchState& state, const type::Type* ty) const {
-  if (match_i32(state, ty)) {
-    return build_i32(state);
-  }
-  if (match_u32(state, ty)) {
-    return build_u32(state);
-  }
-  return nullptr;
-}
-
-std::string Iu32::String(MatchState*) const {
-  StringStream ss;
-  // Note: We pass nullptr to the TypeMatcher::String() functions, as 'matcher's do not support
-  // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
-  ss << I32().String(nullptr) << " or " << U32().String(nullptr);
-  return ss.str();
-}
-
 /// EnumMatcher for 'match f32_texel_format'
-class F32TexelFormat : public NumberMatcher {
- public:
-  /// Checks whether the given number matches the enum matcher rules.
-  /// Match may define template numbers in state.
-  /// @param state the MatchState
-  /// @param number the enum value as a Number
-  /// @return true if the enum value matches the set
-  Number Match(MatchState& state, Number number) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
-};
-
-Number F32TexelFormat::Match(MatchState&, Number number) const {
-  switch (static_cast<TexelFormat>(number.Value())) {
-    case TexelFormat::kBgra8Unorm:
-    case TexelFormat::kRgba8Unorm:
-    case TexelFormat::kRgba8Snorm:
-    case TexelFormat::kRgba16Float:
-    case TexelFormat::kR32Float:
-    case TexelFormat::kRg32Float:
-    case TexelFormat::kRgba32Float:
-      return number;
-    default:
-      return Number::invalid;
+constexpr NumberMatcher kF32TexelFormatMatcher {
+/* match */ [](MatchState&, Number number) -> Number {
+    switch (static_cast<TexelFormat>(number.Value())) {
+      case TexelFormat::kBgra8Unorm:
+      case TexelFormat::kRgba8Unorm:
+      case TexelFormat::kRgba8Snorm:
+      case TexelFormat::kRgba16Float:
+      case TexelFormat::kR32Float:
+      case TexelFormat::kRg32Float:
+      case TexelFormat::kRgba32Float:
+        return number;
+      default:
+        return Number::invalid;
+    }
+  },
+/* string */ [](MatchState*) -> std::string {
+    return "bgra8unorm, rgba8unorm, rgba8snorm, rgba16float, r32float, rg32float or rgba32float";
   }
-}
-
-std::string F32TexelFormat::String(MatchState*) const {
-  return "bgra8unorm, rgba8unorm, rgba8snorm, rgba16float, r32float, rg32float or rgba32float";
-}
+};
 
 /// EnumMatcher for 'match i32_texel_format'
-class I32TexelFormat : public NumberMatcher {
- public:
-  /// Checks whether the given number matches the enum matcher rules.
-  /// Match may define template numbers in state.
-  /// @param state the MatchState
-  /// @param number the enum value as a Number
-  /// @return true if the enum value matches the set
-  Number Match(MatchState& state, Number number) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
-};
-
-Number I32TexelFormat::Match(MatchState&, Number number) const {
-  switch (static_cast<TexelFormat>(number.Value())) {
-    case TexelFormat::kRgba8Sint:
-    case TexelFormat::kRgba16Sint:
-    case TexelFormat::kR32Sint:
-    case TexelFormat::kRg32Sint:
-    case TexelFormat::kRgba32Sint:
-      return number;
-    default:
-      return Number::invalid;
+constexpr NumberMatcher kI32TexelFormatMatcher {
+/* match */ [](MatchState&, Number number) -> Number {
+    switch (static_cast<TexelFormat>(number.Value())) {
+      case TexelFormat::kRgba8Sint:
+      case TexelFormat::kRgba16Sint:
+      case TexelFormat::kR32Sint:
+      case TexelFormat::kRg32Sint:
+      case TexelFormat::kRgba32Sint:
+        return number;
+      default:
+        return Number::invalid;
+    }
+  },
+/* string */ [](MatchState*) -> std::string {
+    return "rgba8sint, rgba16sint, r32sint, rg32sint or rgba32sint";
   }
-}
-
-std::string I32TexelFormat::String(MatchState*) const {
-  return "rgba8sint, rgba16sint, r32sint, rg32sint or rgba32sint";
-}
+};
 
 /// EnumMatcher for 'match u32_texel_format'
-class U32TexelFormat : public NumberMatcher {
- public:
-  /// Checks whether the given number matches the enum matcher rules.
-  /// Match may define template numbers in state.
-  /// @param state the MatchState
-  /// @param number the enum value as a Number
-  /// @return true if the enum value matches the set
-  Number Match(MatchState& state, Number number) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
-};
-
-Number U32TexelFormat::Match(MatchState&, Number number) const {
-  switch (static_cast<TexelFormat>(number.Value())) {
-    case TexelFormat::kRgba8Uint:
-    case TexelFormat::kRgba16Uint:
-    case TexelFormat::kR32Uint:
-    case TexelFormat::kRg32Uint:
-    case TexelFormat::kRgba32Uint:
-      return number;
-    default:
-      return Number::invalid;
+constexpr NumberMatcher kU32TexelFormatMatcher {
+/* match */ [](MatchState&, Number number) -> Number {
+    switch (static_cast<TexelFormat>(number.Value())) {
+      case TexelFormat::kRgba8Uint:
+      case TexelFormat::kRgba16Uint:
+      case TexelFormat::kR32Uint:
+      case TexelFormat::kRg32Uint:
+      case TexelFormat::kRgba32Uint:
+        return number;
+      default:
+        return Number::invalid;
+    }
+  },
+/* string */ [](MatchState*) -> std::string {
+    return "rgba8uint, rgba16uint, r32uint, rg32uint or rgba32uint";
   }
-}
-
-std::string U32TexelFormat::String(MatchState*) const {
-  return "rgba8uint, rgba16uint, r32uint, rg32uint or rgba32uint";
-}
+};
 
 /// EnumMatcher for 'match write'
-class Write : public NumberMatcher {
- public:
-  /// Checks whether the given number matches the enum matcher rules.
-  /// Match may define template numbers in state.
-  /// @param state the MatchState
-  /// @param number the enum value as a Number
-  /// @return true if the enum value matches the set
-  Number Match(MatchState& state, Number number) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
-};
-
-Number Write::Match(MatchState&, Number number) const {
-  if (number.IsAny() || number.Value() == static_cast<uint32_t>(Access::kWrite)) {
-    return Number(static_cast<uint32_t>(Access::kWrite));
+constexpr NumberMatcher kWriteMatcher {
+/* match */ [](MatchState&, Number number) -> Number {
+    if (number.IsAny() || number.Value() == static_cast<uint32_t>(Access::kWrite)) {
+      return Number(static_cast<uint32_t>(Access::kWrite));
+    }
+    return Number::invalid;
+  },
+/* string */ [](MatchState*) -> std::string {
+    return "write";
   }
-  return Number::invalid;
-}
-
-std::string Write::String(MatchState*) const {
-  return "write";
-}
+};
 
 /// EnumMatcher for 'match read_write'
-class ReadWrite : public NumberMatcher {
- public:
-  /// Checks whether the given number matches the enum matcher rules.
-  /// Match may define template numbers in state.
-  /// @param state the MatchState
-  /// @param number the enum value as a Number
-  /// @return true if the enum value matches the set
-  Number Match(MatchState& state, Number number) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
-};
-
-Number ReadWrite::Match(MatchState&, Number number) const {
-  if (number.IsAny() || number.Value() == static_cast<uint32_t>(Access::kReadWrite)) {
-    return Number(static_cast<uint32_t>(Access::kReadWrite));
+constexpr NumberMatcher kReadWriteMatcher {
+/* match */ [](MatchState&, Number number) -> Number {
+    if (number.IsAny() || number.Value() == static_cast<uint32_t>(Access::kReadWrite)) {
+      return Number(static_cast<uint32_t>(Access::kReadWrite));
+    }
+    return Number::invalid;
+  },
+/* string */ [](MatchState*) -> std::string {
+    return "read_write";
   }
-  return Number::invalid;
-}
-
-std::string ReadWrite::String(MatchState*) const {
-  return "read_write";
-}
+};
 
 /// EnumMatcher for 'match function_private_workgroup'
-class FunctionPrivateWorkgroup : public NumberMatcher {
- public:
-  /// Checks whether the given number matches the enum matcher rules.
-  /// Match may define template numbers in state.
-  /// @param state the MatchState
-  /// @param number the enum value as a Number
-  /// @return true if the enum value matches the set
-  Number Match(MatchState& state, Number number) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
-};
-
-Number FunctionPrivateWorkgroup::Match(MatchState&, Number number) const {
-  switch (static_cast<AddressSpace>(number.Value())) {
-    case AddressSpace::kFunction:
-    case AddressSpace::kPrivate:
-    case AddressSpace::kWorkgroup:
-      return number;
-    default:
-      return Number::invalid;
+constexpr NumberMatcher kFunctionPrivateWorkgroupMatcher {
+/* match */ [](MatchState&, Number number) -> Number {
+    switch (static_cast<AddressSpace>(number.Value())) {
+      case AddressSpace::kFunction:
+      case AddressSpace::kPrivate:
+      case AddressSpace::kWorkgroup:
+        return number;
+      default:
+        return Number::invalid;
+    }
+  },
+/* string */ [](MatchState*) -> std::string {
+    return "function, private or workgroup";
   }
-}
-
-std::string FunctionPrivateWorkgroup::String(MatchState*) const {
-  return "function, private or workgroup";
-}
+};
 
 /// EnumMatcher for 'match workgroup_or_storage'
-class WorkgroupOrStorage : public NumberMatcher {
- public:
-  /// Checks whether the given number matches the enum matcher rules.
-  /// Match may define template numbers in state.
-  /// @param state the MatchState
-  /// @param number the enum value as a Number
-  /// @return true if the enum value matches the set
-  Number Match(MatchState& state, Number number) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
-};
-
-Number WorkgroupOrStorage::Match(MatchState&, Number number) const {
-  switch (static_cast<AddressSpace>(number.Value())) {
-    case AddressSpace::kWorkgroup:
-    case AddressSpace::kStorage:
-      return number;
-    default:
-      return Number::invalid;
+constexpr NumberMatcher kWorkgroupOrStorageMatcher {
+/* match */ [](MatchState&, Number number) -> Number {
+    switch (static_cast<AddressSpace>(number.Value())) {
+      case AddressSpace::kWorkgroup:
+      case AddressSpace::kStorage:
+        return number;
+      default:
+        return Number::invalid;
+    }
+  },
+/* string */ [](MatchState*) -> std::string {
+    return "workgroup or storage";
   }
-}
-
-std::string WorkgroupOrStorage::String(MatchState*) const {
-  return "workgroup or storage";
-}
+};
 
 /// EnumMatcher for 'match storage'
-class Storage : public NumberMatcher {
- public:
-  /// Checks whether the given number matches the enum matcher rules.
-  /// Match may define template numbers in state.
-  /// @param state the MatchState
-  /// @param number the enum value as a Number
-  /// @return true if the enum value matches the set
-  Number Match(MatchState& state, Number number) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
-};
-
-Number Storage::Match(MatchState&, Number number) const {
-  if (number.IsAny() || number.Value() == static_cast<uint32_t>(AddressSpace::kStorage)) {
-    return Number(static_cast<uint32_t>(AddressSpace::kStorage));
+constexpr NumberMatcher kStorageMatcher {
+/* match */ [](MatchState&, Number number) -> Number {
+    if (number.IsAny() || number.Value() == static_cast<uint32_t>(AddressSpace::kStorage)) {
+      return Number(static_cast<uint32_t>(AddressSpace::kStorage));
+    }
+    return Number::invalid;
+  },
+/* string */ [](MatchState*) -> std::string {
+    return "storage";
   }
-  return Number::invalid;
-}
-
-std::string Storage::String(MatchState*) const {
-  return "storage";
-}
+};
 
 /// EnumMatcher for 'match workgroup'
-class Workgroup : public NumberMatcher {
- public:
-  /// Checks whether the given number matches the enum matcher rules.
-  /// Match may define template numbers in state.
-  /// @param state the MatchState
-  /// @param number the enum value as a Number
-  /// @return true if the enum value matches the set
-  Number Match(MatchState& state, Number number) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
-};
-
-Number Workgroup::Match(MatchState&, Number number) const {
-  if (number.IsAny() || number.Value() == static_cast<uint32_t>(AddressSpace::kWorkgroup)) {
-    return Number(static_cast<uint32_t>(AddressSpace::kWorkgroup));
+constexpr NumberMatcher kWorkgroupMatcher {
+/* match */ [](MatchState&, Number number) -> Number {
+    if (number.IsAny() || number.Value() == static_cast<uint32_t>(AddressSpace::kWorkgroup)) {
+      return Number(static_cast<uint32_t>(AddressSpace::kWorkgroup));
+    }
+    return Number::invalid;
+  },
+/* string */ [](MatchState*) -> std::string {
+    return "workgroup";
   }
-  return Number::invalid;
-}
-
-std::string Workgroup::String(MatchState*) const {
-  return "workgroup";
-}
-
-/// Matchers holds type and number matchers
-class Matchers {
- private:
-  TemplateTypeMatcher template_type_0_{0};
-  TemplateTypeMatcher template_type_1_{1};
-  TemplateTypeMatcher template_type_2_{2};
-  TemplateTypeMatcher template_type_3_{3};
-  TemplateNumberMatcher template_number_0_{0};
-  TemplateNumberMatcher template_number_1_{1};
-  TemplateNumberMatcher template_number_2_{2};
-  Bool Bool_;
-  Ia Ia_;
-  Fa Fa_;
-  I32 I32_;
-  U32 U32_;
-  F32 F32_;
-  F16 F16_;
-  Vec2 Vec2_;
-  Vec3 Vec3_;
-  Vec4 Vec4_;
-  Mat2X2 Mat2X2_;
-  Mat2X3 Mat2X3_;
-  Mat2X4 Mat2X4_;
-  Mat3X2 Mat3X2_;
-  Mat3X3 Mat3X3_;
-  Mat3X4 Mat3X4_;
-  Mat4X2 Mat4X2_;
-  Mat4X3 Mat4X3_;
-  Mat4X4 Mat4X4_;
-  Vec Vec_;
-  Mat Mat_;
-  Ptr Ptr_;
-  Atomic Atomic_;
-  Array Array_;
-  Sampler Sampler_;
-  SamplerComparison SamplerComparison_;
-  Texture1D Texture1D_;
-  Texture2D Texture2D_;
-  Texture2DArray Texture2DArray_;
-  Texture3D Texture3D_;
-  TextureCube TextureCube_;
-  TextureCubeArray TextureCubeArray_;
-  TextureMultisampled2D TextureMultisampled2D_;
-  TextureDepth2D TextureDepth2D_;
-  TextureDepth2DArray TextureDepth2DArray_;
-  TextureDepthCube TextureDepthCube_;
-  TextureDepthCubeArray TextureDepthCubeArray_;
-  TextureDepthMultisampled2D TextureDepthMultisampled2D_;
-  TextureStorage1D TextureStorage1D_;
-  TextureStorage2D TextureStorage2D_;
-  TextureStorage2DArray TextureStorage2DArray_;
-  TextureStorage3D TextureStorage3D_;
-  TextureExternal TextureExternal_;
-  PackedVec3 PackedVec3_;
-  ModfResult ModfResult_;
-  ModfResultVec ModfResultVec_;
-  FrexpResult FrexpResult_;
-  FrexpResultVec FrexpResultVec_;
-  AtomicCompareExchangeResult AtomicCompareExchangeResult_;
-  Scalar Scalar_;
-  ConcreteScalar ConcreteScalar_;
-  ScalarNoF32 ScalarNoF32_;
-  ScalarNoF16 ScalarNoF16_;
-  ScalarNoI32 ScalarNoI32_;
-  ScalarNoU32 ScalarNoU32_;
-  ScalarNoBool ScalarNoBool_;
-  FiaFiu32F16 FiaFiu32F16_;
-  FiaFi32F16 FiaFi32F16_;
-  FiaFiu32 FiaFiu32_;
-  FaF32 FaF32_;
-  FaF32F16 FaF32F16_;
-  IaIu32 IaIu32_;
-  IaI32 IaI32_;
-  Fiu32F16 Fiu32F16_;
-  Fiu32 Fiu32_;
-  Fi32F16 Fi32F16_;
-  Fi32 Fi32_;
-  F32F16 F32F16_;
-  Iu32 Iu32_;
-  F32TexelFormat F32TexelFormat_;
-  I32TexelFormat I32TexelFormat_;
-  U32TexelFormat U32TexelFormat_;
-  Write Write_;
-  ReadWrite ReadWrite_;
-  FunctionPrivateWorkgroup FunctionPrivateWorkgroup_;
-  WorkgroupOrStorage WorkgroupOrStorage_;
-  Storage Storage_;
-  Workgroup Workgroup_;
-
- public:
-  /// Constructor
-  Matchers();
-  /// Destructor
-  ~Matchers();
-
-  /// The template types, types, and type matchers
-  TypeMatcher const* const type[73] = {
-    /* [0] */ &template_type_0_,
-    /* [1] */ &template_type_1_,
-    /* [2] */ &template_type_2_,
-    /* [3] */ &template_type_3_,
-    /* [4] */ &Bool_,
-    /* [5] */ &Ia_,
-    /* [6] */ &Fa_,
-    /* [7] */ &I32_,
-    /* [8] */ &U32_,
-    /* [9] */ &F32_,
-    /* [10] */ &F16_,
-    /* [11] */ &Vec2_,
-    /* [12] */ &Vec3_,
-    /* [13] */ &Vec4_,
-    /* [14] */ &Mat2X2_,
-    /* [15] */ &Mat2X3_,
-    /* [16] */ &Mat2X4_,
-    /* [17] */ &Mat3X2_,
-    /* [18] */ &Mat3X3_,
-    /* [19] */ &Mat3X4_,
-    /* [20] */ &Mat4X2_,
-    /* [21] */ &Mat4X3_,
-    /* [22] */ &Mat4X4_,
-    /* [23] */ &Vec_,
-    /* [24] */ &Mat_,
-    /* [25] */ &Ptr_,
-    /* [26] */ &Atomic_,
-    /* [27] */ &Array_,
-    /* [28] */ &Sampler_,
-    /* [29] */ &SamplerComparison_,
-    /* [30] */ &Texture1D_,
-    /* [31] */ &Texture2D_,
-    /* [32] */ &Texture2DArray_,
-    /* [33] */ &Texture3D_,
-    /* [34] */ &TextureCube_,
-    /* [35] */ &TextureCubeArray_,
-    /* [36] */ &TextureMultisampled2D_,
-    /* [37] */ &TextureDepth2D_,
-    /* [38] */ &TextureDepth2DArray_,
-    /* [39] */ &TextureDepthCube_,
-    /* [40] */ &TextureDepthCubeArray_,
-    /* [41] */ &TextureDepthMultisampled2D_,
-    /* [42] */ &TextureStorage1D_,
-    /* [43] */ &TextureStorage2D_,
-    /* [44] */ &TextureStorage2DArray_,
-    /* [45] */ &TextureStorage3D_,
-    /* [46] */ &TextureExternal_,
-    /* [47] */ &PackedVec3_,
-    /* [48] */ &ModfResult_,
-    /* [49] */ &ModfResultVec_,
-    /* [50] */ &FrexpResult_,
-    /* [51] */ &FrexpResultVec_,
-    /* [52] */ &AtomicCompareExchangeResult_,
-    /* [53] */ &Scalar_,
-    /* [54] */ &ConcreteScalar_,
-    /* [55] */ &ScalarNoF32_,
-    /* [56] */ &ScalarNoF16_,
-    /* [57] */ &ScalarNoI32_,
-    /* [58] */ &ScalarNoU32_,
-    /* [59] */ &ScalarNoBool_,
-    /* [60] */ &FiaFiu32F16_,
-    /* [61] */ &FiaFi32F16_,
-    /* [62] */ &FiaFiu32_,
-    /* [63] */ &FaF32_,
-    /* [64] */ &FaF32F16_,
-    /* [65] */ &IaIu32_,
-    /* [66] */ &IaI32_,
-    /* [67] */ &Fiu32F16_,
-    /* [68] */ &Fiu32_,
-    /* [69] */ &Fi32F16_,
-    /* [70] */ &Fi32_,
-    /* [71] */ &F32F16_,
-    /* [72] */ &Iu32_,
-  };
-
-  /// The template numbers, and number matchers
-  NumberMatcher const* const number[12] = {
-    /* [0] */ &template_number_0_,
-    /* [1] */ &template_number_1_,
-    /* [2] */ &template_number_2_,
-    /* [3] */ &F32TexelFormat_,
-    /* [4] */ &I32TexelFormat_,
-    /* [5] */ &U32TexelFormat_,
-    /* [6] */ &Write_,
-    /* [7] */ &ReadWrite_,
-    /* [8] */ &FunctionPrivateWorkgroup_,
-    /* [9] */ &WorkgroupOrStorage_,
-    /* [10] */ &Storage_,
-    /* [11] */ &Workgroup_,
-  };
 };
 
-Matchers::Matchers() = default;
-Matchers::~Matchers() = default;
+/// Type and number matchers
+
+/// The template types, types, and type matchers
+constexpr TypeMatcher kTypeMatchers[] = {
+  /* [0] */ TemplateTypeMatcher<0>::matcher,
+  /* [1] */ TemplateTypeMatcher<1>::matcher,
+  /* [2] */ TemplateTypeMatcher<2>::matcher,
+  /* [3] */ TemplateTypeMatcher<3>::matcher,
+  /* [4] */ kBoolMatcher,
+  /* [5] */ kIaMatcher,
+  /* [6] */ kFaMatcher,
+  /* [7] */ kI32Matcher,
+  /* [8] */ kU32Matcher,
+  /* [9] */ kF32Matcher,
+  /* [10] */ kF16Matcher,
+  /* [11] */ kVec2Matcher,
+  /* [12] */ kVec3Matcher,
+  /* [13] */ kVec4Matcher,
+  /* [14] */ kMat2X2Matcher,
+  /* [15] */ kMat2X3Matcher,
+  /* [16] */ kMat2X4Matcher,
+  /* [17] */ kMat3X2Matcher,
+  /* [18] */ kMat3X3Matcher,
+  /* [19] */ kMat3X4Matcher,
+  /* [20] */ kMat4X2Matcher,
+  /* [21] */ kMat4X3Matcher,
+  /* [22] */ kMat4X4Matcher,
+  /* [23] */ kVecMatcher,
+  /* [24] */ kMatMatcher,
+  /* [25] */ kPtrMatcher,
+  /* [26] */ kAtomicMatcher,
+  /* [27] */ kArrayMatcher,
+  /* [28] */ kSamplerMatcher,
+  /* [29] */ kSamplerComparisonMatcher,
+  /* [30] */ kTexture1DMatcher,
+  /* [31] */ kTexture2DMatcher,
+  /* [32] */ kTexture2DArrayMatcher,
+  /* [33] */ kTexture3DMatcher,
+  /* [34] */ kTextureCubeMatcher,
+  /* [35] */ kTextureCubeArrayMatcher,
+  /* [36] */ kTextureMultisampled2DMatcher,
+  /* [37] */ kTextureDepth2DMatcher,
+  /* [38] */ kTextureDepth2DArrayMatcher,
+  /* [39] */ kTextureDepthCubeMatcher,
+  /* [40] */ kTextureDepthCubeArrayMatcher,
+  /* [41] */ kTextureDepthMultisampled2DMatcher,
+  /* [42] */ kTextureStorage1DMatcher,
+  /* [43] */ kTextureStorage2DMatcher,
+  /* [44] */ kTextureStorage2DArrayMatcher,
+  /* [45] */ kTextureStorage3DMatcher,
+  /* [46] */ kTextureExternalMatcher,
+  /* [47] */ kPackedVec3Matcher,
+  /* [48] */ kModfResultMatcher,
+  /* [49] */ kModfResultVecMatcher,
+  /* [50] */ kFrexpResultMatcher,
+  /* [51] */ kFrexpResultVecMatcher,
+  /* [52] */ kAtomicCompareExchangeResultMatcher,
+  /* [53] */ kScalarMatcher,
+  /* [54] */ kConcreteScalarMatcher,
+  /* [55] */ kScalarNoF32Matcher,
+  /* [56] */ kScalarNoF16Matcher,
+  /* [57] */ kScalarNoI32Matcher,
+  /* [58] */ kScalarNoU32Matcher,
+  /* [59] */ kScalarNoBoolMatcher,
+  /* [60] */ kFiaFiu32F16Matcher,
+  /* [61] */ kFiaFi32F16Matcher,
+  /* [62] */ kFiaFiu32Matcher,
+  /* [63] */ kFaF32Matcher,
+  /* [64] */ kFaF32F16Matcher,
+  /* [65] */ kIaIu32Matcher,
+  /* [66] */ kIaI32Matcher,
+  /* [67] */ kFiu32F16Matcher,
+  /* [68] */ kFiu32Matcher,
+  /* [69] */ kFi32F16Matcher,
+  /* [70] */ kFi32Matcher,
+  /* [71] */ kF32F16Matcher,
+  /* [72] */ kIu32Matcher,
+};
+
+/// The template numbers, and number matchers
+constexpr NumberMatcher kNumberMatchers[] = {
+  /* [0] */ TemplateNumberMatcher<0>::matcher,
+  /* [1] */ TemplateNumberMatcher<1>::matcher,
+  /* [2] */ TemplateNumberMatcher<2>::matcher,
+  /* [3] */ kF32TexelFormatMatcher,
+  /* [4] */ kI32TexelFormatMatcher,
+  /* [5] */ kU32TexelFormatMatcher,
+  /* [6] */ kWriteMatcher,
+  /* [7] */ kReadWriteMatcher,
+  /* [8] */ kFunctionPrivateWorkgroupMatcher,
+  /* [9] */ kWorkgroupOrStorageMatcher,
+  /* [10] */ kStorageMatcher,
+  /* [11] */ kWorkgroupMatcher,
+};
 
 constexpr MatcherIndex kMatcherIndices[] = {
   /* [0] */ 25,
@@ -15404,3 +14364,38 @@
 };
 
 // clang-format on
+
+}  // anonymous namespace
+
+const TableData& CoreTableData() {
+    static const TableData data{
+        /* type_matchers */ kTypeMatchers,
+        /* number_matchers */ kNumberMatchers,
+        /* ctor_conv */ kConstructorsAndConverters,
+        /* builtins */ kBuiltins,
+        /* binary_plus */ kBinaryOperators[kBinaryOperatorPlus],
+        /* binary_minus */ kBinaryOperators[kBinaryOperatorMinus],
+        /* binary_star */ kBinaryOperators[kBinaryOperatorStar],
+        /* binary_divide */ kBinaryOperators[kBinaryOperatorDivide],
+        /* binary_modulo */ kBinaryOperators[kBinaryOperatorModulo],
+        /* binary_xor */ kBinaryOperators[kBinaryOperatorXor],
+        /* binary_and */ kBinaryOperators[kBinaryOperatorAnd],
+        /* binary_or */ kBinaryOperators[kBinaryOperatorOr],
+        /* binary_logical_and */ kBinaryOperators[kBinaryOperatorLogicalAnd],
+        /* binary_logical_or */ kBinaryOperators[kBinaryOperatorLogicalOr],
+        /* binary_equal */ kBinaryOperators[kBinaryOperatorEqual],
+        /* binary_not_equal */ kBinaryOperators[kBinaryOperatorNotEqual],
+        /* binary_less_than */ kBinaryOperators[kBinaryOperatorLessThan],
+        /* binary_greater_than */ kBinaryOperators[kBinaryOperatorGreaterThan],
+        /* binary_less_than_equal */ kBinaryOperators[kBinaryOperatorLessThanEqual],
+        /* binary_greater_than_equal */ kBinaryOperators[kBinaryOperatorGreaterThanEqual],
+        /* binary_shift_left */ kBinaryOperators[kBinaryOperatorShiftLeft],
+        /* binary_shift_right */ kBinaryOperators[kBinaryOperatorShiftRight],
+        /* unary_not */ kUnaryOperators[kUnaryOperatorNot],
+        /* unary_complement */ kUnaryOperators[kUnaryOperatorComplement],
+        /* unary_minus */ kUnaryOperators[kUnaryOperatorMinus],
+    };
+    return data;
+}
+
+}  // namespace tint::core::intrinsic
diff --git a/src/tint/lang/core/intrinsic/core_table_data.cc.tmpl b/src/tint/lang/core/intrinsic/core_table_data.cc.tmpl
new file mode 100644
index 0000000..e0b096b
--- /dev/null
+++ b/src/tint/lang/core/intrinsic/core_table_data.cc.tmpl
@@ -0,0 +1,26 @@
+{{- /*
+--------------------------------------------------------------------------------
+Template file for use with tools/src/cmd/gen to generate intrinsic_table.inl
+Used by BuiltinTable.cc for builtin overload resolution.
+
+To update the generated file, run:
+    ./tools/run gen
+
+See:
+* tools/src/cmd/gen for structures used by this template
+* https://golang.org/pkg/text/template/ for documentation on the template syntax
+--------------------------------------------------------------------------------
+*/ -}}
+
+{{- Import "src/tint/utils/templates/intrinsic_table_data.tmpl.inc" -}}
+
+{{- $I := LoadIntrinsics "src/tint/lang/core/core.def" -}}
+
+#include <limits>
+#include <string>
+
+#include "src/tint/lang/core/intrinsic/core_table_data.h"
+#include "src/tint/lang/core/intrinsic/core_type_matchers.h"
+#include "src/tint/utils/text/string_stream.h"
+
+{{ Eval "Data" "Intrinsics" $I "Namespace" "tint::core::intrinsic" "Name" "CoreTableData" -}}
diff --git a/src/tint/lang/core/intrinsic/core_table_data.h b/src/tint/lang/core/intrinsic/core_table_data.h
new file mode 100644
index 0000000..a9a7bd1
--- /dev/null
+++ b/src/tint/lang/core/intrinsic/core_table_data.h
@@ -0,0 +1,26 @@
+// Copyright 2023 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.
+
+#ifndef SRC_TINT_LANG_CORE_INTRINSIC_CORE_TABLE_DATA_H_
+#define SRC_TINT_LANG_CORE_INTRINSIC_CORE_TABLE_DATA_H_
+
+#include "src/tint/lang/core/intrinsic/table_data.h"
+
+namespace tint::core::intrinsic {
+
+const TableData& CoreTableData();
+
+}  // namespace tint::core::intrinsic
+
+#endif  // SRC_TINT_LANG_CORE_INTRINSIC_CORE_TABLE_DATA_H_
diff --git a/src/tint/lang/core/intrinsic/core_type_matchers.h b/src/tint/lang/core/intrinsic/core_type_matchers.h
new file mode 100644
index 0000000..b061443
--- /dev/null
+++ b/src/tint/lang/core/intrinsic/core_type_matchers.h
@@ -0,0 +1,570 @@
+// Copyright 2023 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.
+
+#ifndef SRC_TINT_LANG_CORE_INTRINSIC_CORE_TYPE_MATCHERS_H_
+#define SRC_TINT_LANG_CORE_INTRINSIC_CORE_TYPE_MATCHERS_H_
+
+#include "src/tint/lang/core/evaluation_stage.h"
+#include "src/tint/lang/core/intrinsic/table_data.h"
+#include "src/tint/lang/core/type/abstract_float.h"
+#include "src/tint/lang/core/type/abstract_int.h"
+#include "src/tint/lang/core/type/abstract_numeric.h"
+#include "src/tint/lang/core/type/array.h"
+#include "src/tint/lang/core/type/atomic.h"
+#include "src/tint/lang/core/type/bool.h"
+#include "src/tint/lang/core/type/builtin_structs.h"
+#include "src/tint/lang/core/type/depth_multisampled_texture.h"
+#include "src/tint/lang/core/type/depth_texture.h"
+#include "src/tint/lang/core/type/external_texture.h"
+#include "src/tint/lang/core/type/f16.h"
+#include "src/tint/lang/core/type/f32.h"
+#include "src/tint/lang/core/type/i32.h"
+#include "src/tint/lang/core/type/manager.h"
+#include "src/tint/lang/core/type/matrix.h"
+#include "src/tint/lang/core/type/multisampled_texture.h"
+#include "src/tint/lang/core/type/pointer.h"
+#include "src/tint/lang/core/type/sampled_texture.h"
+#include "src/tint/lang/core/type/storage_texture.h"
+#include "src/tint/lang/core/type/texture_dimension.h"
+#include "src/tint/lang/core/type/u32.h"
+#include "src/tint/lang/core/type/vector.h"
+
+namespace tint::core::intrinsic {
+
+inline bool match_bool(TableData::MatchState&, const type::Type* ty) {
+    return ty->IsAnyOf<TableData::Any, type::Bool>();
+}
+
+inline const type::AbstractFloat* build_fa(TableData::MatchState& state) {
+    return state.types.AFloat();
+}
+
+inline bool match_fa(TableData::MatchState& state, const type::Type* ty) {
+    return (state.earliest_eval_stage <= EvaluationStage::kConstant) &&
+           ty->IsAnyOf<TableData::Any, type::AbstractNumeric>();
+}
+
+inline const type::AbstractInt* build_ia(TableData::MatchState& state) {
+    return state.types.AInt();
+}
+
+inline bool match_ia(TableData::MatchState& state, const type::Type* ty) {
+    return (state.earliest_eval_stage <= EvaluationStage::kConstant) &&
+           ty->IsAnyOf<TableData::Any, type::AbstractInt>();
+}
+
+inline const type::Bool* build_bool(TableData::MatchState& state) {
+    return state.types.bool_();
+}
+
+inline const type::F16* build_f16(TableData::MatchState& state) {
+    return state.types.f16();
+}
+
+inline bool match_f16(TableData::MatchState&, const type::Type* ty) {
+    return ty->IsAnyOf<TableData::Any, type::F16, type::AbstractNumeric>();
+}
+
+inline const type::F32* build_f32(TableData::MatchState& state) {
+    return state.types.f32();
+}
+
+inline bool match_f32(TableData::MatchState&, const type::Type* ty) {
+    return ty->IsAnyOf<TableData::Any, type::F32, type::AbstractNumeric>();
+}
+
+inline const type::I32* build_i32(TableData::MatchState& state) {
+    return state.types.i32();
+}
+
+inline bool match_i32(TableData::MatchState&, const type::Type* ty) {
+    return ty->IsAnyOf<TableData::Any, type::I32, type::AbstractInt>();
+}
+
+inline const type::U32* build_u32(TableData::MatchState& state) {
+    return state.types.u32();
+}
+
+inline bool match_u32(TableData::MatchState&, const type::Type* ty) {
+    return ty->IsAnyOf<TableData::Any, type::U32, type::AbstractInt>();
+}
+
+inline bool match_vec(TableData::MatchState&,
+                      const type::Type* ty,
+                      TableData::Number& N,
+                      const type::Type*& T) {
+    if (ty->Is<TableData::Any>()) {
+        N = TableData::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>
+inline bool match_vec(TableData::MatchState&, const type::Type* ty, const type::Type*& T) {
+    if (ty->Is<TableData::Any>()) {
+        T = ty;
+        return true;
+    }
+
+    if (auto* v = ty->As<type::Vector>()) {
+        if (v->Width() == N) {
+            T = v->type();
+            return true;
+        }
+    }
+    return false;
+}
+
+inline const type::Vector* build_vec(TableData::MatchState& state,
+                                     TableData::Number N,
+                                     const type::Type* el) {
+    return state.types.vec(el, N.Value());
+}
+
+template <uint32_t N>
+inline const type::Vector* build_vec(TableData::MatchState& state, const type::Type* el) {
+    return state.types.vec(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>;
+
+inline bool match_packedVec3(TableData::MatchState&, const type::Type* ty, const type::Type*& T) {
+    if (ty->Is<TableData::Any>()) {
+        T = ty;
+        return true;
+    }
+
+    if (auto* v = ty->As<type::Vector>()) {
+        if (v->Packed()) {
+            T = v->type();
+            return true;
+        }
+    }
+    return false;
+}
+
+inline const type::Vector* build_packedVec3(TableData::MatchState& state, const type::Type* el) {
+    return state.types.Get<type::Vector>(el, 3u, /* packed */ true);
+}
+
+inline bool match_mat(TableData::MatchState&,
+                      const type::Type* ty,
+                      TableData::Number& M,
+                      TableData::Number& N,
+                      const type::Type*& T) {
+    if (ty->Is<TableData::Any>()) {
+        M = TableData::Number::any;
+        N = TableData::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>
+inline bool match_mat(TableData::MatchState&, const type::Type* ty, const type::Type*& T) {
+    if (ty->Is<TableData::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;
+}
+
+inline const type::Matrix* build_mat(TableData::MatchState& state,
+                                     TableData::Number C,
+                                     TableData::Number R,
+                                     const type::Type* T) {
+    auto* column_type = state.types.vec(T, R.Value());
+    return state.types.mat(column_type, C.Value());
+}
+
+template <uint32_t C, uint32_t R>
+inline const type::Matrix* build_mat(TableData::MatchState& state, const type::Type* T) {
+    auto* column_type = state.types.vec(T, R);
+    return state.types.mat(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>;
+
+inline bool match_array(TableData::MatchState&, const type::Type* ty, const type::Type*& T) {
+    if (ty->Is<TableData::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;
+}
+
+inline const type::Array* build_array(TableData::MatchState& state, const type::Type* el) {
+    return state.types.Get<type::Array>(el,
+                                        /* count */ state.types.Get<type::RuntimeArrayCount>(),
+                                        /* align */ 0u,
+                                        /* size */ 0u,
+                                        /* stride */ 0u,
+                                        /* stride_implicit */ 0u);
+}
+
+inline bool match_ptr(TableData::MatchState&,
+                      const type::Type* ty,
+                      TableData::Number& S,
+                      const type::Type*& T,
+                      TableData::Number& A) {
+    if (ty->Is<TableData::Any>()) {
+        S = TableData::Number::any;
+        T = ty;
+        A = TableData::Number::any;
+        return true;
+    }
+
+    if (auto* p = ty->As<type::Pointer>()) {
+        S = TableData::Number(static_cast<uint32_t>(p->AddressSpace()));
+        T = p->StoreType();
+        A = TableData::Number(static_cast<uint32_t>(p->Access()));
+        return true;
+    }
+    return false;
+}
+
+inline const type::Pointer* build_ptr(TableData::MatchState& state,
+                                      TableData::Number S,
+                                      const type::Type* T,
+                                      TableData::Number& A) {
+    return state.types.ptr(static_cast<core::AddressSpace>(S.Value()), T,
+                           static_cast<core::Access>(A.Value()));
+}
+
+inline bool match_atomic(TableData::MatchState&, const type::Type* ty, const type::Type*& T) {
+    if (ty->Is<TableData::Any>()) {
+        T = ty;
+        return true;
+    }
+
+    if (auto* a = ty->As<type::Atomic>()) {
+        T = a->Type();
+        return true;
+    }
+    return false;
+}
+
+inline const type::Atomic* build_atomic(TableData::MatchState& state, const type::Type* T) {
+    return state.types.atomic(T);
+}
+
+inline bool match_sampler(TableData::MatchState&, const type::Type* ty) {
+    if (ty->Is<TableData::Any>()) {
+        return true;
+    }
+    return ty->Is([](const type::Sampler* s) { return s->kind() == type::SamplerKind::kSampler; });
+}
+
+inline const type::Sampler* build_sampler(TableData::MatchState& state) {
+    return state.types.sampler();
+}
+
+inline bool match_sampler_comparison(TableData::MatchState&, const type::Type* ty) {
+    if (ty->Is<TableData::Any>()) {
+        return true;
+    }
+    return ty->Is(
+        [](const type::Sampler* s) { return s->kind() == type::SamplerKind::kComparisonSampler; });
+}
+
+inline const type::Sampler* build_sampler_comparison(TableData::MatchState& state) {
+    return state.types.comparison_sampler();
+}
+
+inline bool match_texture(TableData::MatchState&,
+                          const type::Type* ty,
+                          type::TextureDimension dim,
+                          const type::Type*& T) {
+    if (ty->Is<TableData::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)                                                       \
+    inline bool JOIN(match_texture_, suffix)(TableData::MatchState & state, const type::Type* ty,  \
+                                             const type::Type*& T) {                               \
+        return match_texture(state, ty, dim, T);                                                   \
+    }                                                                                              \
+    inline const type::SampledTexture* JOIN(build_texture_, suffix)(TableData::MatchState & state, \
+                                                                    const type::Type* T) {         \
+        return state.types.Get<type::SampledTexture>(dim, T);                                      \
+    }
+
+DECLARE_SAMPLED_TEXTURE(1d, type::TextureDimension::k1d)
+DECLARE_SAMPLED_TEXTURE(2d, type::TextureDimension::k2d)
+DECLARE_SAMPLED_TEXTURE(2d_array, type::TextureDimension::k2dArray)
+DECLARE_SAMPLED_TEXTURE(3d, type::TextureDimension::k3d)
+DECLARE_SAMPLED_TEXTURE(cube, type::TextureDimension::kCube)
+DECLARE_SAMPLED_TEXTURE(cube_array, type::TextureDimension::kCubeArray)
+#undef DECLARE_SAMPLED_TEXTURE
+
+inline bool match_texture_multisampled(TableData::MatchState&,
+                                       const type::Type* ty,
+                                       type::TextureDimension dim,
+                                       const type::Type*& T) {
+    if (ty->Is<TableData::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)                                      \
+    inline bool JOIN(match_texture_multisampled_, suffix)(                             \
+        TableData::MatchState & state, const type::Type* ty, const type::Type*& T) {   \
+        return match_texture_multisampled(state, ty, dim, T);                          \
+    }                                                                                  \
+    inline const type::MultisampledTexture* JOIN(build_texture_multisampled_, suffix)( \
+        TableData::MatchState & state, const type::Type* T) {                          \
+        return state.types.Get<type::MultisampledTexture>(dim, T);                     \
+    }
+
+DECLARE_MULTISAMPLED_TEXTURE(2d, type::TextureDimension::k2d)
+#undef DECLARE_MULTISAMPLED_TEXTURE
+
+inline bool match_texture_depth(TableData::MatchState&,
+                                const type::Type* ty,
+                                type::TextureDimension dim) {
+    if (ty->Is<TableData::Any>()) {
+        return true;
+    }
+    return ty->Is([&](const type::DepthTexture* t) { return t->dim() == dim; });
+}
+
+#define DECLARE_DEPTH_TEXTURE(suffix, dim)                                         \
+    inline bool JOIN(match_texture_depth_, suffix)(TableData::MatchState & state,  \
+                                                   const type::Type* ty) {         \
+        return match_texture_depth(state, ty, dim);                                \
+    }                                                                              \
+    inline const type::DepthTexture* JOIN(build_texture_depth_,                    \
+                                          suffix)(TableData::MatchState & state) { \
+        return state.types.Get<type::DepthTexture>(dim);                           \
+    }
+
+DECLARE_DEPTH_TEXTURE(2d, type::TextureDimension::k2d)
+DECLARE_DEPTH_TEXTURE(2d_array, type::TextureDimension::k2dArray)
+DECLARE_DEPTH_TEXTURE(cube, type::TextureDimension::kCube)
+DECLARE_DEPTH_TEXTURE(cube_array, type::TextureDimension::kCubeArray)
+#undef DECLARE_DEPTH_TEXTURE
+
+inline bool match_texture_depth_multisampled_2d(TableData::MatchState&, const type::Type* ty) {
+    if (ty->Is<TableData::Any>()) {
+        return true;
+    }
+    return ty->Is([&](const type::DepthMultisampledTexture* t) {
+        return t->dim() == type::TextureDimension::k2d;
+    });
+}
+
+inline type::DepthMultisampledTexture* build_texture_depth_multisampled_2d(
+    TableData::MatchState& state) {
+    return state.types.Get<type::DepthMultisampledTexture>(type::TextureDimension::k2d);
+}
+
+inline bool match_texture_storage(TableData::MatchState&,
+                                  const type::Type* ty,
+                                  type::TextureDimension dim,
+                                  TableData::Number& F,
+                                  TableData::Number& A) {
+    if (ty->Is<TableData::Any>()) {
+        F = TableData::Number::any;
+        A = TableData::Number::any;
+        return true;
+    }
+    if (auto* v = ty->As<type::StorageTexture>()) {
+        if (v->dim() == dim) {
+            F = TableData::Number(static_cast<uint32_t>(v->texel_format()));
+            A = TableData::Number(static_cast<uint32_t>(v->access()));
+            return true;
+        }
+    }
+    return false;
+}
+
+#define DECLARE_STORAGE_TEXTURE(suffix, dim)                                                     \
+    inline bool JOIN(match_texture_storage_, suffix)(TableData::MatchState & state,              \
+                                                     const type::Type* ty, TableData::Number& F, \
+                                                     TableData::Number& A) {                     \
+        return match_texture_storage(state, ty, dim, F, A);                                      \
+    }                                                                                            \
+    inline const type::StorageTexture* JOIN(build_texture_storage_, suffix)(                     \
+        TableData::MatchState & state, TableData::Number F, TableData::Number A) {               \
+        auto format = static_cast<TexelFormat>(F.Value());                                       \
+        auto access = static_cast<Access>(A.Value());                                            \
+        auto* T = type::StorageTexture::SubtypeFor(format, state.types);                         \
+        return state.types.Get<type::StorageTexture>(dim, format, access, T);                    \
+    }
+
+DECLARE_STORAGE_TEXTURE(1d, type::TextureDimension::k1d)
+DECLARE_STORAGE_TEXTURE(2d, type::TextureDimension::k2d)
+DECLARE_STORAGE_TEXTURE(2d_array, type::TextureDimension::k2dArray)
+DECLARE_STORAGE_TEXTURE(3d, type::TextureDimension::k3d)
+#undef DECLARE_STORAGE_TEXTURE
+
+inline bool match_texture_external(TableData::MatchState&, const type::Type* ty) {
+    return ty->IsAnyOf<TableData::Any, type::ExternalTexture>();
+}
+
+inline const type::ExternalTexture* build_texture_external(TableData::MatchState& state) {
+    return state.types.Get<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.
+inline bool match_modf_result(TableData::MatchState&, const type::Type* ty, const type::Type*& T) {
+    if (!ty->Is<TableData::Any>()) {
+        return false;
+    }
+    T = ty;
+    return true;
+}
+inline bool match_modf_result_vec(TableData::MatchState&,
+                                  const type::Type* ty,
+                                  TableData::Number& N,
+                                  const type::Type*& T) {
+    if (!ty->Is<TableData::Any>()) {
+        return false;
+    }
+    N = TableData::Number::any;
+    T = ty;
+    return true;
+}
+inline bool match_frexp_result(TableData::MatchState&, const type::Type* ty, const type::Type*& T) {
+    if (!ty->Is<TableData::Any>()) {
+        return false;
+    }
+    T = ty;
+    return true;
+}
+inline bool match_frexp_result_vec(TableData::MatchState&,
+                                   const type::Type* ty,
+                                   TableData::Number& N,
+                                   const type::Type*& T) {
+    if (!ty->Is<TableData::Any>()) {
+        return false;
+    }
+    N = TableData::Number::any;
+    T = ty;
+    return true;
+}
+
+inline bool match_atomic_compare_exchange_result(TableData::MatchState&,
+                                                 const type::Type* ty,
+                                                 const type::Type*& T) {
+    if (ty->Is<TableData::Any>()) {
+        T = ty;
+        return true;
+    }
+    return false;
+}
+
+inline const type::Struct* build_modf_result(TableData::MatchState& state, const type::Type* el) {
+    return type::CreateModfResult(state.types, state.symbols, el);
+}
+
+inline const type::Struct* build_modf_result_vec(TableData::MatchState& state,
+                                                 TableData::Number& n,
+                                                 const type::Type* el) {
+    auto* vec = state.types.vec(el, n.Value());
+    return type::CreateModfResult(state.types, state.symbols, vec);
+}
+
+inline const type::Struct* build_frexp_result(TableData::MatchState& state, const type::Type* el) {
+    return type::CreateFrexpResult(state.types, state.symbols, el);
+}
+
+inline const type::Struct* build_frexp_result_vec(TableData::MatchState& state,
+                                                  TableData::Number& n,
+                                                  const type::Type* el) {
+    auto* vec = state.types.vec(el, n.Value());
+    return type::CreateFrexpResult(state.types, state.symbols, vec);
+}
+
+inline const type::Struct* build_atomic_compare_exchange_result(TableData::MatchState& state,
+                                                                const type::Type* ty) {
+    return type::CreateAtomicCompareExchangeResult(state.types, state.symbols, ty);
+}
+
+}  // namespace tint::core::intrinsic
+
+#endif  // SRC_TINT_LANG_CORE_INTRINSIC_CORE_TYPE_MATCHERS_H_
diff --git a/src/tint/lang/core/intrinsic/table.cc b/src/tint/lang/core/intrinsic/table.cc
index 778db1a..b9dbcc8 100644
--- a/src/tint/lang/core/intrinsic/table.cc
+++ b/src/tint/lang/core/intrinsic/table.cc
@@ -19,18 +19,8 @@
 #include <utility>
 
 #include "src/tint/lang/core/evaluation_stage.h"
-#include "src/tint/lang/core/type/abstract_float.h"
-#include "src/tint/lang/core/type/abstract_int.h"
-#include "src/tint/lang/core/type/abstract_numeric.h"
-#include "src/tint/lang/core/type/atomic.h"
-#include "src/tint/lang/core/type/builtin_structs.h"
-#include "src/tint/lang/core/type/depth_multisampled_texture.h"
-#include "src/tint/lang/core/type/depth_texture.h"
-#include "src/tint/lang/core/type/external_texture.h"
-#include "src/tint/lang/core/type/multisampled_texture.h"
-#include "src/tint/lang/core/type/sampled_texture.h"
-#include "src/tint/lang/core/type/storage_texture.h"
-#include "src/tint/lang/core/type/texture_dimension.h"
+#include "src/tint/lang/core/intrinsic/core_table_data.h"
+#include "src/tint/lang/core/intrinsic/table_data.h"
 #include "src/tint/lang/wgsl/ast/binary_expression.h"
 #include "src/tint/lang/wgsl/program/program_builder.h"
 #include "src/tint/lang/wgsl/sem/pipeline_stage_set.h"
@@ -44,290 +34,49 @@
 #include "src/tint/utils/text/string_stream.h"
 
 namespace tint::core::intrinsic {
+
+const TableData::Number TableData::Number::any{Number::kAny};
+const TableData::Number TableData::Number::invalid{Number::kInvalid};
+
+TableData::Any::Any() : Base(0u, type::Flags{}) {}
+TableData::Any::~Any() = default;
+
+bool TableData::Any::Equals(const type::UniqueNode&) const {
+    return false;
+}
+
+std::string TableData::Any::FriendlyName() const {
+    return "<any>";
+}
+
+type::Type* TableData::Any::Clone(type::CloneContext&) const {
+    return nullptr;
+}
+
 namespace {
 
-// Forward declarations
-struct OverloadInfo;
-class Matchers;
-class NumberMatcher;
-class TypeMatcher;
+// Aliases
+using Any = TableData::Any;
+using Number = TableData::Number;
+using MatcherIndex = TableData::MatcherIndex;
+using TypeMatcher = TableData::TypeMatcher;
+using NumberMatcher = TableData::NumberMatcher;
+using MatchState = TableData::MatchState;
+using TemplateTypeInfo = TableData::TemplateTypeInfo;
+using TemplateNumberInfo = TableData::TemplateNumberInfo;
+using ParameterInfo = TableData::ParameterInfo;
+using IntrinsicInfo = TableData::IntrinsicInfo;
+using OverloadInfo = TableData::OverloadInfo;
+using OverloadFlag = TableData::OverloadFlag;
+using OverloadFlags = TableData::OverloadFlags;
+using TemplateState = TableData::TemplateState;
+constexpr const auto kNoMatcher = TableData::kNoMatcher;
 
 /// The Vector `N` template argument value for arrays of parameters.
-constexpr static const size_t kNumFixedParams = 8;
+constexpr const size_t kNumFixedParams = 8;
 
 /// The 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 override { return "<any>"; }
-    type::Type* Clone(type::CloneContext&) const override { return nullptr; }
-};
-
-/// 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(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];
-    }
-
-    /// @return the total number of type and number templates
-    size_t Count() const { return types_.Length() + numbers_.Length(); }
-
-  private:
-    Vector<const type::Type*, 4> types_;
-    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,
-               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
-    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_;
-};
+constexpr const size_t kNumFixedCandidates = 8;
 
 ////////////////////////////////////////////////////////////////////////////////
 // Binding functions for use in the generated builtin_table.inl
@@ -336,573 +85,6 @@
 ////////////////////////////////////////////////////////////////////////////////
 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')
-    kIsConstructor,             // The overload is a value constructor ('ctor')
-    kIsConverter,               // The overload is a value 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
-    kMustUse,                   // The overload cannot be called as a statement
-    kIsDeprecated,              // The overload is deprecated
-};
-
-// An enum set of OverloadFlag, used by OperatorInfo
-using OverloadFlags = tint::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 <= 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 <= 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_packedVec3(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->Packed()) {
-            T = v->type();
-            return true;
-        }
-    }
-    return false;
-}
-
-const type::Vector* build_packedVec3(MatchState& state, const type::Type* el) {
-    return state.builder.create<type::Vector>(el, 3u, /* packed */ true);
-}
-
-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>(static_cast<core::AddressSpace>(S.Value()), T,
-                                               static_cast<core::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() == type::SamplerKind::kSampler; });
-}
-
-const type::Sampler* build_sampler(MatchState& state) {
-    return state.builder.create<type::Sampler>(type::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() == type::SamplerKind::kComparisonSampler; });
-}
-
-const type::Sampler* build_sampler_comparison(MatchState& state) {
-    return state.builder.create<type::Sampler>(type::SamplerKind::kComparisonSampler);
-}
-
-bool match_texture(MatchState&,
-                   const type::Type* ty,
-                   type::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, type::TextureDimension::k1d)
-DECLARE_SAMPLED_TEXTURE(2d, type::TextureDimension::k2d)
-DECLARE_SAMPLED_TEXTURE(2d_array, type::TextureDimension::k2dArray)
-DECLARE_SAMPLED_TEXTURE(3d, type::TextureDimension::k3d)
-DECLARE_SAMPLED_TEXTURE(cube, type::TextureDimension::kCube)
-DECLARE_SAMPLED_TEXTURE(cube_array, type::TextureDimension::kCubeArray)
-#undef DECLARE_SAMPLED_TEXTURE
-
-bool match_texture_multisampled(MatchState&,
-                                const type::Type* ty,
-                                type::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, type::TextureDimension::k2d)
-#undef DECLARE_MULTISAMPLED_TEXTURE
-
-bool match_texture_depth(MatchState&, const type::Type* ty, type::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, type::TextureDimension::k2d)
-DECLARE_DEPTH_TEXTURE(2d_array, type::TextureDimension::k2dArray)
-DECLARE_DEPTH_TEXTURE(cube, type::TextureDimension::kCube)
-DECLARE_DEPTH_TEXTURE(cube_array, type::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() == type::TextureDimension::k2d;
-    });
-}
-
-type::DepthMultisampledTexture* build_texture_depth_multisampled_2d(MatchState& state) {
-    return state.builder.create<type::DepthMultisampledTexture>(type::TextureDimension::k2d);
-}
-
-bool match_texture_storage(MatchState&,
-                           const type::Type* ty,
-                           type::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, type::TextureDimension::k1d)
-DECLARE_STORAGE_TEXTURE(2d, type::TextureDimension::k2d)
-DECLARE_STORAGE_TEXTURE(2d_array, type::TextureDimension::k2dArray)
-DECLARE_STORAGE_TEXTURE(3d, type::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;
-}
-
-const type::Struct* build_modf_result(MatchState& state, const type::Type* el) {
-    return type::CreateModfResult(state.builder.Types(), state.builder.Symbols(), el);
-}
-
-const type::Struct* build_modf_result_vec(MatchState& state, Number& n, const type::Type* el) {
-    auto* vec = state.builder.create<type::Vector>(el, n.Value());
-    return type::CreateModfResult(state.builder.Types(), state.builder.Symbols(), vec);
-}
-
-const type::Struct* build_frexp_result(MatchState& state, const type::Type* el) {
-    return type::CreateFrexpResult(state.builder.Types(), state.builder.Symbols(), el);
-}
-
-const type::Struct* build_frexp_result_vec(MatchState& state, Number& n, const type::Type* el) {
-    auto* vec = state.builder.create<type::Vector>(el, n.Value());
-    return type::CreateFrexpResult(state.builder.Types(), state.builder.Symbols(), vec);
-}
-
-const type::Struct* build_atomic_compare_exchange_result(MatchState& state, const type::Type* ty) {
-    return type::CreateAtomicCompareExchangeResult(state.builder.Types(), state.builder.Symbols(),
-                                                   ty);
-}
-
-/// 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.
-    constant::Eval::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 "table.inl"
-
 /// IntrinsicPrototype describes a fully matched intrinsic.
 struct IntrinsicPrototype {
     /// Parameter describes a single parameter
@@ -926,7 +108,7 @@
         }
     };
 
-    const OverloadInfo* overload = nullptr;
+    const TableData::OverloadInfo* overload = nullptr;
     type::Type const* return_type = nullptr;
     Vector<Parameter, kNumFixedParams> parameters;
 };
@@ -950,7 +132,7 @@
 /// Impl is the private implementation of the Table interface.
 class Impl : public Table {
   public:
-    explicit Impl(ProgramBuilder& builder);
+    Impl(ProgramBuilder& b, const TableData& d);
 
     Builtin Lookup(core::Function builtin_type,
                    VectorRef<const type::Type*> args,
@@ -979,7 +161,7 @@
     /// Candidate holds information about an overload evaluated for resolution.
     struct Candidate {
         /// The candidate overload
-        const OverloadInfo* overload;
+        const TableData::OverloadInfo* overload;
         /// The template types and numbers
         TemplateState templates;
         /// The parameter types for the candidate overload
@@ -1029,7 +211,7 @@
     ///                  arguments. For example `vec3<f32>()` would have the first template-type
     ///                  template as `f32`.
     /// @returns the evaluated Candidate information.
-    Candidate ScoreOverload(const OverloadInfo* overload,
+    Candidate ScoreOverload(const TableData::OverloadInfo* overload,
                             VectorRef<const type::Type*> args,
                             EvaluationStage earliest_eval_stage,
                             const TemplateState& templates) const;
@@ -1054,13 +236,13 @@
     /// @param overload the overload being evaluated
     /// @param matcher_indices pointer to a list of matcher indices
     MatchState Match(TemplateState& templates,
-                     const OverloadInfo* overload,
+                     const TableData::OverloadInfo* overload,
                      MatcherIndex const* matcher_indices,
                      EvaluationStage earliest_eval_stage) const;
 
     // Prints the overload for emitting diagnostics
     void PrintOverload(StringStream& ss,
-                       const OverloadInfo* overload,
+                       const TableData::OverloadInfo* overload,
                        const char* intrinsic_name) const;
 
     // Prints the list of candidates for emitting diagnostics
@@ -1075,7 +257,7 @@
                               VectorRef<Candidate> candidates) const;
 
     ProgramBuilder& builder;
-    Matchers matchers;
+    const TableData& data;
     Hashmap<IntrinsicPrototype, sem::Builtin*, 64, IntrinsicPrototype::Hasher> builtins;
     Hashmap<IntrinsicPrototype, sem::ValueConstructor*, 16, IntrinsicPrototype::Hasher>
         constructors;
@@ -1108,15 +290,7 @@
     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::Impl(ProgramBuilder& b, const TableData& d) : builder(b), data(d) {}
 
 Impl::Builtin Impl::Lookup(core::Function builtin_type,
                            VectorRef<const type::Type*> args,
@@ -1138,8 +312,8 @@
     };
 
     // Resolve the intrinsic overload
-    auto match = MatchIntrinsic(kBuiltins[static_cast<size_t>(builtin_type)], intrinsic_name, args,
-                                earliest_eval_stage, TemplateState{}, on_no_match);
+    auto match = MatchIntrinsic(data.builtins[static_cast<size_t>(builtin_type)], intrinsic_name,
+                                args, earliest_eval_stage, TemplateState{}, on_no_match);
     if (!match.overload) {
         return {};
     }
@@ -1178,18 +352,23 @@
                                   const type::Type* arg,
                                   EvaluationStage earliest_eval_stage,
                                   const Source& source) {
-    auto [intrinsic_index, intrinsic_name] = [&]() -> std::pair<size_t, const char*> {
+    auto [intrinsic_info, intrinsic_name] = [&]() -> std::pair<const IntrinsicInfo*, const char*> {
         switch (op) {
             case core::UnaryOp::kComplement:
-                return {kUnaryOperatorComplement, "operator ~ "};
+                return {&data.unary_complement, "operator ~ "};
             case core::UnaryOp::kNegation:
-                return {kUnaryOperatorMinus, "operator - "};
+                return {&data.unary_minus, "operator - "};
             case core::UnaryOp::kNot:
-                return {kUnaryOperatorNot, "operator ! "};
+                return {&data.unary_not, "operator ! "};
             default:
-                return {0, "<unknown>"};
+                break;
         }
+        TINT_UNREACHABLE() << "invalid unary op: " << op;
+        return {};
     }();
+    if (!intrinsic_info) {
+        return {};
+    }
 
     Vector args{arg};
 
@@ -1207,8 +386,8 @@
     };
 
     // Resolve the intrinsic overload
-    auto match = MatchIntrinsic(kUnaryOperators[intrinsic_index], intrinsic_name, args,
-                                earliest_eval_stage, TemplateState{}, on_no_match);
+    auto match = MatchIntrinsic(*intrinsic_info, intrinsic_name, args, earliest_eval_stage,
+                                TemplateState{}, on_no_match);
     if (!match.overload) {
         return {};
     }
@@ -1226,48 +405,51 @@
                                    EvaluationStage earliest_eval_stage,
                                    const Source& source,
                                    bool is_compound) {
-    auto [intrinsic_index, intrinsic_name] = [&]() -> std::pair<size_t, const char*> {
+    auto [intrinsic_info, intrinsic_name] = [&]() -> std::pair<const IntrinsicInfo*, const char*> {
         switch (op) {
             case core::BinaryOp::kAnd:
-                return {kBinaryOperatorAnd, is_compound ? "operator &= " : "operator & "};
+                return {&data.binary_and, is_compound ? "operator &= " : "operator & "};
             case core::BinaryOp::kOr:
-                return {kBinaryOperatorOr, is_compound ? "operator |= " : "operator | "};
+                return {&data.binary_or, is_compound ? "operator |= " : "operator | "};
             case core::BinaryOp::kXor:
-                return {kBinaryOperatorXor, is_compound ? "operator ^= " : "operator ^ "};
+                return {&data.binary_xor, is_compound ? "operator ^= " : "operator ^ "};
             case core::BinaryOp::kLogicalAnd:
-                return {kBinaryOperatorLogicalAnd, "operator && "};
+                return {&data.binary_logical_and, "operator && "};
             case core::BinaryOp::kLogicalOr:
-                return {kBinaryOperatorLogicalOr, "operator || "};
+                return {&data.binary_logical_or, "operator || "};
             case core::BinaryOp::kEqual:
-                return {kBinaryOperatorEqual, "operator == "};
+                return {&data.binary_equal, "operator == "};
             case core::BinaryOp::kNotEqual:
-                return {kBinaryOperatorNotEqual, "operator != "};
+                return {&data.binary_not_equal, "operator != "};
             case core::BinaryOp::kLessThan:
-                return {kBinaryOperatorLessThan, "operator < "};
+                return {&data.binary_less_than, "operator < "};
             case core::BinaryOp::kGreaterThan:
-                return {kBinaryOperatorGreaterThan, "operator > "};
+                return {&data.binary_greater_than, "operator > "};
             case core::BinaryOp::kLessThanEqual:
-                return {kBinaryOperatorLessThanEqual, "operator <= "};
+                return {&data.binary_less_than_equal, "operator <= "};
             case core::BinaryOp::kGreaterThanEqual:
-                return {kBinaryOperatorGreaterThanEqual, "operator >= "};
+                return {&data.binary_greater_than_equal, "operator >= "};
             case core::BinaryOp::kShiftLeft:
-                return {kBinaryOperatorShiftLeft, is_compound ? "operator <<= " : "operator << "};
+                return {&data.binary_shift_left, is_compound ? "operator <<= " : "operator << "};
             case core::BinaryOp::kShiftRight:
-                return {kBinaryOperatorShiftRight, is_compound ? "operator >>= " : "operator >> "};
+                return {&data.binary_shift_right, is_compound ? "operator >>= " : "operator >> "};
             case core::BinaryOp::kAdd:
-                return {kBinaryOperatorPlus, is_compound ? "operator += " : "operator + "};
+                return {&data.binary_plus, is_compound ? "operator += " : "operator + "};
             case core::BinaryOp::kSubtract:
-                return {kBinaryOperatorMinus, is_compound ? "operator -= " : "operator - "};
+                return {&data.binary_minus, is_compound ? "operator -= " : "operator - "};
             case core::BinaryOp::kMultiply:
-                return {kBinaryOperatorStar, is_compound ? "operator *= " : "operator * "};
+                return {&data.binary_star, is_compound ? "operator *= " : "operator * "};
             case core::BinaryOp::kDivide:
-                return {kBinaryOperatorDivide, is_compound ? "operator /= " : "operator / "};
+                return {&data.binary_divide, is_compound ? "operator /= " : "operator / "};
             case core::BinaryOp::kModulo:
-                return {kBinaryOperatorModulo, is_compound ? "operator %= " : "operator % "};
+                return {&data.binary_modulo, is_compound ? "operator %= " : "operator % "};
         }
         TINT_UNREACHABLE() << "unhandled BinaryOp: " << op;
         return {};
     }();
+    if (!intrinsic_info) {
+        return {};
+    }
 
     Vector args{lhs, rhs};
 
@@ -1285,8 +467,8 @@
     };
 
     // Resolve the intrinsic overload
-    auto match = MatchIntrinsic(kBinaryOperators[intrinsic_index], intrinsic_name, args,
-                                earliest_eval_stage, TemplateState{}, on_no_match);
+    auto match = MatchIntrinsic(*intrinsic_info, intrinsic_name, args, earliest_eval_stage,
+                                TemplateState{}, on_no_match);
     if (!match.overload) {
         return {};
     }
@@ -1341,7 +523,7 @@
     }
 
     // Resolve the intrinsic overload
-    auto match = MatchIntrinsic(kConstructorsAndConverters[static_cast<size_t>(type)], name, args,
+    auto match = MatchIntrinsic(data.ctor_conv[static_cast<size_t>(type)], name, args,
                                 earliest_eval_stage, templates, on_no_match);
     if (!match.overload) {
         return {};
@@ -1435,7 +617,7 @@
     return IntrinsicPrototype{match.overload, return_type, std::move(match.parameters)};
 }
 
-Impl::Candidate Impl::ScoreOverload(const OverloadInfo* overload,
+Impl::Candidate Impl::ScoreOverload(const TableData::OverloadInfo* overload,
                                     VectorRef<const type::Type*> args,
                                     EvaluationStage earliest_eval_stage,
                                     const TemplateState& in_templates) const {
@@ -1614,14 +796,15 @@
 }
 
 MatchState Impl::Match(TemplateState& templates,
-                       const OverloadInfo* overload,
+                       const TableData::OverloadInfo* overload,
                        MatcherIndex const* matcher_indices,
                        EvaluationStage earliest_eval_stage) const {
-    return MatchState(builder, templates, matchers, overload, matcher_indices, earliest_eval_stage);
+    return MatchState{builder.Types(), builder.Symbols(), templates,          data,
+                      overload,        matcher_indices,   earliest_eval_stage};
 }
 
 void Impl::PrintOverload(StringStream& ss,
-                         const OverloadInfo* overload,
+                         const TableData::OverloadInfo* overload,
                          const char* intrinsic_name) const {
     TemplateState templates;
 
@@ -1702,30 +885,6 @@
     }
 }
 
-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,
                                 VectorRef<const type::Type*> args,
                                 TemplateState templates,
@@ -1765,7 +924,7 @@
 }  // namespace
 
 std::unique_ptr<Table> Table::Create(ProgramBuilder& builder) {
-    return std::make_unique<Impl>(builder);
+    return std::make_unique<Impl>(builder, CoreTableData());
 }
 
 Table::~Table() = default;
diff --git a/src/tint/lang/core/intrinsic/table_data.h b/src/tint/lang/core/intrinsic/table_data.h
new file mode 100644
index 0000000..6055d15
--- /dev/null
+++ b/src/tint/lang/core/intrinsic/table_data.h
@@ -0,0 +1,492 @@
+// Copyright 2023 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.
+
+#ifndef SRC_TINT_LANG_CORE_INTRINSIC_TABLE_DATA_H_
+#define SRC_TINT_LANG_CORE_INTRINSIC_TABLE_DATA_H_
+
+#include <stdint.h>
+#include <limits>
+#include <string>
+
+#include "src/tint/lang/core/constant/eval.h"
+#include "src/tint/lang/core/evaluation_stage.h"
+#include "src/tint/lang/core/parameter_usage.h"
+#include "src/tint/utils/containers/enum_set.h"
+#include "src/tint/utils/containers/slice.h"
+
+namespace tint::type {
+class Manager;
+}  // namespace tint::type
+
+namespace tint::core::intrinsic {
+
+/// TableData holds the immutable data that holds the intrinsic data for a language.
+struct TableData {
+    /// Index type used for matcher indices
+    using MatcherIndex = uint8_t;
+
+    /// Index value used for template types / numbers that do not have a constraint
+    static constexpr MatcherIndex kNoMatcher = std::numeric_limits<MatcherIndex>::max();
+
+    /// Unique flag bits for overloads
+    enum class OverloadFlag {
+        kIsBuiltin,                 // The overload is a builtin ('fn')
+        kIsOperator,                // The overload is an operator ('op')
+        kIsConstructor,             // The overload is a value constructor ('ctor')
+        kIsConverter,               // The overload is a value 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
+        kMustUse,                   // The overload cannot be called as a statement
+        kIsDeprecated,              // The overload is deprecated
+    };
+
+    /// An enum set of OverloadFlag, used by OperatorInfo
+    using OverloadFlags = tint::EnumSet<OverloadFlag>;
+
+    /// 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.
+        constant::Eval::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;
+    };
+
+    /// 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
+    class Number {
+        enum State {
+            kInvalid,
+            kValid,
+            kAny,
+        };
+
+        constexpr explicit Number(State state) : state_(state) {}
+
+      public:
+        /// A special number representing any number
+        static const Number any;
+        /// An invalid number
+        static const Number invalid;
+
+        /// Constructed as a valid number with the value v
+        /// @param v the value for the number
+        explicit constexpr 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
+        /// @param n the new value for the number
+        /// @returns this so calls can be chained
+        inline Number& operator=(uint32_t n) {
+            value_ = n;
+            state_ = kValid;
+            return *this;
+        }
+
+      private:
+        uint32_t value_ = 0;
+        State state_ = kInvalid;
+    };
+
+    /// A special type that matches all TypeMatchers
+    class Any final : public Castable<Any, type::Type> {
+      public:
+        Any();
+        ~Any() override;
+
+        /// @copydoc type::UniqueNode::Equals
+        bool Equals(const type::UniqueNode& other) const override;
+        /// @copydoc type::Type::FriendlyName
+        std::string FriendlyName() const override;
+        /// @copydoc type::Type::Clone
+        type::Type* Clone(type::CloneContext& ctx) const override;
+    };
+
+    /// TemplateState holds the state of the template numbers and types.
+    /// Used by the MatchState.
+    class TemplateState {
+      public:
+        /// If the template type with index @p idx is undefined, then it is defined with the @p ty
+        /// and Type() returns @p ty. If the template type is defined, and @p 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 @p ty, then the template type is replaced with
+        /// @p ty, and @p ty is returned. If none of the above applies, then @p ty is a type
+        /// mismatch for the template type, and nullptr is returned.
+        /// @param idx the index of the template type
+        /// @param ty the type
+        /// @returns true on match or newly defined
+        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(Vector{t, ty});
+            if (ty) {
+                t = ty;
+            }
+            return ty;
+        }
+
+        /// If the number with index @p 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 @p ty.
+        /// @param idx the index of the template number
+        /// @param number the number
+        /// @returns true on match or newly defined
+        bool Num(size_t idx, TableData::Number number) {
+            if (idx >= numbers_.Length()) {
+                numbers_.Resize(idx + 1, TableData::Number::invalid);
+            }
+            auto& n = numbers_[idx];
+            if (!n.IsValid()) {
+                n = number.Value();
+                return true;
+            }
+            return n.Value() == number.Value();
+        }
+
+        /// @param idx the index of the template type
+        /// @returns the template type with index @p 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 @p idx with type @p ty.
+        /// @param idx the index of the template type
+        /// @param ty the new type for the template
+        void SetType(size_t idx, const type::Type* ty) {
+            if (idx >= types_.Length()) {
+                types_.Resize(idx + 1);
+            }
+            types_[idx] = ty;
+        }
+
+        /// @returns the number type with index @p idx.
+        /// @param idx the index of the template number
+        TableData::Number Num(size_t idx) const {
+            if (idx >= numbers_.Length()) {
+                return TableData::Number::invalid;
+            }
+            return numbers_[idx];
+        }
+
+        /// @return the total number of type and number templates
+        size_t Count() const { return types_.Length() + numbers_.Length(); }
+
+      private:
+        Vector<const type::Type*, 4> types_;
+        Vector<TableData::Number, 2> numbers_;
+    };
+
+    /// The current overload match state
+    /// MatchState holds the state used to match an overload.
+    class MatchState {
+      public:
+        /// Constructor
+        /// @param ty_mgr the type manager
+        /// @param syms the symbol table
+        /// @param t the template state
+        /// @param d the table data
+        /// @param o the current overload
+        /// @param matcher_indices the remaining matcher indices
+        /// @param s the required evaluation stage of the overload
+        MatchState(type::Manager& ty_mgr,
+                   SymbolTable& syms,
+                   TemplateState& t,
+                   const TableData& d,
+                   const OverloadInfo* o,
+                   MatcherIndex const* matcher_indices,
+                   EvaluationStage s)
+            : types(ty_mgr),
+              symbols(syms),
+              templates(t),
+              data(d),
+              overload(o),
+              earliest_eval_stage(s),
+              matcher_indices_(matcher_indices) {}
+
+        /// The type manager
+        type::Manager& types;
+
+        /// The symbol manager
+        SymbolTable& symbols;
+
+        /// The template types and numbers
+        TemplateState& templates;
+
+        /// The table data
+        TableData const& data;
+
+        /// The current overload being evaluated
+        OverloadInfo const* const overload;
+
+        /// The earliest evaluation stage of the builtin call
+        EvaluationStage earliest_eval_stage;
+
+        /// Type uses the next TypeMatcher from the matcher indices to match the type @p ty.
+        /// @param ty the type to try matching
+        /// @returns the canonical expected type if the type matches, otherwise nullptr.
+        /// @note: The matcher indices are progressed on calling.
+        const type::Type* Type(const type::Type* ty) {
+            MatcherIndex matcher_index = *matcher_indices_++;
+            auto& matcher = data.type_matchers[matcher_index];
+            return matcher.match(*this, ty);
+        }
+
+        /// Num uses the next NumMatcher from the matcher indices to match @p number.
+        /// @param number the number to try matching
+        /// @returns the canonical expected number if the number matches, otherwise an invalid
+        /// number.
+        /// @note: The matcher indices are progressed on calling.
+        Number Num(Number number) {
+            MatcherIndex matcher_index = *matcher_indices_++;
+            auto& matcher = data.number_matchers[matcher_index];
+            return matcher.match(*this, number);
+        }
+
+        /// @returns a string representation of the next TypeMatcher from the matcher indices.
+        /// @note: The matcher indices are progressed on calling.
+        std::string TypeName() {
+            MatcherIndex matcher_index = *matcher_indices_++;
+            auto& matcher = data.type_matchers[matcher_index];
+            return matcher.string(this);
+        }
+
+        /// @returns a string representation of the next NumberMatcher from the matcher indices.
+        /// @note: The matcher indices are progressed on calling.
+        std::string NumName() {
+            MatcherIndex matcher_index = *matcher_indices_++;
+            auto& matcher = data.number_matchers[matcher_index];
+            return matcher.string(this);
+        }
+
+      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.
+    struct TypeMatcher {
+        /// 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.
+        /// The parameter `type` is the type to match
+        /// Returns the canonicalized type on match, otherwise nullptr
+        using MatchFn = const type::Type*(MatchState& state, const type::Type* type);
+
+        /// @see #MatchFn
+        MatchFn* const match;
+
+        /// Returns a string representation of the matcher.
+        /// Used for printing error messages when no overload is found.
+        using StringFn = std::string(MatchState* state);
+
+        /// @see #StringFn
+        StringFn* const string;
+    };
+
+    /// A NumberMatcher is the interface used to match a number or enumerator used
+    /// as part of an overload's parameter or return type.
+    struct NumberMatcher {
+        /// Checks whether the given number matches the matcher rules.
+        /// Match may define template numbers in state.
+        /// The parameter `number` is the number to match
+        /// Returns true if the argument type is as expected.
+        using MatchFn = Number(MatchState& state, Number number);
+
+        /// @see #MatchFn
+        MatchFn* const match;
+
+        /// Returns a string representation of the matcher.
+        /// Used for printing error messages when no overload is found.
+        using StringFn = std::string(MatchState* state);
+
+        /// @see #StringFn
+        StringFn* const string;
+    };
+
+    /// 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
+    template <size_t INDEX>
+    struct TemplateTypeMatcher {
+        /// The TypeMatcher for the template type with the index `INDEX`
+        static constexpr TypeMatcher matcher{
+            /* match */
+            [](MatchState& state, const type::Type* type) -> const type::Type* {
+                if (type->Is<Any>()) {
+                    return state.templates.Type(INDEX);
+                }
+                if (auto* templates = state.templates.Type(INDEX, type)) {
+                    return templates;
+                }
+                return nullptr;
+            },
+            /* string */
+            [](MatchState* state) -> std::string {
+                return state->overload->template_types[INDEX].name;
+            },
+        };
+    };
+
+    /// 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)
+    template <size_t INDEX>
+    struct TemplateNumberMatcher {
+        /// The NumberMatcher for the template number with the index `INDEX`
+        static constexpr NumberMatcher matcher{
+            /* match */
+            [](TableData::MatchState& state, TableData::Number number) -> TableData::Number {
+                if (number.IsAny()) {
+                    return state.templates.Num(INDEX);
+                }
+                return state.templates.Num(INDEX, number) ? number : TableData::Number::invalid;
+            },
+            /* string */
+            [](TableData::MatchState* state) -> std::string {
+                return state->overload->template_numbers[INDEX].name;
+            },
+        };
+    };
+
+    /// The list of type matchers used by the intrinsic overloads
+    Slice<TypeMatcher const> const type_matchers;
+    /// The list of number matchers used by the intrinsic overloads
+    Slice<NumberMatcher const> const number_matchers;
+    /// The type constructor and convertor intrinsic overloads
+    Slice<IntrinsicInfo const> const ctor_conv;
+    /// The builtin function intrinsic overloads
+    Slice<IntrinsicInfo const> const builtins;
+    /// The IntrinsicInfo for the binary operator 'plus'
+    IntrinsicInfo const& binary_plus;
+    /// The IntrinsicInfo for the binary operator 'minus'
+    IntrinsicInfo const& binary_minus;
+    /// The IntrinsicInfo for the binary operator 'star'
+    IntrinsicInfo const& binary_star;
+    /// The IntrinsicInfo for the binary operator 'divide'
+    IntrinsicInfo const& binary_divide;
+    /// The IntrinsicInfo for the binary operator 'modulo'
+    IntrinsicInfo const& binary_modulo;
+    /// The IntrinsicInfo for the binary operator 'xor'
+    IntrinsicInfo const& binary_xor;
+    /// The IntrinsicInfo for the binary operator 'and'
+    IntrinsicInfo const& binary_and;
+    /// The IntrinsicInfo for the binary operator 'or'
+    IntrinsicInfo const& binary_or;
+    /// The IntrinsicInfo for the binary operator 'logical_and'
+    IntrinsicInfo const& binary_logical_and;
+    /// The IntrinsicInfo for the binary operator 'logical_or'
+    IntrinsicInfo const& binary_logical_or;
+    /// The IntrinsicInfo for the binary operator 'equal'
+    IntrinsicInfo const& binary_equal;
+    /// The IntrinsicInfo for the binary operator 'not_equal'
+    IntrinsicInfo const& binary_not_equal;
+    /// The IntrinsicInfo for the binary operator 'less_than'
+    IntrinsicInfo const& binary_less_than;
+    /// The IntrinsicInfo for the binary operator 'greater_than'
+    IntrinsicInfo const& binary_greater_than;
+    /// The IntrinsicInfo for the binary operator 'less_than_equal'
+    IntrinsicInfo const& binary_less_than_equal;
+    /// The IntrinsicInfo for the binary operator 'greater_than_equal'
+    IntrinsicInfo const& binary_greater_than_equal;
+    /// The IntrinsicInfo for the binary operator 'shift_left'
+    IntrinsicInfo const& binary_shift_left;
+    /// The IntrinsicInfo for the binary operator 'shift_right'
+    IntrinsicInfo const& binary_shift_right;
+    /// The IntrinsicInfo for the unary operator 'not'
+    IntrinsicInfo const& unary_not;
+    /// The IntrinsicInfo for the unary operator 'complement'
+    IntrinsicInfo const& unary_complement;
+    /// The IntrinsicInfo for the unary operator 'minus'
+    IntrinsicInfo const& unary_minus;
+};
+
+}  // namespace tint::core::intrinsic
+
+#endif  // SRC_TINT_LANG_CORE_INTRINSIC_TABLE_DATA_H_
diff --git a/src/tint/lang/core/type/manager.cc b/src/tint/lang/core/type/manager.cc
index 07bbf2a..a1178f2 100644
--- a/src/tint/lang/core/type/manager.cc
+++ b/src/tint/lang/core/type/manager.cc
@@ -100,6 +100,10 @@
     return Get<type::Matrix>(vec(inner, rows), cols);
 }
 
+const type::Matrix* Manager::mat(const type::Vector* column_type, uint32_t cols) {
+    return Get<type::Matrix>(column_type, cols);
+}
+
 const type::Matrix* Manager::mat2x2(const type::Type* inner) {
     return mat(inner, 2, 2);
 }
diff --git a/src/tint/lang/core/type/manager.h b/src/tint/lang/core/type/manager.h
index 6189ccd..55def97 100644
--- a/src/tint/lang/core/type/manager.h
+++ b/src/tint/lang/core/type/manager.h
@@ -238,6 +238,11 @@
     /// @returns the matrix type
     const type::Matrix* mat(const type::Type* inner, uint32_t cols, uint32_t rows);
 
+    /// @param column_type the column vector type
+    /// @param cols the number of columns
+    /// @returns the matrix type
+    const type::Matrix* mat(const type::Vector* column_type, uint32_t cols);
+
     /// @param inner the inner type
     /// @returns a mat2x2 with the element @p inner
     const type::Matrix* mat2x2(const type::Type* inner);
diff --git a/src/tint/lang/core/intrinsic/table.inl.tmpl b/src/tint/utils/templates/intrinsic_table_data.tmpl.inc
similarity index 63%
rename from src/tint/lang/core/intrinsic/table.inl.tmpl
rename to src/tint/utils/templates/intrinsic_table_data.tmpl.inc
index 0e9b104..467a871 100644
--- a/src/tint/lang/core/intrinsic/table.inl.tmpl
+++ b/src/tint/utils/templates/intrinsic_table_data.tmpl.inc
@@ -1,7 +1,7 @@
 {{- /*
 --------------------------------------------------------------------------------
-Template file for use with tools/src/cmd/gen to generate intrinsic_table.inl
-Used by BuiltinTable.cc for builtin overload resolution.
+Template file for use with tools/src/cmd/gen to generate the constant data that
+is held in a core::intrinsic::TableData.
 
 To update the generated file, run:
     ./tools/run gen
@@ -12,7 +12,37 @@
 --------------------------------------------------------------------------------
 */ -}}
 
-{{- $I := LoadIntrinsics "src/tint/lang/core/core.def" -}}
+
+{{- /* ------------------------------------------------------------------ */ -}}
+{{-                              define "Data"                               -}}
+{{- /* ------------------------------------------------------------------ */ -}}
+{{- $I := $.Intrinsics -}}
+
+namespace {{$.Namespace}} {
+namespace {
+
+using IntrinsicInfo = tint::core::intrinsic::TableData::IntrinsicInfo;
+using MatcherIndex = tint::core::intrinsic::TableData::MatcherIndex;
+using MatchState = tint::core::intrinsic::TableData::MatchState;
+using Number = tint::core::intrinsic::TableData::Number;
+using NumberMatcher = tint::core::intrinsic::TableData::NumberMatcher;
+using OverloadFlag = tint::core::intrinsic::TableData::OverloadFlag;
+using OverloadFlags = tint::core::intrinsic::TableData::OverloadFlags;
+using OverloadInfo = tint::core::intrinsic::TableData::OverloadInfo;
+using ParameterInfo = tint::core::intrinsic::TableData::ParameterInfo;
+using StringStream = tint::StringStream;
+using TemplateNumberInfo = tint::core::intrinsic::TableData::TemplateNumberInfo;
+using TemplateTypeInfo = tint::core::intrinsic::TableData::TemplateTypeInfo;
+using Type = tint::type::Type;
+using TypeMatcher = tint::core::intrinsic::TableData::TypeMatcher;
+
+template<size_t N>
+using TemplateNumberMatcher = tint::core::intrinsic::TableData::TemplateNumberMatcher<N>;
+
+template<size_t N>
+using TemplateTypeMatcher = tint::core::intrinsic::TableData::TemplateTypeMatcher<N>;
+
+static constexpr auto kNoMatcher = tint::core::intrinsic::TableData::kNoMatcher;
 
 // clang-format off
 
@@ -178,153 +208,148 @@
 };
 
 // clang-format on
-{{ end -}}
+
+}  // anonymous namespace
+
+const TableData& {{$.Name}}() {
+  static const TableData data{
+    /* type_matchers */ kTypeMatchers,
+    /* number_matchers */ kNumberMatchers,
+    /* ctor_conv */ kConstructorsAndConverters,
+    /* builtins */ kBuiltins,
+    /* binary_plus */ kBinaryOperators[kBinaryOperatorPlus],
+    /* binary_minus */ kBinaryOperators[kBinaryOperatorMinus],
+    /* binary_star */ kBinaryOperators[kBinaryOperatorStar],
+    /* binary_divide */ kBinaryOperators[kBinaryOperatorDivide],
+    /* binary_modulo */ kBinaryOperators[kBinaryOperatorModulo],
+    /* binary_xor */ kBinaryOperators[kBinaryOperatorXor],
+    /* binary_and */ kBinaryOperators[kBinaryOperatorAnd],
+    /* binary_or */ kBinaryOperators[kBinaryOperatorOr],
+    /* binary_logical_and */ kBinaryOperators[kBinaryOperatorLogicalAnd],
+    /* binary_logical_or */ kBinaryOperators[kBinaryOperatorLogicalOr],
+    /* binary_equal */ kBinaryOperators[kBinaryOperatorEqual],
+    /* binary_not_equal */ kBinaryOperators[kBinaryOperatorNotEqual],
+    /* binary_less_than */ kBinaryOperators[kBinaryOperatorLessThan],
+    /* binary_greater_than */ kBinaryOperators[kBinaryOperatorGreaterThan],
+    /* binary_less_than_equal */ kBinaryOperators[kBinaryOperatorLessThanEqual],
+    /* binary_greater_than_equal */ kBinaryOperators[kBinaryOperatorGreaterThanEqual],
+    /* binary_shift_left */ kBinaryOperators[kBinaryOperatorShiftLeft],
+    /* binary_shift_right */ kBinaryOperators[kBinaryOperatorShiftRight],
+    /* unary_not */ kUnaryOperators[kUnaryOperatorNot],
+    /* unary_complement */ kUnaryOperators[kUnaryOperatorComplement],
+    /* unary_minus */ kUnaryOperators[kUnaryOperatorMinus],
+  };
+  return data;
+}
+
+}  // namespace {{$.Namespace}}
+
+{{  end -}}
+{{- end -}}
+
 
 {{- /* ------------------------------------------------------------------ */ -}}
 {{-                              define "Type"                               -}}
 {{- /* ------------------------------------------------------------------ */ -}}
 {{- $class := PascalCase .Name -}}
 /// TypeMatcher for 'type {{.Name}}'
-{{- if .Decl.Source.S.Filepath  }}
-{{- end  }}
-class {{$class}} : public TypeMatcher {
- public:
-  /// Checks whether the given type matches the matcher rules.
-  /// Match may define and refine the template types and numbers in state.
-  /// @param state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
-};
-
-const type::Type* {{$class}}::Match(MatchState& state, const type::Type* ty) const {
+constexpr TypeMatcher k{{$class}}Matcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
 {{- range .TemplateParams }}
 {{-   template "DeclareLocalTemplateParam" . }}
 {{- end  }}
-  if (!match_{{TrimLeft .Name "_"}}(state, ty{{range .TemplateParams}}, {{.GetName}}{{end}})) {
-    return nullptr;
-  }
+    if (!match_{{TrimLeft .Name "_"}}(state, ty{{range .TemplateParams}}, {{.GetName}}{{end}})) {
+      return nullptr;
+    }
 {{- range .TemplateParams }}
-  {{.Name}} = {{ template "MatchTemplateParam" .}}({{.Name}});
-  if ({{ template "IsTemplateParamInvalid" .}}) {
-    return nullptr;
-  }
+    {{.Name}} = {{ template "MatchTemplateParam" .}}({{.Name}});
+    if ({{ template "IsTemplateParamInvalid" .}}) {
+      return nullptr;
+    }
 {{- end  }}
-  return build_{{TrimLeft .Name "_"}}(state{{range .TemplateParams}}, {{.GetName}}{{end}});
-}
-
-std::string {{$class}}::String(MatchState*{{if .TemplateParams}} state{{end}}) const {
+    return build_{{TrimLeft .Name "_"}}(state{{range .TemplateParams}}, {{.GetName}}{{end}});
+  },
+/* string */ [](MatchState*{{if .TemplateParams}} state{{end}}) -> std::string {
 {{- range .TemplateParams }}
 {{-   template "DeclareLocalTemplateParamName" . }}
 {{- end  }}
 
 {{- if .DisplayName }}
-  StringStream ss;
-  ss{{range SplitDisplayName .DisplayName}} << {{.}}{{end}};
-  return ss.str();
+    StringStream ss;
+    ss{{range SplitDisplayName .DisplayName}} << {{.}}{{end}};
+    return ss.str();
 {{- else if .TemplateParams }}
-  return "{{.Name}}<"{{template "AppendTemplateParamNames" .TemplateParams}} + ">";
+    return "{{.Name}}<"{{template "AppendTemplateParamNames" .TemplateParams}} + ">";
 {{- else }}
-  return "{{.Name}}";
+    return "{{.Name}}";
 {{- end  }}
-}
+  }
+};
+
 {{  end -}}
 
+
 {{- /* ------------------------------------------------------------------ */ -}}
 {{-                          define "TypeMatcher"                            -}}
 {{- /* ------------------------------------------------------------------ */ -}}
 {{- $class := PascalCase .Name -}}
 /// TypeMatcher for 'match {{.Name}}'
-{{- if .Decl.Source.S.Filepath  }}
-{{- end  }}
-class {{$class}} : public TypeMatcher {
- public:
-  /// 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 state the MatchState
-  /// @param type the type to match
-  /// @returns the canonicalized type on match, otherwise nullptr
-  const type::Type* Match(MatchState& state,
-                         const type::Type* type) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
-};
-
-const type::Type* {{$class}}::Match(MatchState& state, const type::Type* ty) const {
+constexpr TypeMatcher k{{$class}}Matcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
 {{- range .PrecedenceSortedTypes }}
-  if (match_{{.Name}}(state, ty)) {
-    return build_{{.Name}}(state);
-  }
+    if (match_{{.Name}}(state, ty)) {
+      return build_{{.Name}}(state);
+    }
 {{- end }}
-  return nullptr;
-}
-
-std::string {{$class}}::String(MatchState*) const {
-  StringStream ss;
-  // Note: We pass nullptr to the TypeMatcher::String() functions, as 'matcher's do not support
-  // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
-  ss
+    return nullptr;
+  },
+/* string */ [](MatchState*) -> std::string {
+    StringStream ss;
+    // Note: We pass nullptr to the TypeMatcher::String() functions, as 'matcher's do not support
+    // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
+    ss
 {{- range .Types -}}
-{{-   if      IsFirstIn . $.Types }} << {{PascalCase .Name}}().String(nullptr)
-{{-   else if IsLastIn  . $.Types }} << " or " << {{PascalCase .Name}}().String(nullptr)
-{{-   else                        }} << ", " << {{PascalCase .Name}}().String(nullptr)
+{{-   if      IsFirstIn . $.Types }} << k{{PascalCase .Name}}Matcher.string(nullptr)
+{{-   else if IsLastIn  . $.Types }} << " or " << k{{PascalCase .Name}}Matcher.string(nullptr)
+{{-   else                        }} << ", " << k{{PascalCase .Name}}Matcher.string(nullptr)
 {{-   end -}}
 {{- end -}};
-  return ss.str();
-}
+    return ss.str();
+  }
+};
 {{  end -}}
 
+
 {{- /* ------------------------------------------------------------------ */ -}}
 {{-                          define "EnumMatcher"                            -}}
 {{- /* ------------------------------------------------------------------ */ -}}
 {{- $class := PascalCase .Name -}}
 {{- $enum := PascalCase .Enum.Name -}}
 /// EnumMatcher for 'match {{.Name}}'
-{{- if .Decl.Source.S.Filepath  }}
-{{- end  }}
-class {{$class}} : public NumberMatcher {
- public:
-  /// Checks whether the given number matches the enum matcher rules.
-  /// Match may define template numbers in state.
-  /// @param state the MatchState
-  /// @param number the enum value as a Number
-  /// @return true if the enum value matches the set
-  Number Match(MatchState& state, Number number) const override;
-  /// @param state the MatchState
-  /// @return a string representation of the matcher.
-  std::string String(MatchState* state) const override;
-};
-
+constexpr NumberMatcher k{{$class}}Matcher {
 {{ if eq 1 (len .Options) -}}
 {{-   $option := index .Options 0 }}
 {{-   $entry := printf "k%v" (PascalCase $option.Name) -}}
-Number {{$class}}::Match(MatchState&, Number number) const {
-  if (number.IsAny() || number.Value() == static_cast<uint32_t>({{$enum}}::{{$entry}})) {
-    return Number(static_cast<uint32_t>({{$enum}}::{{$entry}}));
-  }
-  return Number::invalid;
-}
+/* match */ [](MatchState&, Number number) -> Number {
+    if (number.IsAny() || number.Value() == static_cast<uint32_t>({{$enum}}::{{$entry}})) {
+      return Number(static_cast<uint32_t>({{$enum}}::{{$entry}}));
+    }
+    return Number::invalid;
+  },
 {{- else -}}
-Number {{$class}}::Match(MatchState&, Number number) const {
-  switch (static_cast<{{$enum}}>(number.Value())) {
+/* match */ [](MatchState&, Number number) -> Number {
+    switch (static_cast<{{$enum}}>(number.Value())) {
 {{-   range .Options }}
-    case {{$enum}}::k{{PascalCase .Name}}:
+      case {{$enum}}::k{{PascalCase .Name}}:
 {{-   end }}
-      return number;
-    default:
-      return Number::invalid;
-  }
-}
+        return number;
+      default:
+        return Number::invalid;
+    }
+  },
 {{- end }}
-
-std::string {{$class}}::String(MatchState*) const {
-  return "
+/* string */ [](MatchState*) -> std::string {
+    return "
 {{- range .Options -}}
 {{-   if      IsFirstIn . $.Options }}{{.Name}}
 {{-   else if IsLastIn  . $.Options }} or {{.Name}}
@@ -332,79 +357,65 @@
 {{-   end -}}
 {{- end -}}
 ";
-}
+  }
+};
 {{  end -}}
 
 {{- /* ------------------------------------------------------------------ */ -}}
 {{-                            define "Matchers"                             -}}
 {{- /* ------------------------------------------------------------------ */ -}}
-/// Matchers holds type and number matchers
-class Matchers {
- private:
+/// Type and number matchers
 {{- $t_names := Map -}}
 {{- $n_names := Map -}}
 {{- range Iterate $.Sem.MaxTemplateTypes -}}
-{{-   $name := printf "template_type_%v" . -}}
+{{-   $name := printf "TemplateTypeMatcher<%v>::matcher" . -}}
 {{-   $t_names.Put . $name }}
-  TemplateTypeMatcher {{$name}}_{ {{- . -}} };
 {{- end }}
 {{- range Iterate $.Sem.MaxTemplateNumbers -}}
-{{-   $name := printf "template_number_%v" . -}}
+{{-   $name := printf "TemplateNumberMatcher<%v>::matcher" . -}}
 {{-   $n_names.Put . $name }}
-  TemplateNumberMatcher {{$name}}_{ {{- . -}} };
 {{- end }}
 {{- range $.Sem.Types -}}
-{{-   $name := PascalCase .Name -}}
+{{-   $name := printf "k%vMatcher" (PascalCase .Name) -}}
 {{-   $t_names.Put . $name }}
-  {{$name}} {{$name}}_;
 {{- end }}
 {{- range $.Sem.TypeMatchers -}}
-{{-   $name := PascalCase .Name -}}
+{{-   $name := printf "k%vMatcher" (PascalCase .Name) -}}
 {{-   $t_names.Put . $name }}
-  {{$name}} {{$name}}_;
 {{- end }}
 {{- range $.Sem.EnumMatchers -}}
-{{-   $name := PascalCase .Name -}}
+{{-   $name := printf "k%vMatcher" (PascalCase .Name) -}}
 {{-   $n_names.Put . $name }}
-  {{$name}} {{$name}}_;
 {{- end }}
 
- public:
-  /// Constructor
-  Matchers();
-  /// Destructor
-  ~Matchers();
-
-  /// The template types, types, and type matchers
-  TypeMatcher const* const type[{{len $.Table.TMatchers}}] = {
+/// The template types, types, and type matchers
+constexpr TypeMatcher kTypeMatchers[] = {
 {{- range $i, $m := $.Table.TMatchers }}
-    /* [{{$i}}] */
-{{-   if $m }} &{{$t_names.Get $m}}_,
-{{-   else  }} &{{$t_names.Get $i}}_,
+  /* [{{$i}}] */
+{{-   if $m }} {{$t_names.Get $m}},
+{{-   else  }} {{$t_names.Get $i}},
 {{-   end   }}
 {{- end }}
-  };
-
-  /// The template numbers, and number matchers
-  NumberMatcher const* const number[{{len $.Table.NMatchers}}] = {
-{{- range $i, $m := $.Table.NMatchers }}
-    /* [{{$i}}] */
-{{-   if $m }} &{{$n_names.Get $m}}_,
-{{-   else  }} &{{$n_names.Get $i}}_,
-{{-   end   }}
-{{- end }}
-  };
 };
 
-Matchers::Matchers() = default;
-Matchers::~Matchers() = default;
+/// The template numbers, and number matchers
+constexpr NumberMatcher kNumberMatchers[] = {
+{{- range $i, $m := $.Table.NMatchers }}
+  /* [{{$i}}] */
+{{-   if $m }} {{$n_names.Get $m}},
+{{-   else  }} {{$n_names.Get $i}},
+{{-   end   }}
+{{- end }}
+};
+
 {{- end -}}
 
+
 {{- /* ------------------------------------------------------------------ */ -}}
 {{-                     define "DeclareLocalTemplateParam"                   -}}
 {{- /* ------------------------------------------------------------------ */ -}}
 {{-   if      IsTemplateTypeParam . }}
-  const type::Type* {{.Name}} = nullptr;
+  const Type* {{.Name}} = nullptr;
 {{-   else if IsTemplateNumberParam . }}
   Number {{.Name}} = Number::invalid;
 {{-   else if IsTemplateEnumParam . }}
@@ -412,6 +423,7 @@
 {{-   end -}}
 {{- end -}}
 
+
 {{- /* ------------------------------------------------------------------ */ -}}
 {{-                   define "DeclareLocalTemplateParamName"                 -}}
 {{- /* ------------------------------------------------------------------ */ -}}
@@ -424,6 +436,7 @@
 {{-   end -}}
 {{- end -}}
 
+
 {{- /* ------------------------------------------------------------------ */ -}}
 {{-                       define "MatchTemplateParam"                        -}}
 {{- /* ------------------------------------------------------------------ */ -}}
@@ -436,6 +449,7 @@
 {{-   end -}}
 {{- end -}}
 
+
 {{- /* ------------------------------------------------------------------ */ -}}
 {{-                       define "IsTemplateParamInvalid"                    -}}
 {{- /* ------------------------------------------------------------------ */ -}}
@@ -448,6 +462,7 @@
 {{-   end -}}
 {{- end -}}
 
+
 {{- /* ------------------------------------------------------------------ */ -}}
 {{-                      define "AppendTemplateParamNames"                   -}}
 {{- /* ------------------------------------------------------------------ */ -}}
@@ -458,6 +473,7 @@
 {{-   end -}}
 {{- end -}}
 
+
 {{- /* ------------------------------------------------------------------ */ -}}
 {{-                           define "ExpandName"                            -}}
 {{- /* ------------------------------------------------------------------ */ -}}
@@ -494,4 +510,3 @@
 {{-   if eq .Kind "operator" -}}Op{{end -}}
   {{template "ExpandName" .ConstEvalFunction}}
 {{- end -}}
-
diff --git a/tools/src/cmd/git-stats/main.go b/tools/src/cmd/git-stats/main.go
index 1830e6f..38374de 100644
--- a/tools/src/cmd/git-stats/main.go
+++ b/tools/src/cmd/git-stats/main.go
@@ -62,6 +62,7 @@
 		"package-lock.json",
 		"src/tint/builtin_table.inl",
 		"src/tint/lang/core/intrinsic/table.inl",
+		"src/tint/lang/core/intrinsic/core_table_data.cc.tmpl",
 		"test/tint/",
 		"third_party/gn/webgpu-cts/test_list.txt",
 		"third_party/khronos/",