Import Tint changes from Dawn

Changes:
  - 5ca21ed720844ddd5b10547f0758564cfc59997a [ir] Move `sampled_image` over to spirv builtins. by dan sinclair <dsinclair@chromium.org>
  - 3472b80985d2f65defd5697db45a3e2ba700967a Resolve subgroupBroadcast by David Neto <dneto@google.com>
GitOrigin-RevId: 5ca21ed720844ddd5b10547f0758564cfc59997a
Change-Id: Ie357924e3b72927cd80a5688f63a2594f24a7da1
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/152320
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
diff --git a/src/tint/lang/spirv/BUILD.cmake b/src/tint/lang/spirv/BUILD.cmake
index 359a3c6..c6fc438 100644
--- a/src/tint/lang/spirv/BUILD.cmake
+++ b/src/tint/lang/spirv/BUILD.cmake
@@ -24,4 +24,5 @@
 include(lang/spirv/intrinsic/BUILD.cmake)
 include(lang/spirv/ir/BUILD.cmake)
 include(lang/spirv/reader/BUILD.cmake)
+include(lang/spirv/type/BUILD.cmake)
 include(lang/spirv/writer/BUILD.cmake)
diff --git a/src/tint/lang/spirv/intrinsic/data/BUILD.bazel b/src/tint/lang/spirv/intrinsic/data/BUILD.bazel
index e43a7f6..8239db0 100644
--- a/src/tint/lang/spirv/intrinsic/data/BUILD.bazel
+++ b/src/tint/lang/spirv/intrinsic/data/BUILD.bazel
@@ -37,7 +37,9 @@
     "//src/tint/lang/core/constant",
     "//src/tint/lang/core/intrinsic",
     "//src/tint/lang/core/intrinsic/data",
+    "//src/tint/lang/core/ir",
     "//src/tint/lang/core/type",
+    "//src/tint/lang/spirv/type",
     "//src/tint/utils/containers",
     "//src/tint/utils/ice",
     "//src/tint/utils/id",
diff --git a/src/tint/lang/spirv/intrinsic/data/BUILD.cmake b/src/tint/lang/spirv/intrinsic/data/BUILD.cmake
index 1f5e78f..bfa57c3 100644
--- a/src/tint/lang/spirv/intrinsic/data/BUILD.cmake
+++ b/src/tint/lang/spirv/intrinsic/data/BUILD.cmake
@@ -36,7 +36,9 @@
   tint_lang_core_constant
   tint_lang_core_intrinsic
   tint_lang_core_intrinsic_data
+  tint_lang_core_ir
   tint_lang_core_type
+  tint_lang_spirv_type
   tint_utils_containers
   tint_utils_ice
   tint_utils_id
diff --git a/src/tint/lang/spirv/intrinsic/data/BUILD.gn b/src/tint/lang/spirv/intrinsic/data/BUILD.gn
index 4ae224e..642a14a 100644
--- a/src/tint/lang/spirv/intrinsic/data/BUILD.gn
+++ b/src/tint/lang/spirv/intrinsic/data/BUILD.gn
@@ -36,7 +36,9 @@
     "${tint_src_dir}/lang/core/constant",
     "${tint_src_dir}/lang/core/intrinsic",
     "${tint_src_dir}/lang/core/intrinsic/data",
+    "${tint_src_dir}/lang/core/ir",
     "${tint_src_dir}/lang/core/type",
+    "${tint_src_dir}/lang/spirv/type",
     "${tint_src_dir}/utils/containers",
     "${tint_src_dir}/utils/ice",
     "${tint_src_dir}/utils/id",
diff --git a/src/tint/lang/spirv/intrinsic/data/data.cc b/src/tint/lang/spirv/intrinsic/data/data.cc
index 666a8b9..4add088 100644
--- a/src/tint/lang/spirv/intrinsic/data/data.cc
+++ b/src/tint/lang/spirv/intrinsic/data/data.cc
@@ -458,6 +458,210 @@
 };
 
 
+/// TypeMatcher for 'type sampler'
+constexpr TypeMatcher kSamplerMatcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+    if (!MatchSampler(state, ty)) {
+      return nullptr;
+    }
+    return BuildSampler(state, ty);
+  },
+/* string */ [](MatchState*) -> std::string {
+    return "sampler";
+  }
+};
+
+
+/// TypeMatcher for 'type sampler_comparison'
+constexpr TypeMatcher kSamplerComparisonMatcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+    if (!MatchSamplerComparison(state, ty)) {
+      return nullptr;
+    }
+    return BuildSamplerComparison(state, ty);
+  },
+/* string */ [](MatchState*) -> std::string {
+    return "sampler_comparison";
+  }
+};
+
+
+/// TypeMatcher for 'type texture_1d'
+constexpr TypeMatcher kTexture1DMatcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+  const Type* T = nullptr;
+    if (!MatchTexture1D(state, ty, T)) {
+      return nullptr;
+    }
+    T = state.Type(T);
+    if (T == nullptr) {
+      return nullptr;
+    }
+    return BuildTexture1D(state, ty, T);
+  },
+/* string */ [](MatchState* state) -> std::string {
+  const std::string T = state->TypeName();
+    return "texture_1d<" + T + ">";
+  }
+};
+
+
+/// TypeMatcher for 'type texture_2d'
+constexpr TypeMatcher kTexture2DMatcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+  const Type* T = nullptr;
+    if (!MatchTexture2D(state, ty, T)) {
+      return nullptr;
+    }
+    T = state.Type(T);
+    if (T == nullptr) {
+      return nullptr;
+    }
+    return BuildTexture2D(state, ty, T);
+  },
+/* string */ [](MatchState* state) -> std::string {
+  const std::string T = state->TypeName();
+    return "texture_2d<" + T + ">";
+  }
+};
+
+
+/// TypeMatcher for 'type texture_2d_array'
+constexpr TypeMatcher kTexture2DArrayMatcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+  const Type* T = nullptr;
+    if (!MatchTexture2DArray(state, ty, T)) {
+      return nullptr;
+    }
+    T = state.Type(T);
+    if (T == nullptr) {
+      return nullptr;
+    }
+    return BuildTexture2DArray(state, ty, T);
+  },
+/* string */ [](MatchState* state) -> std::string {
+  const std::string T = state->TypeName();
+    return "texture_2d_array<" + T + ">";
+  }
+};
+
+
+/// TypeMatcher for 'type texture_3d'
+constexpr TypeMatcher kTexture3DMatcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+  const Type* T = nullptr;
+    if (!MatchTexture3D(state, ty, T)) {
+      return nullptr;
+    }
+    T = state.Type(T);
+    if (T == nullptr) {
+      return nullptr;
+    }
+    return BuildTexture3D(state, ty, T);
+  },
+/* string */ [](MatchState* state) -> std::string {
+  const std::string T = state->TypeName();
+    return "texture_3d<" + T + ">";
+  }
+};
+
+
+/// TypeMatcher for 'type texture_cube'
+constexpr TypeMatcher kTextureCubeMatcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+  const Type* T = nullptr;
+    if (!MatchTextureCube(state, ty, T)) {
+      return nullptr;
+    }
+    T = state.Type(T);
+    if (T == nullptr) {
+      return nullptr;
+    }
+    return BuildTextureCube(state, ty, T);
+  },
+/* string */ [](MatchState* state) -> std::string {
+  const std::string T = state->TypeName();
+    return "texture_cube<" + T + ">";
+  }
+};
+
+
+/// TypeMatcher for 'type texture_cube_array'
+constexpr TypeMatcher kTextureCubeArrayMatcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+  const Type* T = nullptr;
+    if (!MatchTextureCubeArray(state, ty, T)) {
+      return nullptr;
+    }
+    T = state.Type(T);
+    if (T == nullptr) {
+      return nullptr;
+    }
+    return BuildTextureCubeArray(state, ty, T);
+  },
+/* string */ [](MatchState* state) -> std::string {
+  const std::string T = state->TypeName();
+    return "texture_cube_array<" + T + ">";
+  }
+};
+
+
+/// TypeMatcher for 'type texture_depth_2d'
+constexpr TypeMatcher kTextureDepth2DMatcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+    if (!MatchTextureDepth2D(state, ty)) {
+      return nullptr;
+    }
+    return BuildTextureDepth2D(state, ty);
+  },
+/* string */ [](MatchState*) -> std::string {
+    return "texture_depth_2d";
+  }
+};
+
+
+/// TypeMatcher for 'type texture_depth_2d_array'
+constexpr TypeMatcher kTextureDepth2DArrayMatcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+    if (!MatchTextureDepth2DArray(state, ty)) {
+      return nullptr;
+    }
+    return BuildTextureDepth2DArray(state, ty);
+  },
+/* string */ [](MatchState*) -> std::string {
+    return "texture_depth_2d_array";
+  }
+};
+
+
+/// TypeMatcher for 'type texture_depth_cube'
+constexpr TypeMatcher kTextureDepthCubeMatcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+    if (!MatchTextureDepthCube(state, ty)) {
+      return nullptr;
+    }
+    return BuildTextureDepthCube(state, ty);
+  },
+/* string */ [](MatchState*) -> std::string {
+    return "texture_depth_cube";
+  }
+};
+
+
+/// TypeMatcher for 'type texture_depth_cube_array'
+constexpr TypeMatcher kTextureDepthCubeArrayMatcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+    if (!MatchTextureDepthCubeArray(state, ty)) {
+      return nullptr;
+    }
+    return BuildTextureDepthCubeArray(state, ty);
+  },
+/* string */ [](MatchState*) -> std::string {
+    return "texture_depth_cube_array";
+  }
+};
+
+
 /// TypeMatcher for 'type ptr'
 constexpr TypeMatcher kPtrMatcher {
 /* match */ [](MatchState& state, const Type* ty) -> const Type* {
@@ -504,6 +708,26 @@
 };
 
 
+/// TypeMatcher for 'type sampled_image'
+constexpr TypeMatcher kSampledImageMatcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+  const Type* T = nullptr;
+    if (!MatchSampledImage(state, ty, T)) {
+      return nullptr;
+    }
+    T = state.Type(T);
+    if (T == nullptr) {
+      return nullptr;
+    }
+    return BuildSampledImage(state, ty, T);
+  },
+/* string */ [](MatchState* state) -> std::string {
+  const std::string T = state->TypeName();
+    return "sampled_image<" + T + ">";
+  }
+};
+
+
 /// TypeMatcher for 'match f32_f16'
 constexpr TypeMatcher kF32F16Matcher {
 /* match */ [](MatchState& state, const Type* ty) -> const Type* {
@@ -544,6 +768,29 @@
   }
 };
 
+/// TypeMatcher for 'match fiu32'
+constexpr TypeMatcher kFiu32Matcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+    if (MatchF32(state, ty)) {
+      return BuildF32(state, ty);
+    }
+    if (MatchI32(state, ty)) {
+      return BuildI32(state, ty);
+    }
+    if (MatchU32(state, ty)) {
+      return BuildU32(state, ty);
+    }
+    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();
+  }
+};
+
 /// TypeMatcher for 'match scalar'
 constexpr TypeMatcher kScalarMatcher {
 /* match */ [](MatchState& state, const Type* ty) -> const Type* {
@@ -573,6 +820,26 @@
   }
 };
 
+/// TypeMatcher for 'match samplers'
+constexpr TypeMatcher kSamplersMatcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+    if (MatchSampler(state, ty)) {
+      return BuildSampler(state, ty);
+    }
+    if (MatchSamplerComparison(state, ty)) {
+      return BuildSamplerComparison(state, ty);
+    }
+    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 << kSamplerMatcher.string(nullptr) << " or " << kSamplerComparisonMatcher.string(nullptr);
+    return ss.str();
+  }
+};
+
 /// EnumMatcher for 'match read_write'
 constexpr NumberMatcher kReadWriteMatcher {
 /* match */ [](MatchState&, Number number) -> Number {
@@ -641,11 +908,26 @@
   /* [19] */ kVecMatcher,
   /* [20] */ kMatMatcher,
   /* [21] */ kAtomicMatcher,
-  /* [22] */ kPtrMatcher,
-  /* [23] */ kStructWithRuntimeArrayMatcher,
-  /* [24] */ kF32F16Matcher,
-  /* [25] */ kIu32Matcher,
-  /* [26] */ kScalarMatcher,
+  /* [22] */ kSamplerMatcher,
+  /* [23] */ kSamplerComparisonMatcher,
+  /* [24] */ kTexture1DMatcher,
+  /* [25] */ kTexture2DMatcher,
+  /* [26] */ kTexture2DArrayMatcher,
+  /* [27] */ kTexture3DMatcher,
+  /* [28] */ kTextureCubeMatcher,
+  /* [29] */ kTextureCubeArrayMatcher,
+  /* [30] */ kTextureDepth2DMatcher,
+  /* [31] */ kTextureDepth2DArrayMatcher,
+  /* [32] */ kTextureDepthCubeMatcher,
+  /* [33] */ kTextureDepthCubeArrayMatcher,
+  /* [34] */ kPtrMatcher,
+  /* [35] */ kStructWithRuntimeArrayMatcher,
+  /* [36] */ kSampledImageMatcher,
+  /* [37] */ kF32F16Matcher,
+  /* [38] */ kIu32Matcher,
+  /* [39] */ kFiu32Matcher,
+  /* [40] */ kScalarMatcher,
+  /* [41] */ kSamplersMatcher,
 };
 
 /// The template numbers, and number matchers
@@ -659,19 +941,45 @@
 };
 
 constexpr TypeMatcherIndex kTypeMatcherIndices[] = {
-  /* [0] */ TypeMatcherIndex(22),
+  /* [0] */ TypeMatcherIndex(34),
   /* [1] */ TypeMatcherIndex(21),
   /* [2] */ TypeMatcherIndex(0),
-  /* [3] */ TypeMatcherIndex(22),
-  /* [4] */ TypeMatcherIndex(23),
-  /* [5] */ TypeMatcherIndex(19),
-  /* [6] */ TypeMatcherIndex(0),
-  /* [7] */ TypeMatcherIndex(20),
+  /* [3] */ TypeMatcherIndex(36),
+  /* [4] */ TypeMatcherIndex(24),
+  /* [5] */ TypeMatcherIndex(0),
+  /* [6] */ TypeMatcherIndex(36),
+  /* [7] */ TypeMatcherIndex(25),
   /* [8] */ TypeMatcherIndex(0),
-  /* [9] */ TypeMatcherIndex(19),
-  /* [10] */ TypeMatcherIndex(2),
-  /* [11] */ TypeMatcherIndex(6),
-  /* [12] */ TypeMatcherIndex(1),
+  /* [9] */ TypeMatcherIndex(36),
+  /* [10] */ TypeMatcherIndex(26),
+  /* [11] */ TypeMatcherIndex(0),
+  /* [12] */ TypeMatcherIndex(36),
+  /* [13] */ TypeMatcherIndex(27),
+  /* [14] */ TypeMatcherIndex(0),
+  /* [15] */ TypeMatcherIndex(36),
+  /* [16] */ TypeMatcherIndex(28),
+  /* [17] */ TypeMatcherIndex(0),
+  /* [18] */ TypeMatcherIndex(36),
+  /* [19] */ TypeMatcherIndex(29),
+  /* [20] */ TypeMatcherIndex(0),
+  /* [21] */ TypeMatcherIndex(34),
+  /* [22] */ TypeMatcherIndex(35),
+  /* [23] */ TypeMatcherIndex(19),
+  /* [24] */ TypeMatcherIndex(0),
+  /* [25] */ TypeMatcherIndex(20),
+  /* [26] */ TypeMatcherIndex(0),
+  /* [27] */ TypeMatcherIndex(36),
+  /* [28] */ TypeMatcherIndex(30),
+  /* [29] */ TypeMatcherIndex(36),
+  /* [30] */ TypeMatcherIndex(31),
+  /* [31] */ TypeMatcherIndex(36),
+  /* [32] */ TypeMatcherIndex(32),
+  /* [33] */ TypeMatcherIndex(36),
+  /* [34] */ TypeMatcherIndex(33),
+  /* [35] */ TypeMatcherIndex(19),
+  /* [36] */ TypeMatcherIndex(2),
+  /* [37] */ TypeMatcherIndex(6),
+  /* [38] */ TypeMatcherIndex(1),
 };
 
 static_assert(TypeMatcherIndex::CanIndex(kTypeMatcherIndices),
@@ -703,19 +1011,19 @@
   {
     /* [1] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(12),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(38),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [2] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(12),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(38),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [3] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(12),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(38),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
@@ -739,13 +1047,13 @@
   {
     /* [7] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(12),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(38),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [8] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(12),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(38),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
@@ -757,7 +1065,7 @@
   {
     /* [10] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(10),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(36),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
@@ -775,31 +1083,31 @@
   {
     /* [13] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(9),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(35),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(1),
   },
   {
     /* [14] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(5),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(23),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(1),
   },
   {
     /* [15] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(5),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(23),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(1),
   },
   {
     /* [16] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(7),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(7),
   },
   {
     /* [17] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(3),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(21),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(0),
   },
   {
@@ -811,19 +1119,19 @@
   {
     /* [19] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(7),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(5),
   },
   {
     /* [20] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(7),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(7),
   },
   {
     /* [21] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(7),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(8),
   },
   {
@@ -835,13 +1143,13 @@
   {
     /* [23] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(7),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(8),
   },
   {
     /* [24] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(5),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(23),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(1),
   },
   {
@@ -850,6 +1158,126 @@
     /* type_matcher_indices */ TypeMatcherIndicesIndex(2),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
+  {
+    /* [26] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(4),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [27] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(38),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [28] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(7),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [29] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(38),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [30] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(10),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [31] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(38),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [32] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(13),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [33] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(38),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [34] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(16),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [35] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(38),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [36] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(19),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [37] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(38),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [38] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(28),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [39] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(2),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [40] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(30),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [41] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(2),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [42] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(32),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [43] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(2),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [44] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(34),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [45] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(2),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
 };
 
 static_assert(ParameterIndex::CanIndex(kParameters),
@@ -859,7 +1287,7 @@
   {
     /* [0] */
     /* name */ "T",
-    /* matcher_index */ TypeMatcherIndex(25),
+    /* matcher_index */ TypeMatcherIndex(38),
   },
   {
     /* [1] */
@@ -868,18 +1296,28 @@
   },
   {
     /* [2] */
+    /* name */ "T",
+    /* matcher_index */ TypeMatcherIndex(39),
+  },
+  {
+    /* [3] */
+    /* name */ "S",
+    /* matcher_index */ TypeMatcherIndex(41),
+  },
+  {
+    /* [4] */
     /* name */ "I",
     /* matcher_index */ TypeMatcherIndex(6),
   },
   {
-    /* [3] */
+    /* [5] */
     /* name */ "T",
-    /* matcher_index */ TypeMatcherIndex(24),
+    /* matcher_index */ TypeMatcherIndex(37),
   },
   {
-    /* [4] */
+    /* [6] */
     /* name */ "T",
-    /* matcher_index */ TypeMatcherIndex(26),
+    /* matcher_index */ TypeMatcherIndex(40),
   },
 };
 
@@ -931,10 +1369,140 @@
   {
     /* [0] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 2,
+    /* num_template_types */ 2,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(2),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(26),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(3),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [1] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 2,
+    /* num_template_types */ 2,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(2),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(28),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(6),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [2] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 2,
+    /* num_template_types */ 2,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(2),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(30),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(9),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [3] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 2,
+    /* num_template_types */ 2,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(2),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(32),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(12),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [4] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 2,
+    /* num_template_types */ 2,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(2),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(34),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(15),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [5] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 2,
+    /* num_template_types */ 2,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(2),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(36),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(18),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [6] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 2,
+    /* num_template_types */ 1,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(3),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(38),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(27),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [7] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 2,
+    /* num_template_types */ 1,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(3),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(40),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(29),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [8] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 2,
+    /* num_template_types */ 1,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(3),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(42),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(31),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [9] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 2,
+    /* num_template_types */ 1,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(3),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(44),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(33),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [10] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* num_parameters */ 3,
     /* num_template_types */ 1,
     /* num_template_numbers */ 0,
-    /* template_types */ TemplateTypeIndex(4),
+    /* template_types */ TemplateTypeIndex(6),
     /* template_numbers */ TemplateNumberIndex(/* invalid */),
     /* parameters */ ParameterIndex(10),
     /* return_type_matcher_indices */ TypeMatcherIndicesIndex(2),
@@ -942,33 +1510,33 @@
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
-    /* [1] */
+    /* [11] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* num_parameters */ 3,
     /* num_template_types */ 1,
     /* num_template_numbers */ 1,
-    /* template_types */ TemplateTypeIndex(4),
+    /* template_types */ TemplateTypeIndex(6),
     /* template_numbers */ TemplateNumberIndex(3),
     /* parameters */ ParameterIndex(13),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(5),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(23),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(1),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
-    /* [2] */
+    /* [12] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* num_parameters */ 2,
     /* num_template_types */ 1,
     /* num_template_numbers */ 1,
-    /* template_types */ TemplateTypeIndex(2),
+    /* template_types */ TemplateTypeIndex(4),
     /* template_numbers */ TemplateNumberIndex(5),
     /* parameters */ ParameterIndex(17),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(11),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(37),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
-    /* [3] */
+    /* [13] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* num_parameters */ 4,
     /* num_template_types */ 2,
@@ -981,7 +1549,7 @@
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
-    /* [4] */
+    /* [14] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* num_parameters */ 6,
     /* num_template_types */ 2,
@@ -994,7 +1562,7 @@
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
-    /* [5] */
+    /* [15] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* num_parameters */ 3,
     /* num_template_types */ 2,
@@ -1007,7 +1575,7 @@
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
-    /* [6] */
+    /* [16] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* num_parameters */ 4,
     /* num_template_types */ 2,
@@ -1020,12 +1588,12 @@
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
-    /* [7] */
+    /* [17] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* num_parameters */ 2,
     /* num_template_types */ 1,
     /* num_template_numbers */ 1,
-    /* template_types */ TemplateTypeIndex(3),
+    /* template_types */ TemplateTypeIndex(5),
     /* template_numbers */ TemplateNumberIndex(3),
     /* parameters */ ParameterIndex(14),
     /* return_type_matcher_indices */ TypeMatcherIndicesIndex(2),
@@ -1033,67 +1601,67 @@
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
-    /* [8] */
+    /* [18] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* num_parameters */ 2,
     /* num_template_types */ 1,
     /* num_template_numbers */ 3,
-    /* template_types */ TemplateTypeIndex(3),
+    /* template_types */ TemplateTypeIndex(5),
     /* template_numbers */ TemplateNumberIndex(0),
     /* parameters */ ParameterIndex(19),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(7),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(3),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
-    /* [9] */
+    /* [19] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* num_parameters */ 2,
     /* num_template_types */ 1,
     /* num_template_numbers */ 2,
-    /* template_types */ TemplateTypeIndex(3),
+    /* template_types */ TemplateTypeIndex(5),
     /* template_numbers */ TemplateNumberIndex(3),
     /* parameters */ ParameterIndex(21),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(7),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(8),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
-    /* [10] */
+    /* [20] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* num_parameters */ 2,
     /* num_template_types */ 1,
     /* num_template_numbers */ 2,
-    /* template_types */ TemplateTypeIndex(3),
+    /* template_types */ TemplateTypeIndex(5),
     /* template_numbers */ TemplateNumberIndex(3),
     /* parameters */ ParameterIndex(23),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(5),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(23),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(3),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
-    /* [11] */
+    /* [21] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* num_parameters */ 2,
     /* num_template_types */ 1,
     /* num_template_numbers */ 2,
-    /* template_types */ TemplateTypeIndex(3),
+    /* template_types */ TemplateTypeIndex(5),
     /* template_numbers */ TemplateNumberIndex(3),
     /* parameters */ ParameterIndex(15),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(5),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(23),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(3),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
-    /* [12] */
+    /* [22] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* num_parameters */ 2,
     /* num_template_types */ 1,
     /* num_template_numbers */ 1,
-    /* template_types */ TemplateTypeIndex(3),
+    /* template_types */ TemplateTypeIndex(5),
     /* template_numbers */ TemplateNumberIndex(3),
     /* parameters */ ParameterIndex(24),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(5),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(23),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(1),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
@@ -1107,128 +1675,143 @@
     /* [0] */
     /* fn array_length<I : u32, A : access>(ptr<storage, struct_with_runtime_array, A>, I) -> u32 */
     /* num overloads */ 1,
-    /* overloads */ OverloadIndex(2),
+    /* overloads */ OverloadIndex(12),
   },
   {
     /* [1] */
     /* fn atomic_and<T : iu32, U : u32, S : workgroup_or_storage>(ptr<S, atomic<T>, read_write>, U, U, T) -> T */
     /* num overloads */ 1,
-    /* overloads */ OverloadIndex(3),
+    /* overloads */ OverloadIndex(13),
   },
   {
     /* [2] */
     /* fn atomic_compare_exchange<T : iu32, U : u32, S : workgroup_or_storage>(ptr<S, atomic<T>, read_write>, U, U, U, T, T) -> T */
     /* num overloads */ 1,
-    /* overloads */ OverloadIndex(4),
+    /* overloads */ OverloadIndex(14),
   },
   {
     /* [3] */
     /* fn atomic_exchange<T : iu32, U : u32, S : workgroup_or_storage>(ptr<S, atomic<T>, read_write>, U, U, T) -> T */
     /* num overloads */ 1,
-    /* overloads */ OverloadIndex(3),
+    /* overloads */ OverloadIndex(13),
   },
   {
     /* [4] */
     /* fn atomic_iadd<T : iu32, U : u32, S : workgroup_or_storage>(ptr<S, atomic<T>, read_write>, U, U, T) -> T */
     /* num overloads */ 1,
-    /* overloads */ OverloadIndex(3),
+    /* overloads */ OverloadIndex(13),
   },
   {
     /* [5] */
     /* fn atomic_isub<T : iu32, U : u32, S : workgroup_or_storage>(ptr<S, atomic<T>, read_write>, U, U, T) -> T */
     /* num overloads */ 1,
-    /* overloads */ OverloadIndex(3),
+    /* overloads */ OverloadIndex(13),
   },
   {
     /* [6] */
     /* fn atomic_load<T : iu32, U : u32, S : workgroup_or_storage>(ptr<S, atomic<T>, read_write>, U, U) -> T */
     /* num overloads */ 1,
-    /* overloads */ OverloadIndex(5),
+    /* overloads */ OverloadIndex(15),
   },
   {
     /* [7] */
     /* fn atomic_or<T : iu32, U : u32, S : workgroup_or_storage>(ptr<S, atomic<T>, read_write>, U, U, T) -> T */
     /* num overloads */ 1,
-    /* overloads */ OverloadIndex(3),
+    /* overloads */ OverloadIndex(13),
   },
   {
     /* [8] */
     /* fn atomic_smax<T : iu32, U : u32, S : workgroup_or_storage>(ptr<S, atomic<T>, read_write>, U, U, T) -> T */
     /* num overloads */ 1,
-    /* overloads */ OverloadIndex(3),
+    /* overloads */ OverloadIndex(13),
   },
   {
     /* [9] */
     /* fn atomic_smin<T : iu32, U : u32, S : workgroup_or_storage>(ptr<S, atomic<T>, read_write>, U, U, T) -> T */
     /* num overloads */ 1,
-    /* overloads */ OverloadIndex(3),
+    /* overloads */ OverloadIndex(13),
   },
   {
     /* [10] */
     /* fn atomic_store<T : iu32, U : u32, S : workgroup_or_storage>(ptr<S, atomic<T>, read_write>, U, U, T) */
     /* num overloads */ 1,
-    /* overloads */ OverloadIndex(6),
+    /* overloads */ OverloadIndex(16),
   },
   {
     /* [11] */
     /* fn atomic_umax<T : iu32, U : u32, S : workgroup_or_storage>(ptr<S, atomic<T>, read_write>, U, U, T) -> T */
     /* num overloads */ 1,
-    /* overloads */ OverloadIndex(3),
+    /* overloads */ OverloadIndex(13),
   },
   {
     /* [12] */
     /* fn atomic_umin<T : iu32, U : u32, S : workgroup_or_storage>(ptr<S, atomic<T>, read_write>, U, U, T) -> T */
     /* num overloads */ 1,
-    /* overloads */ OverloadIndex(3),
+    /* overloads */ OverloadIndex(13),
   },
   {
     /* [13] */
     /* fn atomic_xor<T : iu32, U : u32, S : workgroup_or_storage>(ptr<S, atomic<T>, read_write>, U, U, T) -> T */
     /* num overloads */ 1,
-    /* overloads */ OverloadIndex(3),
+    /* overloads */ OverloadIndex(13),
   },
   {
     /* [14] */
     /* fn dot<N : num, T : f32_f16>(vec<N, T>, vec<N, T>) -> T */
     /* num overloads */ 1,
-    /* overloads */ OverloadIndex(7),
+    /* overloads */ OverloadIndex(17),
   },
   {
     /* [15] */
     /* fn matrix_times_matrix<T : f32_f16, K : num, C : num, R : num>(mat<K, R, T>, mat<C, K, T>) -> mat<C, R, T> */
     /* num overloads */ 1,
-    /* overloads */ OverloadIndex(8),
+    /* overloads */ OverloadIndex(18),
   },
   {
     /* [16] */
     /* fn matrix_times_scalar<T : f32_f16, N : num, M : num>(mat<N, M, T>, T) -> mat<N, M, T> */
     /* num overloads */ 1,
-    /* overloads */ OverloadIndex(9),
+    /* overloads */ OverloadIndex(19),
   },
   {
     /* [17] */
     /* fn matrix_times_vector<T : f32_f16, N : num, M : num>(mat<N, M, T>, vec<N, T>) -> vec<M, T> */
     /* num overloads */ 1,
-    /* overloads */ OverloadIndex(10),
+    /* overloads */ OverloadIndex(20),
   },
   {
     /* [18] */
-    /* fn vector_times_matrix<T : f32_f16, N : num, M : num>(vec<N, T>, mat<M, N, T>) -> vec<M, T> */
-    /* num overloads */ 1,
-    /* overloads */ OverloadIndex(11),
+    /* fn sampled_image<T : fiu32, S : samplers>(texture_1d<T>, S) -> sampled_image<texture_1d<T>> */
+    /* fn sampled_image<T : fiu32, S : samplers>(texture_2d<T>, S) -> sampled_image<texture_2d<T>> */
+    /* fn sampled_image<T : fiu32, S : samplers>(texture_2d_array<T>, S) -> sampled_image<texture_2d_array<T>> */
+    /* fn sampled_image<T : fiu32, S : samplers>(texture_3d<T>, S) -> sampled_image<texture_3d<T>> */
+    /* fn sampled_image<T : fiu32, S : samplers>(texture_cube<T>, S) -> sampled_image<texture_cube<T>> */
+    /* fn sampled_image<T : fiu32, S : samplers>(texture_cube_array<T>, S) -> sampled_image<texture_cube_array<T>> */
+    /* fn sampled_image<S : samplers>(texture_depth_2d, S) -> sampled_image<texture_depth_2d> */
+    /* fn sampled_image<S : samplers>(texture_depth_2d_array, S) -> sampled_image<texture_depth_2d_array> */
+    /* fn sampled_image<S : samplers>(texture_depth_cube, S) -> sampled_image<texture_depth_cube> */
+    /* fn sampled_image<S : samplers>(texture_depth_cube_array, S) -> sampled_image<texture_depth_cube_array> */
+    /* num overloads */ 10,
+    /* overloads */ OverloadIndex(0),
   },
   {
     /* [19] */
     /* fn select<T : scalar>(bool, T, T) -> T */
     /* fn select<N : num, T : scalar>(vec<N, bool>, vec<N, T>, vec<N, T>) -> vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ OverloadIndex(0),
+    /* overloads */ OverloadIndex(10),
   },
   {
     /* [20] */
+    /* fn vector_times_matrix<T : f32_f16, N : num, M : num>(vec<N, T>, mat<M, N, T>) -> vec<M, T> */
+    /* num overloads */ 1,
+    /* overloads */ OverloadIndex(21),
+  },
+  {
+    /* [21] */
     /* fn vector_times_scalar<T : f32_f16, N : num>(vec<N, T>, T) -> vec<N, T> */
     /* num overloads */ 1,
-    /* overloads */ OverloadIndex(12),
+    /* overloads */ OverloadIndex(22),
   },
 };
 
diff --git a/src/tint/lang/spirv/intrinsic/data/type_matchers.h b/src/tint/lang/spirv/intrinsic/data/type_matchers.h
index 63c99cd..0f94b78 100644
--- a/src/tint/lang/spirv/intrinsic/data/type_matchers.h
+++ b/src/tint/lang/spirv/intrinsic/data/type_matchers.h
@@ -18,6 +18,7 @@
 #include "src/tint/lang/core/intrinsic/table.h"
 #include "src/tint/lang/core/type/array.h"
 #include "src/tint/lang/core/type/struct.h"
+#include "src/tint/lang/spirv/type/sampled_image.h"
 
 namespace tint::spirv::intrinsic::data {
 
@@ -40,6 +41,26 @@
     return ty;
 }
 
+inline bool MatchSampledImage(core::intrinsic::MatchState&,
+                              const core::type::Type* ty,
+                              const core::type::Type*& T) {
+    if (ty->Is<core::intrinsic::Any>()) {
+        T = ty;
+        return true;
+    }
+    if (auto* v = ty->As<core::type::SampledTexture>()) {
+        T = v->type();
+        return true;
+    }
+    return false;
+}
+
+inline const core::type::Type* BuildSampledImage(core::intrinsic::MatchState& state,
+                                                 const core::type::Type*,
+                                                 const core::type::Type* T) {
+    return state.types.Get<type::SampledImage>(T);
+}
+
 }  // namespace tint::spirv::intrinsic::data
 
 #endif  // SRC_TINT_LANG_SPIRV_INTRINSIC_DATA_TYPE_MATCHERS_H_
diff --git a/src/tint/lang/spirv/ir/function.cc b/src/tint/lang/spirv/ir/function.cc
index 1c30eed..a0e4d8f 100644
--- a/src/tint/lang/spirv/ir/function.cc
+++ b/src/tint/lang/spirv/ir/function.cc
@@ -65,10 +65,12 @@
             return "spirv.matrix_times_scalar";
         case Function::kMatrixTimesVector:
             return "spirv.matrix_times_vector";
-        case Function::kVectorTimesMatrix:
-            return "spirv.vector_times_matrix";
+        case Function::kSampledImage:
+            return "spirv.sampled_image";
         case Function::kSelect:
             return "spirv.select";
+        case Function::kVectorTimesMatrix:
+            return "spirv.vector_times_matrix";
         case Function::kVectorTimesScalar:
             return "spirv.vector_times_scalar";
     }
diff --git a/src/tint/lang/spirv/ir/function.h b/src/tint/lang/spirv/ir/function.h
index 1ac09f8..50cd120 100644
--- a/src/tint/lang/spirv/ir/function.h
+++ b/src/tint/lang/spirv/ir/function.h
@@ -52,8 +52,9 @@
     kMatrixTimesMatrix,
     kMatrixTimesScalar,
     kMatrixTimesVector,
-    kVectorTimesMatrix,
+    kSampledImage,
     kSelect,
+    kVectorTimesMatrix,
     kVectorTimesScalar,
     kNone,
 };
diff --git a/src/tint/lang/spirv/ir/intrinsic.cc b/src/tint/lang/spirv/ir/intrinsic.cc
index ed401f7..86623ba 100644
--- a/src/tint/lang/spirv/ir/intrinsic.cc
+++ b/src/tint/lang/spirv/ir/intrinsic.cc
@@ -62,9 +62,6 @@
     if (str == "image_write") {
         return Intrinsic::kImageWrite;
     }
-    if (str == "sampled_image") {
-        return Intrinsic::kSampledImage;
-    }
     return Intrinsic::kUndefined;
 }
 
@@ -94,8 +91,6 @@
             return "image_sample_implicit_lod";
         case Intrinsic::kImageWrite:
             return "image_write";
-        case Intrinsic::kSampledImage:
-            return "sampled_image";
     }
     return "<unknown>";
 }
diff --git a/src/tint/lang/spirv/ir/intrinsic.h b/src/tint/lang/spirv/ir/intrinsic.h
index 0f49bf9..a45688c 100644
--- a/src/tint/lang/spirv/ir/intrinsic.h
+++ b/src/tint/lang/spirv/ir/intrinsic.h
@@ -45,7 +45,6 @@
     kImageSampleExplicitLod,
     kImageSampleImplicitLod,
     kImageWrite,
-    kSampledImage,
 };
 
 /// @param value the enum value
@@ -77,7 +76,6 @@
     "image_sample_explicit_lod",
     "image_sample_implicit_lod",
     "image_write",
-    "sampled_image",
 };
 
 }  // namespace tint::spirv::ir
diff --git a/src/tint/lang/spirv/spirv.def b/src/tint/lang/spirv/spirv.def
index 3949ffe..9557909 100644
--- a/src/tint/lang/spirv/spirv.def
+++ b/src/tint/lang/spirv/spirv.def
@@ -39,9 +39,22 @@
 @display("vec{N}<{T}>")     type vec<N: num, T>
 @display("mat{N}x{M}<{T}>") type mat<N: num, M: num, T>
 type atomic<T>
+type sampler
+type sampler_comparison
+type texture_1d<T>
+type texture_2d<T>
+type texture_2d_array<T>
+type texture_3d<T>
+type texture_cube<T>
+type texture_cube_array<T>
+type texture_depth_2d
+type texture_depth_2d_array
+type texture_depth_cube
+type texture_depth_cube_array
 type ptr<S: address_space, T, A: access>
 
 type struct_with_runtime_array
+type sampled_image<T>
 
 enum address_space {
   function
@@ -61,6 +74,7 @@
 
 match f32_f16: f32 | f16
 match iu32: i32 | u32
+match fiu32: f32 | i32 | u32
 match scalar: f32 | f16 | i32 | u32 | bool
 
 match read_write: access.read_write
@@ -70,6 +84,10 @@
   : address_space.workgroup
   | address_space.storage
 
+match samplers
+  : sampler
+  | sampler_comparison
+
 ////////////////////////////////////////////////////////////////////////////////
 // Enumerators                                                                //
 ////////////////////////////////////////////////////////////////////////////////
@@ -86,13 +104,13 @@
   image_sample_dref_implicit_lod
   image_sample_dref_explicit_lod
   image_write
-  sampled_image
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // Builtin Functions                                                          //
 ////////////////////////////////////////////////////////////////////////////////
 fn array_length<I: u32, A: access>(ptr<storage, struct_with_runtime_array, A>, I) -> u32
+
 @stage("fragment", "compute") fn atomic_and<T: iu32, U: u32, S: workgroup_or_storage>(ptr<S, atomic<T>, read_write>, U, U, T) -> T
 @stage("fragment", "compute") fn atomic_compare_exchange<T: iu32, U: u32, S: workgroup_or_storage>(ptr<S, atomic<T>, read_write>, U, U, U, T, T) -> T
 @stage("fragment", "compute") fn atomic_exchange<T: iu32, U: u32, S: workgroup_or_storage>(ptr<S, atomic<T>, read_write>, U, U, T) -> T
@@ -106,12 +124,27 @@
 @stage("fragment", "compute") fn atomic_umax<T: iu32, U: u32, S: workgroup_or_storage>(ptr<S, atomic<T>, read_write>, U, U, T) -> T
 @stage("fragment", "compute") fn atomic_umin<T: iu32, U: u32, S: workgroup_or_storage>(ptr<S, atomic<T>, read_write>, U, U, T) -> T
 @stage("fragment", "compute") fn atomic_xor<T: iu32, U: u32, S: workgroup_or_storage>(ptr<S, atomic<T>, read_write>, U, U, T) -> T
+
 fn dot<N: num, T: f32_f16>(vec<N, T>, vec<N, T>) -> T
+
 fn matrix_times_matrix<T: f32_f16, K: num, C: num, R: num>(mat<K, R, T>, mat<C, K, T>) -> mat<C, R, T>
 fn matrix_times_scalar<T: f32_f16, N: num, M: num>(mat<N, M, T>, T) -> mat<N, M, T>
 fn matrix_times_vector<T: f32_f16, N: num, M: num>(mat<N, M, T>, vec<N, T>) -> vec<M, T>
-fn vector_times_matrix<T: f32_f16, N: num, M: num>(vec<N, T>, mat<M, N, T>) -> vec<M, T>
+
+fn sampled_image<T: fiu32, S: samplers>(texture_1d<T>, S) -> sampled_image<texture_1d<T> >
+fn sampled_image<T: fiu32, S: samplers>(texture_2d<T>, S) -> sampled_image<texture_2d<T> >
+fn sampled_image<T: fiu32, S: samplers>(texture_2d_array<T>, S) -> sampled_image<texture_2d_array<T> >
+fn sampled_image<T: fiu32, S: samplers>(texture_3d<T>, S) -> sampled_image<texture_3d<T> >
+fn sampled_image<T: fiu32, S: samplers>(texture_cube<T>, S) -> sampled_image<texture_cube<T> >
+fn sampled_image<T: fiu32, S: samplers>(texture_cube_array<T>, S) -> sampled_image<texture_cube_array<T> >
+fn sampled_image<S: samplers>(texture_depth_2d, S) -> sampled_image<texture_depth_2d>
+fn sampled_image<S: samplers>(texture_depth_2d_array, S) -> sampled_image<texture_depth_2d_array>
+fn sampled_image<S: samplers>(texture_depth_cube, S) -> sampled_image<texture_depth_cube>
+fn sampled_image<S: samplers>(texture_depth_cube_array, S) -> sampled_image<texture_depth_cube_array>
+
 fn select<T: scalar>(bool, T, T) -> T
 fn select<N: num, T: scalar>(vec<N, bool>, vec<N, T>, vec<N, T>) -> vec<N, T>
+
+fn vector_times_matrix<T: f32_f16, N: num, M: num>(vec<N, T>, mat<M, N, T>) -> vec<M, T>
 fn vector_times_scalar<T: f32_f16, N: num>(vec<N, T>, T) -> vec<N, T>
 
diff --git a/src/tint/lang/spirv/type/BUILD.bazel b/src/tint/lang/spirv/type/BUILD.bazel
new file mode 100644
index 0000000..4337199
--- /dev/null
+++ b/src/tint/lang/spirv/type/BUILD.bazel
@@ -0,0 +1,54 @@
+# 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.
+
+################################################################################
+# File generated by 'tools/src/cmd/gen' using the template:
+#   tools/src/cmd/gen/build/BUILD.bazel.tmpl
+#
+# To regenerate run: './tools/run gen'
+#
+#                       Do not modify this file directly
+################################################################################
+
+load("//src/tint:flags.bzl", "COPTS")
+load("@bazel_skylib//lib:selects.bzl", "selects")
+cc_library(
+  name = "type",
+  srcs = [
+    "sampled_image.cc",
+  ],
+  hdrs = [
+    "sampled_image.h",
+  ],
+  deps = [
+    "//src/tint/lang/core",
+    "//src/tint/lang/core/constant",
+    "//src/tint/lang/core/ir",
+    "//src/tint/lang/core/type",
+    "//src/tint/utils/containers",
+    "//src/tint/utils/ice",
+    "//src/tint/utils/id",
+    "//src/tint/utils/macros",
+    "//src/tint/utils/math",
+    "//src/tint/utils/memory",
+    "//src/tint/utils/result",
+    "//src/tint/utils/rtti",
+    "//src/tint/utils/symbol",
+    "//src/tint/utils/text",
+    "//src/tint/utils/traits",
+  ],
+  copts = COPTS,
+  visibility = ["//visibility:public"],
+)
+
diff --git a/src/tint/lang/spirv/type/BUILD.cmake b/src/tint/lang/spirv/type/BUILD.cmake
new file mode 100644
index 0000000..1e2546a
--- /dev/null
+++ b/src/tint/lang/spirv/type/BUILD.cmake
@@ -0,0 +1,49 @@
+# 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.
+
+################################################################################
+# File generated by 'tools/src/cmd/gen' using the template:
+#   tools/src/cmd/gen/build/BUILD.cmake.tmpl
+#
+# To regenerate run: './tools/run gen'
+#
+#                       Do not modify this file directly
+################################################################################
+
+################################################################################
+# Target:    tint_lang_spirv_type
+# Kind:      lib
+################################################################################
+tint_add_target(tint_lang_spirv_type lib
+  lang/spirv/type/sampled_image.cc
+  lang/spirv/type/sampled_image.h
+)
+
+tint_target_add_dependencies(tint_lang_spirv_type lib
+  tint_lang_core
+  tint_lang_core_constant
+  tint_lang_core_ir
+  tint_lang_core_type
+  tint_utils_containers
+  tint_utils_ice
+  tint_utils_id
+  tint_utils_macros
+  tint_utils_math
+  tint_utils_memory
+  tint_utils_result
+  tint_utils_rtti
+  tint_utils_symbol
+  tint_utils_text
+  tint_utils_traits
+)
diff --git a/src/tint/lang/spirv/type/BUILD.gn b/src/tint/lang/spirv/type/BUILD.gn
new file mode 100644
index 0000000..41c6378
--- /dev/null
+++ b/src/tint/lang/spirv/type/BUILD.gn
@@ -0,0 +1,50 @@
+# 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.
+
+################################################################################
+# File generated by 'tools/src/cmd/gen' using the template:
+#   tools/src/cmd/gen/build/BUILD.gn.tmpl
+#
+# To regenerate run: './tools/run gen'
+#
+#                       Do not modify this file directly
+################################################################################
+
+import("../../../../../scripts/tint_overrides_with_defaults.gni")
+
+import("${tint_src_dir}/tint.gni")
+
+libtint_source_set("type") {
+  sources = [
+    "sampled_image.cc",
+    "sampled_image.h",
+  ]
+  deps = [
+    "${tint_src_dir}/lang/core",
+    "${tint_src_dir}/lang/core/constant",
+    "${tint_src_dir}/lang/core/ir",
+    "${tint_src_dir}/lang/core/type",
+    "${tint_src_dir}/utils/containers",
+    "${tint_src_dir}/utils/ice",
+    "${tint_src_dir}/utils/id",
+    "${tint_src_dir}/utils/macros",
+    "${tint_src_dir}/utils/math",
+    "${tint_src_dir}/utils/memory",
+    "${tint_src_dir}/utils/result",
+    "${tint_src_dir}/utils/rtti",
+    "${tint_src_dir}/utils/symbol",
+    "${tint_src_dir}/utils/text",
+    "${tint_src_dir}/utils/traits",
+  ]
+}
diff --git a/src/tint/lang/spirv/type/sampled_image.cc b/src/tint/lang/spirv/type/sampled_image.cc
new file mode 100644
index 0000000..883849d
--- /dev/null
+++ b/src/tint/lang/spirv/type/sampled_image.cc
@@ -0,0 +1,33 @@
+// 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.
+
+#include "src/tint/lang/spirv/type/sampled_image.h"
+
+#include "src/tint/lang/core/type/manager.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::spirv::type::SampledImage);
+
+namespace tint::spirv::type {
+
+SampledImage::SampledImage(const core::type::Type* image)
+    : Base(static_cast<size_t>(Hash(tint::TypeInfo::Of<SampledImage>().full_hashcode, image)),
+           core::type::Flags{}),
+      image_(image) {}
+
+SampledImage* SampledImage::Clone(core::type::CloneContext& ctx) const {
+    auto* image = image_->Clone(ctx);
+    return ctx.dst.mgr->Get<SampledImage>(image);
+}
+
+}  // namespace tint::spirv::type
diff --git a/src/tint/lang/spirv/type/sampled_image.h b/src/tint/lang/spirv/type/sampled_image.h
new file mode 100644
index 0000000..dbfcb22
--- /dev/null
+++ b/src/tint/lang/spirv/type/sampled_image.h
@@ -0,0 +1,53 @@
+// 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_SPIRV_TYPE_SAMPLED_IMAGE_H_
+#define SRC_TINT_LANG_SPIRV_TYPE_SAMPLED_IMAGE_H_
+
+#include <string>
+
+#include "src/tint/lang/core/ir/constant.h"
+#include "src/tint/lang/core/type/type.h"
+#include "src/tint/utils/result/result.h"
+
+namespace tint::spirv::type {
+
+/// SampledImage represents an OpTypeSampledImage in SPIR-V.
+class SampledImage final : public Castable<SampledImage, core::type::Type> {
+  public:
+    /// Constructor
+    /// @param image the image type
+    explicit SampledImage(const core::type::Type* image);
+
+    /// @param other the other node to compare against
+    /// @returns true if the this type is equal to @p other
+    bool Equals(const UniqueNode& other) const override { return &other.TypeInfo() == &TypeInfo(); }
+
+    /// @returns the friendly name for this type
+    std::string FriendlyName() const override { return "spirv.sampled_image"; }
+
+    /// @param ctx the clone context
+    /// @returns a clone of this type
+    SampledImage* Clone(core::type::CloneContext& ctx) const override;
+
+    /// @returns the image type
+    const core::type::Type* Image() const { return image_; }
+
+  private:
+    const core::type::Type* image_;
+};
+
+}  // namespace tint::spirv::type
+
+#endif  // SRC_TINT_LANG_SPIRV_TYPE_SAMPLED_IMAGE_H_
diff --git a/src/tint/lang/spirv/writer/printer/BUILD.bazel b/src/tint/lang/spirv/writer/printer/BUILD.bazel
index 338ee9f..27a6c34 100644
--- a/src/tint/lang/spirv/writer/printer/BUILD.bazel
+++ b/src/tint/lang/spirv/writer/printer/BUILD.bazel
@@ -42,6 +42,7 @@
     "//src/tint/lang/core/type",
     "//src/tint/lang/spirv/intrinsic/data",
     "//src/tint/lang/spirv/ir",
+    "//src/tint/lang/spirv/type",
     "//src/tint/lang/wgsl/ast",
     "//src/tint/lang/wgsl/program",
     "//src/tint/lang/wgsl/sem",
diff --git a/src/tint/lang/spirv/writer/printer/BUILD.cmake b/src/tint/lang/spirv/writer/printer/BUILD.cmake
index cecda96..18d6d86 100644
--- a/src/tint/lang/spirv/writer/printer/BUILD.cmake
+++ b/src/tint/lang/spirv/writer/printer/BUILD.cmake
@@ -43,6 +43,7 @@
   tint_lang_core_type
   tint_lang_spirv_intrinsic_data
   tint_lang_spirv_ir
+  tint_lang_spirv_type
   tint_lang_wgsl_ast
   tint_lang_wgsl_program
   tint_lang_wgsl_sem
diff --git a/src/tint/lang/spirv/writer/printer/BUILD.gn b/src/tint/lang/spirv/writer/printer/BUILD.gn
index 9cc2ff0..07c5fcf 100644
--- a/src/tint/lang/spirv/writer/printer/BUILD.gn
+++ b/src/tint/lang/spirv/writer/printer/BUILD.gn
@@ -41,6 +41,7 @@
       "${tint_src_dir}/lang/core/type",
       "${tint_src_dir}/lang/spirv/intrinsic/data",
       "${tint_src_dir}/lang/spirv/ir",
+      "${tint_src_dir}/lang/spirv/type",
       "${tint_src_dir}/lang/wgsl/ast",
       "${tint_src_dir}/lang/wgsl/program",
       "${tint_src_dir}/lang/wgsl/sem",
diff --git a/src/tint/lang/spirv/writer/printer/printer.cc b/src/tint/lang/spirv/writer/printer/printer.cc
index d3a4b6e..c5d07c7 100644
--- a/src/tint/lang/spirv/writer/printer/printer.cc
+++ b/src/tint/lang/spirv/writer/printer/printer.cc
@@ -75,6 +75,7 @@
 #include "src/tint/lang/core/type/vector.h"
 #include "src/tint/lang/core/type/void.h"
 #include "src/tint/lang/spirv/ir/intrinsic_call.h"
+#include "src/tint/lang/spirv/type/sampled_image.h"
 #include "src/tint/lang/spirv/writer/ast_printer/ast_printer.h"
 #include "src/tint/lang/spirv/writer/common/module.h"
 #include "src/tint/lang/spirv/writer/raise/builtin_polyfill.h"
@@ -138,10 +139,10 @@
         },
 
         // Dedup a SampledImage if its underlying image will be deduped.
-        [&](const raise::SampledImage* si) -> const core::type::Type* {
+        [&](const type::SampledImage* si) -> const core::type::Type* {
             auto* img = DedupType(si->Image(), types);
             if (img != si->Image()) {
-                return types.Get<raise::SampledImage>(img);
+                return types.Get<type::SampledImage>(img);
             }
             return si;
         },
@@ -375,7 +376,7 @@
             [&](const core::type::Struct* str) { EmitStructType(id, str); },
             [&](const core::type::Texture* tex) { EmitTextureType(id, tex); },
             [&](const core::type::Sampler*) { module_.PushType(spv::Op::OpTypeSampler, {id}); },
-            [&](const raise::SampledImage* s) {
+            [&](const type::SampledImage* s) {
                 module_.PushType(spv::Op::OpTypeSampledImage, {id, Type(s->Image())});
             },
             [&](Default) { TINT_ICE() << "unhandled type: " << ty->FriendlyName(); });
@@ -1089,6 +1090,9 @@
         case spirv::ir::Function::kMatrixTimesVector:
             op = spv::Op::OpMatrixTimesVector;
             break;
+        case spirv::ir::Function::kSampledImage:
+            op = spv::Op::OpSampledImage;
+            break;
         case spirv::ir::Function::kSelect:
             op = spv::Op::OpSelect;
             break;
@@ -1597,9 +1601,6 @@
         case spirv::ir::Intrinsic::kImageWrite:
             op = spv::Op::OpImageWrite;
             break;
-        case spirv::ir::Intrinsic::kSampledImage:
-            op = spv::Op::OpSampledImage;
-            break;
         case spirv::ir::Intrinsic::kUndefined:
             TINT_ICE() << "undefined spirv intrinsic";
             return;
diff --git a/src/tint/lang/spirv/writer/raise/BUILD.bazel b/src/tint/lang/spirv/writer/raise/BUILD.bazel
index 8e185e7..2f3e484 100644
--- a/src/tint/lang/spirv/writer/raise/BUILD.bazel
+++ b/src/tint/lang/spirv/writer/raise/BUILD.bazel
@@ -55,6 +55,7 @@
     "//src/tint/lang/core/type",
     "//src/tint/lang/spirv/intrinsic/data",
     "//src/tint/lang/spirv/ir",
+    "//src/tint/lang/spirv/type",
     "//src/tint/utils/containers",
     "//src/tint/utils/diagnostic",
     "//src/tint/utils/ice",
diff --git a/src/tint/lang/spirv/writer/raise/BUILD.cmake b/src/tint/lang/spirv/writer/raise/BUILD.cmake
index 65a438c..5462889 100644
--- a/src/tint/lang/spirv/writer/raise/BUILD.cmake
+++ b/src/tint/lang/spirv/writer/raise/BUILD.cmake
@@ -56,6 +56,7 @@
   tint_lang_core_type
   tint_lang_spirv_intrinsic_data
   tint_lang_spirv_ir
+  tint_lang_spirv_type
   tint_utils_containers
   tint_utils_diagnostic
   tint_utils_ice
diff --git a/src/tint/lang/spirv/writer/raise/BUILD.gn b/src/tint/lang/spirv/writer/raise/BUILD.gn
index de5a1d6..b62e46f 100644
--- a/src/tint/lang/spirv/writer/raise/BUILD.gn
+++ b/src/tint/lang/spirv/writer/raise/BUILD.gn
@@ -58,6 +58,7 @@
       "${tint_src_dir}/lang/core/type",
       "${tint_src_dir}/lang/spirv/intrinsic/data",
       "${tint_src_dir}/lang/spirv/ir",
+      "${tint_src_dir}/lang/spirv/type",
       "${tint_src_dir}/utils/containers",
       "${tint_src_dir}/utils/diagnostic",
       "${tint_src_dir}/utils/ice",
diff --git a/src/tint/lang/spirv/writer/raise/builtin_polyfill.cc b/src/tint/lang/spirv/writer/raise/builtin_polyfill.cc
index fea3909..d5fd5bb 100644
--- a/src/tint/lang/spirv/writer/raise/builtin_polyfill.cc
+++ b/src/tint/lang/spirv/writer/raise/builtin_polyfill.cc
@@ -30,10 +30,10 @@
 #include "src/tint/lang/core/type/texture.h"
 #include "src/tint/lang/spirv/ir/builtin_call.h"
 #include "src/tint/lang/spirv/ir/intrinsic_call.h"
+#include "src/tint/lang/spirv/type/sampled_image.h"
 #include "src/tint/utils/ice/ice.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::spirv::writer::raise::LiteralOperand);
-TINT_INSTANTIATE_TYPEINFO(tint::spirv::writer::raise::SampledImage);
 
 using namespace tint::core::number_suffixes;  // NOLINT
 using namespace tint::core::fluent_types;     // NOLINT
@@ -479,9 +479,9 @@
         auto* texture_ty = texture->Type()->As<core::type::Texture>();
 
         // Use OpSampledImage to create an OpTypeSampledImage object.
-        auto* sampled_image = b.Call<spirv::ir::IntrinsicCall>(ty.Get<SampledImage>(texture_ty),
-                                                               spirv::ir::Intrinsic::kSampledImage,
-                                                               Vector{texture, sampler});
+        auto* sampled_image = b.Call<spirv::ir::BuiltinCall>(ty.Get<type::SampledImage>(texture_ty),
+                                                             spirv::ir::Function::kSampledImage,
+                                                             Vector{texture, sampler});
         sampled_image->InsertBefore(builtin);
 
         // Append the array index to the coordinates if provided.
@@ -587,9 +587,9 @@
         auto* texture_ty = texture->Type()->As<core::type::Texture>();
 
         // Use OpSampledImage to create an OpTypeSampledImage object.
-        auto* sampled_image = b.Call<spirv::ir::IntrinsicCall>(ty.Get<SampledImage>(texture_ty),
-                                                               spirv::ir::Intrinsic::kSampledImage,
-                                                               Vector{texture, sampler});
+        auto* sampled_image = b.Call<spirv::ir::BuiltinCall>(ty.Get<type::SampledImage>(texture_ty),
+                                                             spirv::ir::Function::kSampledImage,
+                                                             Vector{texture, sampler});
         sampled_image->InsertBefore(builtin);
 
         // Append the array index to the coordinates if provided.
@@ -868,14 +868,4 @@
 
 LiteralOperand::~LiteralOperand() = default;
 
-SampledImage::SampledImage(const core::type::Type* image)
-    : Base(static_cast<size_t>(Hash(tint::TypeInfo::Of<SampledImage>().full_hashcode, image)),
-           core::type::Flags{}),
-      image_(image) {}
-
-SampledImage* SampledImage::Clone(core::type::CloneContext& ctx) const {
-    auto* image = image_->Clone(ctx);
-    return ctx.dst.mgr->Get<SampledImage>(image);
-}
-
 }  // namespace tint::spirv::writer::raise
diff --git a/src/tint/lang/spirv/writer/raise/builtin_polyfill.h b/src/tint/lang/spirv/writer/raise/builtin_polyfill.h
index a99adb9..5b16292 100644
--- a/src/tint/lang/spirv/writer/raise/builtin_polyfill.h
+++ b/src/tint/lang/spirv/writer/raise/builtin_polyfill.h
@@ -47,32 +47,6 @@
     ~LiteralOperand() override;
 };
 
-/// SampledImage represents an OpTypeSampledImage in SPIR-V.
-/// TODO(jrprice): Move this to lang/spirv.
-class SampledImage final : public Castable<SampledImage, core::type::Type> {
-  public:
-    /// Constructor
-    /// @param image the image type
-    explicit SampledImage(const core::type::Type* image);
-
-    /// @param other the other node to compare against
-    /// @returns true if the this type is equal to @p other
-    bool Equals(const UniqueNode& other) const override { return &other.TypeInfo() == &TypeInfo(); }
-
-    /// @returns the friendly name for this type
-    std::string FriendlyName() const override { return "spirv.sampled_image"; }
-
-    /// @param ctx the clone context
-    /// @returns a clone of this type
-    SampledImage* Clone(core::type::CloneContext& ctx) const override;
-
-    /// @returns the image type
-    const core::type::Type* Image() const { return image_; }
-
-  private:
-    const core::type::Type* image_;
-};
-
 }  // namespace tint::spirv::writer::raise
 
 #endif  // SRC_TINT_LANG_SPIRV_WRITER_RAISE_BUILTIN_POLYFILL_H_
diff --git a/src/tint/lang/wgsl/resolver/builtin_validation_test.cc b/src/tint/lang/wgsl/resolver/builtin_validation_test.cc
index 09d8f4a..ee29c45 100644
--- a/src/tint/lang/wgsl/resolver/builtin_validation_test.cc
+++ b/src/tint/lang/wgsl/resolver/builtin_validation_test.cc
@@ -669,17 +669,272 @@
 
 TEST_F(ResolverBuiltinValidationTest, SubgroupBallotWithExtension) {
     // enable chromium_experimental_subgroups;
-    // fn func { return subgroupBallot(); }
+    // fn func -> vec4<u32> { return subgroupBallot(); }
     Enable(core::Extension::kChromiumExperimentalSubgroups);
 
     Func("func", tint::Empty, ty.vec4<u32>(),
          Vector{
-             Return(Call(Source{Source::Location{12, 34}}, "subgroupBallot")),
+             Return(Call("subgroupBallot")),
          });
 
     EXPECT_TRUE(r()->Resolve());
 }
 
+TEST_F(ResolverBuiltinValidationTest, SubgroupBroadcastWithoutExtension) {
+    // fn func -> i32 { return subgroupBroadcast(1,0); }
+    Func("func", tint::Empty, ty.i32(),
+         Vector{
+             Return(Call(Source{{12, 34}}, "subgroupBroadcast", 1_i, 0_i)),
+         });
+
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(
+        r()->error(),
+        R"(12:34 error: cannot call built-in function 'subgroupBroadcast' without extension chromium_experimental_subgroups)");
+}
+
+TEST_F(ResolverBuiltinValidationTest, SubgroupBroadcastWithExtension) {
+    // enable chromium_experimental_subgroups;
+    // fn func -> i32 { return subgroupBroadcast(1,0); }
+    Enable(core::Extension::kChromiumExperimentalSubgroups);
+
+    Func("func", tint::Empty, ty.i32(),
+         Vector{
+             Return(Call("subgroupBroadcast", 1_i, 0_i)),
+         });
+
+    EXPECT_TRUE(r()->Resolve());
+}
+
+TEST_F(ResolverBuiltinValidationTest, SubroupBroadcastInComputeStage) {
+    // @vertex fn func { dpdx(1.0); }
+
+    Enable(core::Extension::kChromiumExperimentalSubgroups);
+
+    auto* call = Call("subgroupBroadcast", 1_f, 0_u);
+    Func(Source{{1, 2}}, "func", tint::Empty, ty.void_(), Vector{Ignore(call)},
+         Vector{
+             Stage(ast::PipelineStage::kCompute),
+             WorkgroupSize(1_i),
+         });
+
+    EXPECT_TRUE(r()->Resolve());
+}
+
+TEST_F(ResolverBuiltinValidationTest, SubroupBroadcastInVertexStageIsError) {
+    // @vertex fn func { dpdx(1.0); }
+
+    Enable(core::Extension::kChromiumExperimentalSubgroups);
+
+    auto* call = Call(Source{{3, 4}}, "subgroupBroadcast", 1_f, 0_u);
+    Func("func", tint::Empty, ty.vec4<f32>(), Vector{Ignore(call), Return(Call(ty.vec4<f32>()))},
+         Vector{
+             Stage(ast::PipelineStage::kVertex),
+         },
+         Vector{Builtin(core::BuiltinValue::kPosition)});
+
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(), "3:4 error: built-in cannot be used by vertex pipeline stage");
+}
+
+TEST_F(ResolverBuiltinValidationTest, SubroupBroadcastInFragmentStageIsError) {
+    // @vertex fn func { dpdx(1.0); }
+
+    Enable(core::Extension::kChromiumExperimentalSubgroups);
+
+    auto* call = Call(Source{{3, 4}}, "subgroupBroadcast", 1_f, 0_u);
+    Func("func",
+         Vector{Param("pos", ty.vec4<f32>(), Vector{Builtin(core::BuiltinValue::kPosition)})},
+         ty.void_(), Vector{Ignore(call)},
+         Vector{
+             Stage(ast::PipelineStage::kFragment),
+         });
+
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(), "3:4 error: built-in cannot be used by fragment pipeline stage");
+}
+
+TEST_F(ResolverBuiltinValidationTest, SubgroupBroadcastTooFewArgs) {
+    // enable chromium_experimental_subgroups;
+    // fn func -> i32 { return subgroupBroadcast(1); }
+    Enable(core::Extension::kChromiumExperimentalSubgroups);
+
+    Func("func", tint::Empty, ty.i32(),
+         Vector{
+             Return(Call(Source{Source::Location{12, 34}}, "subgroupBroadcast", 1_i)),
+         });
+
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(),
+              R"(12:34 error: no matching call to subgroupBroadcast(i32)
+
+1 candidate function:
+  subgroupBroadcast(value: T, sourceLaneIndex: L) -> T  where: T is f32, i32 or u32, L is i32 or u32
+)");
+}
+
+TEST_F(ResolverBuiltinValidationTest, SubgroupBroadcastTooManyArgs) {
+    // enable chromium_experimental_subgroups;
+    // fn func -> i32 { return subgroupBroadcast(1,1,1); }
+    Enable(core::Extension::kChromiumExperimentalSubgroups);
+
+    Func("func", tint::Empty, ty.i32(),
+         Vector{
+             Return(Call(Source{{12, 34}}, "subgroupBroadcast", 1_i, 1_i, 1_i)),
+         });
+
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(),
+              R"(12:34 error: no matching call to subgroupBroadcast(i32, i32, i32)
+
+1 candidate function:
+  subgroupBroadcast(value: T, sourceLaneIndex: L) -> T  where: T is f32, i32 or u32, L is i32 or u32
+)");
+}
+
+TEST_F(ResolverBuiltinValidationTest, SubgroupBroadcastValueF32) {
+    Enable(core::Extension::kChromiumExperimentalSubgroups);
+    Func("func", tint::Empty, ty.f32(),
+         Vector{
+             Return(Call("subgroupBroadcast", 1_f, 0_i)),
+         });
+    EXPECT_TRUE(r()->Resolve());
+}
+
+TEST_F(ResolverBuiltinValidationTest, SubgroupBroadcastValueI32) {
+    Enable(core::Extension::kChromiumExperimentalSubgroups);
+    Func("func", tint::Empty, ty.i32(),
+         Vector{
+             Return(Call("subgroupBroadcast", 1_i, 0_i)),
+         });
+    EXPECT_TRUE(r()->Resolve());
+}
+
+TEST_F(ResolverBuiltinValidationTest, SubgroupBroadcastValueU32) {
+    Enable(core::Extension::kChromiumExperimentalSubgroups);
+    Func("func", tint::Empty, ty.u32(),
+         Vector{
+             Return(Call("subgroupBroadcast", 1_u, 0_i)),
+         });
+    EXPECT_TRUE(r()->Resolve());
+}
+
+TEST_F(ResolverBuiltinValidationTest, SubgroupBroadcastValueBoolIsError) {
+    Enable(core::Extension::kChromiumExperimentalSubgroups);
+    const bool value = false;  // could be true too
+    Func("func", tint::Empty, ty.bool_(),
+         Vector{
+             Return(Call(Source{{12, 34}}, "subgroupBroadcast", value, 0_i)),
+         });
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(),
+              R"(12:34 error: no matching call to subgroupBroadcast(bool, i32)
+
+1 candidate function:
+  subgroupBroadcast(value: T, sourceLaneIndex: L) -> T  where: T is f32, i32 or u32, L is i32 or u32
+)");
+}
+
+TEST_F(ResolverBuiltinValidationTest, SubgroupBroadcastValueF32ResultTypeDifferentIsError) {
+    Enable(core::Extension::kChromiumExperimentalSubgroups);
+    Func("func", tint::Empty, ty.i32(),
+         Vector{
+             Return(Source{{12, 34}}, Call("subgroupBroadcast", 1_f, 0_i)),
+         });
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(
+        r()->error(),
+        R"(12:34 error: return statement type must match its function return type, returned 'f32', expected 'i32')");
+}
+
+TEST_F(ResolverBuiltinValidationTest, SubgroupBroadcastValueI32ResultTypeDifferentIsError) {
+    Enable(core::Extension::kChromiumExperimentalSubgroups);
+    Func("func", tint::Empty, ty.u32(),
+         Vector{
+             Return(Source{{12, 34}}, Call("subgroupBroadcast", 1_i, 0_i)),
+         });
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(
+        r()->error(),
+        R"(12:34 error: return statement type must match its function return type, returned 'i32', expected 'u32')");
+}
+
+TEST_F(ResolverBuiltinValidationTest, SubgroupBroadcastValueU32ResultTypeDifferentIsError) {
+    Enable(core::Extension::kChromiumExperimentalSubgroups);
+    Func("func", tint::Empty, ty.f32(),
+         Vector{
+             Return(Source{{12, 34}}, Call("subgroupBroadcast", 1_u, 0_i)),
+         });
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(
+        r()->error(),
+        R"(12:34 error: return statement type must match its function return type, returned 'u32', expected 'f32')");
+}
+
+TEST_F(ResolverBuiltinValidationTest, SubgroupBroadcastLaneArgMustBeConst) {
+    Enable(core::Extension::kChromiumExperimentalSubgroups);
+    Func("func", tint::Empty, ty.void_(),
+         Vector{
+             Decl(Let("lane", Expr(1_u))),
+             Ignore(Call("subgroupBroadcast", 1_f, Ident(Source{{12, 34}}, "lane"))),
+         });
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(
+        r()->error(),
+        R"(12:34 error: the sourceLaneIndex argument of subgroupBroadcast must be a const-expression)");
+}
+
+TEST_F(ResolverBuiltinValidationTest, SubgroupBroadcastLaneArgI32) {
+    Enable(core::Extension::kChromiumExperimentalSubgroups);
+    Func("func", tint::Empty, ty.void_(),
+         Vector{
+             Ignore(Call("subgroupBroadcast", 1_f, 0_i)),
+         });
+    EXPECT_TRUE(r()->Resolve());
+}
+
+TEST_F(ResolverBuiltinValidationTest, SubgroupBroadcastLaneArgU32) {
+    Enable(core::Extension::kChromiumExperimentalSubgroups);
+    Func("func", tint::Empty, ty.void_(),
+         Vector{
+             Ignore(Call("subgroupBroadcast", 1_f, 0_u)),
+         });
+    EXPECT_TRUE(r()->Resolve());
+}
+
+TEST_F(ResolverBuiltinValidationTest, SubgroupBroadcastLaneArgF32IsError) {
+    Enable(core::Extension::kChromiumExperimentalSubgroups);
+    Func("func", tint::Empty, ty.void_(),
+         Vector{
+             Decl(Let("lane", Expr(1_u))),
+             Ignore(Call(Source{{12, 34}}, "subgroupBroadcast", 1_f, 0_f)),
+         });
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(),
+              R"(12:34 error: no matching call to subgroupBroadcast(f32, f32)
+
+1 candidate function:
+  subgroupBroadcast(value: T, sourceLaneIndex: L) -> T  where: T is f32, i32 or u32, L is i32 or u32
+)");
+}
+
+TEST_F(ResolverBuiltinValidationTest, SubgroupBroadcastLaneArgBoolIsError) {
+    Enable(core::Extension::kChromiumExperimentalSubgroups);
+    const bool value = false;  // could be true too
+    Func("func", tint::Empty, ty.void_(),
+         Vector{
+             Decl(Let("lane", Expr(1_u))),
+             Ignore(Call(Source{{12, 34}}, "subgroupBroadcast", 1_f, value)),
+         });
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(),
+              R"(12:34 error: no matching call to subgroupBroadcast(f32, bool)
+
+1 candidate function:
+  subgroupBroadcast(value: T, sourceLaneIndex: L) -> T  where: T is f32, i32 or u32, L is i32 or u32
+)");
+}
+
 TEST_F(ResolverBuiltinValidationTest, TextureBarrierWithoutExtension) {
     // fn func { textureBarrier(); }
     Func("func", tint::Empty, ty.void_(),
diff --git a/src/tint/lang/wgsl/resolver/resolver.cc b/src/tint/lang/wgsl/resolver/resolver.cc
index 5327b0d..df0c53d 100644
--- a/src/tint/lang/wgsl/resolver/resolver.cc
+++ b/src/tint/lang/wgsl/resolver/resolver.cc
@@ -2504,6 +2504,12 @@
         }
     }
 
+    if (fn == core::Function::kSubgroupBroadcast) {
+        if (!validator_.SubgroupBroadcast(call)) {
+            return nullptr;
+        }
+    }
+
     if (!validator_.BuiltinCall(call)) {
         return nullptr;
     }
diff --git a/src/tint/lang/wgsl/resolver/validator.cc b/src/tint/lang/wgsl/resolver/validator.cc
index 22e769f..235c625 100644
--- a/src/tint/lang/wgsl/resolver/validator.cc
+++ b/src/tint/lang/wgsl/resolver/validator.cc
@@ -1716,6 +1716,23 @@
     return true;
 }
 
+bool Validator::SubgroupBroadcast(const sem::Call* call) const {
+    auto* builtin = call->Target()->As<sem::Builtin>();
+    if (!builtin) {
+        return false;
+    }
+
+    TINT_ASSERT(call->Arguments().Length() == 2);
+    auto* laneArg = call->Arguments()[1];
+    if (!laneArg->ConstantValue()) {
+        AddError("the sourceLaneIndex argument of subgroupBroadcast must be a const-expression",
+                 laneArg->Declaration()->source);
+        return false;
+    }
+
+    return true;
+}
+
 bool Validator::RequiredExtensionForBuiltinFunction(const sem::Call* call) const {
     const auto* builtin = call->Target()->As<sem::Builtin>();
     if (!builtin) {
diff --git a/src/tint/lang/wgsl/resolver/validator.h b/src/tint/lang/wgsl/resolver/validator.h
index 27e9082..a4035a3 100644
--- a/src/tint/lang/wgsl/resolver/validator.h
+++ b/src/tint/lang/wgsl/resolver/validator.h
@@ -461,6 +461,11 @@
     /// @returns true on success, false otherwise
     bool WorkgroupUniformLoad(const sem::Call* call) const;
 
+    /// Validates a subgroupBroadcast builtin function
+    /// @param call the builtin call to validate
+    /// @returns true on success, false otherwise
+    bool SubgroupBroadcast(const sem::Call* call) const;
+
     /// Validates an optional builtin function and its required extension.
     /// @param call the builtin call to validate
     /// @returns true on success, false otherwise