[hlsl] Convert bitcast to a transform.

This CL pulls the code out of the printer which supports bitcast and
moves it into a transform to generate the needed IR.

This adds custom intrinsics into the HLSL back to support the `asuint`,
`asint` and `asfloat` HLSL methods along with `f16tof32` and `f32tof16`.

Bug: 42251045, 348590924
Change-Id: I89465de1fd4171c89cbe4e5029ed9dedb0f803a0
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/195354
Commit-Queue: dan sinclair <dsinclair@chromium.org>
Reviewed-by: James Price <jrprice@google.com>
Commit-Queue: James Price <jrprice@google.com>
Auto-Submit: dan sinclair <dsinclair@chromium.org>
diff --git a/src/tint/cmd/test/BUILD.bazel b/src/tint/cmd/test/BUILD.bazel
index cc3866d..1c6c48d 100644
--- a/src/tint/cmd/test/BUILD.bazel
+++ b/src/tint/cmd/test/BUILD.bazel
@@ -51,6 +51,7 @@
     "//src/tint/lang/core/ir:test",
     "//src/tint/lang/core/type:test",
     "//src/tint/lang/core:test",
+    "//src/tint/lang/hlsl/ir:test",
     "//src/tint/lang/hlsl/writer/common:test",
     "//src/tint/lang/hlsl/writer/raise:test",
     "//src/tint/lang/msl/ir:test",
diff --git a/src/tint/cmd/test/BUILD.cmake b/src/tint/cmd/test/BUILD.cmake
index 341bf6e..981ea88 100644
--- a/src/tint/cmd/test/BUILD.cmake
+++ b/src/tint/cmd/test/BUILD.cmake
@@ -52,6 +52,7 @@
   tint_lang_core_ir_test
   tint_lang_core_type_test
   tint_lang_core_test
+  tint_lang_hlsl_ir_test
   tint_lang_hlsl_writer_common_test
   tint_lang_hlsl_writer_raise_test
   tint_lang_msl_ir_test
diff --git a/src/tint/cmd/test/BUILD.gn b/src/tint/cmd/test/BUILD.gn
index ea2de9c..8a954fe 100644
--- a/src/tint/cmd/test/BUILD.gn
+++ b/src/tint/cmd/test/BUILD.gn
@@ -57,6 +57,7 @@
       "${tint_src_dir}/lang/core/ir/transform:unittests",
       "${tint_src_dir}/lang/core/ir/transform/common:unittests",
       "${tint_src_dir}/lang/core/type:unittests",
+      "${tint_src_dir}/lang/hlsl/ir:unittests",
       "${tint_src_dir}/lang/hlsl/writer/common:unittests",
       "${tint_src_dir}/lang/hlsl/writer/raise:unittests",
       "${tint_src_dir}/lang/msl/ir:unittests",
diff --git a/src/tint/lang/hlsl/BUILD.bazel b/src/tint/lang/hlsl/BUILD.bazel
index 1a114c6..5470814 100644
--- a/src/tint/lang/hlsl/BUILD.bazel
+++ b/src/tint/lang/hlsl/BUILD.bazel
@@ -36,4 +36,18 @@
 
 load("//src/tint:flags.bzl", "COPTS")
 load("@bazel_skylib//lib:selects.bzl", "selects")
+cc_library(
+  name = "hlsl",
+  srcs = [
+    "builtin_fn.cc",
+  ],
+  hdrs = [
+    "builtin_fn.h",
+  ],
+  deps = [
+    "//src/tint/utils/traits",
+  ],
+  copts = COPTS,
+  visibility = ["//visibility:public"],
+)
 
diff --git a/src/tint/lang/hlsl/BUILD.cmake b/src/tint/lang/hlsl/BUILD.cmake
index 0257514..43d49fd 100644
--- a/src/tint/lang/hlsl/BUILD.cmake
+++ b/src/tint/lang/hlsl/BUILD.cmake
@@ -34,5 +34,20 @@
 #                       Do not modify this file directly
 ################################################################################
 
+include(lang/hlsl/intrinsic/BUILD.cmake)
+include(lang/hlsl/ir/BUILD.cmake)
 include(lang/hlsl/validate/BUILD.cmake)
 include(lang/hlsl/writer/BUILD.cmake)
+
+################################################################################
+# Target:    tint_lang_hlsl
+# Kind:      lib
+################################################################################
+tint_add_target(tint_lang_hlsl lib
+  lang/hlsl/builtin_fn.cc
+  lang/hlsl/builtin_fn.h
+)
+
+tint_target_add_dependencies(tint_lang_hlsl lib
+  tint_utils_traits
+)
diff --git a/src/tint/lang/hlsl/BUILD.gn b/src/tint/lang/hlsl/BUILD.gn
index 6caa255..a5dc8b3 100644
--- a/src/tint/lang/hlsl/BUILD.gn
+++ b/src/tint/lang/hlsl/BUILD.gn
@@ -37,3 +37,11 @@
 import("../../../../scripts/tint_overrides_with_defaults.gni")
 
 import("${tint_src_dir}/tint.gni")
+
+libtint_source_set("hlsl") {
+  sources = [
+    "builtin_fn.cc",
+    "builtin_fn.h",
+  ]
+  deps = [ "${tint_src_dir}/utils/traits" ]
+}
diff --git a/src/tint/lang/hlsl/builtin_fn.cc b/src/tint/lang/hlsl/builtin_fn.cc
new file mode 100644
index 0000000..6a721ea
--- /dev/null
+++ b/src/tint/lang/hlsl/builtin_fn.cc
@@ -0,0 +1,59 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+////////////////////////////////////////////////////////////////////////////////
+// File generated by 'tools/src/cmd/gen' using the template:
+//   src/tint/lang/hlsl/builtin_fn.cc.tmpl
+//
+// To regenerate run: './tools/run gen'
+//
+//                       Do not modify this file directly
+////////////////////////////////////////////////////////////////////////////////
+
+#include "src/tint/lang/hlsl/builtin_fn.h"
+
+namespace tint::hlsl {
+
+const char* str(BuiltinFn i) {
+    switch (i) {
+        case BuiltinFn::kNone:
+            return "<none>";
+        case BuiltinFn::kAsint:
+            return "asint";
+        case BuiltinFn::kAsuint:
+            return "asuint";
+        case BuiltinFn::kAsfloat:
+            return "asfloat";
+        case BuiltinFn::kF32Tof16:
+            return "f32tof16";
+        case BuiltinFn::kF16Tof32:
+            return "f16tof32";
+    }
+    return "<unknown>";
+}
+
+}  // namespace tint::hlsl
diff --git a/src/tint/lang/hlsl/builtin_fn.cc.tmpl b/src/tint/lang/hlsl/builtin_fn.cc.tmpl
new file mode 100644
index 0000000..0a81199
--- /dev/null
+++ b/src/tint/lang/hlsl/builtin_fn.cc.tmpl
@@ -0,0 +1,31 @@
+{{- /*
+--------------------------------------------------------------------------------
+Template file for use with tools/src/cmd/gen to generate builtin_fn.cc
+
+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
+--------------------------------------------------------------------------------
+*/ -}}
+
+{{- $I := LoadIntrinsics "src/tint/lang/hlsl/hlsl.def" -}}
+#include "src/tint/lang/hlsl/builtin_fn.h"
+
+namespace tint::hlsl {
+
+const char* str(BuiltinFn i) {
+    switch (i) {
+        case BuiltinFn::kNone:
+            return "<none>";
+{{- range $I.Sem.Builtins  }}
+        case BuiltinFn::k{{PascalCase .Name}}:
+            return "{{.Name}}";
+{{- end  }}
+    }
+    return "<unknown>";
+}
+
+}  // namespace tint::hlsl
diff --git a/src/tint/lang/hlsl/builtin_fn.h b/src/tint/lang/hlsl/builtin_fn.h
new file mode 100644
index 0000000..1071021
--- /dev/null
+++ b/src/tint/lang/hlsl/builtin_fn.h
@@ -0,0 +1,70 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+////////////////////////////////////////////////////////////////////////////////
+// File generated by 'tools/src/cmd/gen' using the template:
+//   src/tint/lang/hlsl/builtin_fn.h.tmpl
+//
+// To regenerate run: './tools/run gen'
+//
+//                       Do not modify this file directly
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef SRC_TINT_LANG_HLSL_BUILTIN_FN_H_
+#define SRC_TINT_LANG_HLSL_BUILTIN_FN_H_
+
+#include <cstdint>
+#include <string>
+
+#include "src/tint/utils/traits/traits.h"
+
+// \cond DO_NOT_DOCUMENT
+namespace tint::hlsl {
+
+/// Enumerator of all builtin functions
+enum class BuiltinFn : uint8_t {
+    kAsint,
+    kAsuint,
+    kAsfloat,
+    kF32Tof16,
+    kF16Tof32,
+    kNone,
+};
+
+/// @returns the name of the builtin function type.
+const char* str(BuiltinFn i);
+
+/// Emits the name of the builtin function type.
+template <typename STREAM, typename = traits::EnableIfIsOStream<STREAM>>
+auto& operator<<(STREAM& o, BuiltinFn i) {
+    return o << str(i);
+}
+
+}  // namespace tint::hlsl
+// \endcond
+
+#endif  // SRC_TINT_LANG_HLSL_BUILTIN_FN_H_
diff --git a/src/tint/lang/hlsl/builtin_fn.h.tmpl b/src/tint/lang/hlsl/builtin_fn.h.tmpl
new file mode 100644
index 0000000..566bd9b
--- /dev/null
+++ b/src/tint/lang/hlsl/builtin_fn.h.tmpl
@@ -0,0 +1,47 @@
+{{- /*
+--------------------------------------------------------------------------------
+Template file for use with tools/src/cmd/gen to generate builtin_fn.h
+
+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
+--------------------------------------------------------------------------------
+*/ -}}
+
+{{- $I := LoadIntrinsics "src/tint/lang/hlsl/hlsl.def" -}}
+
+#ifndef SRC_TINT_LANG_HLSL_BUILTIN_FN_H_
+#define SRC_TINT_LANG_HLSL_BUILTIN_FN_H_
+
+#include <cstdint>
+#include <string>
+
+#include "src/tint/utils/traits/traits.h"
+
+// \cond DO_NOT_DOCUMENT
+namespace tint::hlsl {
+
+/// Enumerator of all builtin functions
+enum class BuiltinFn : uint8_t {
+{{- range $I.Sem.Builtins }}
+    k{{PascalCase .Name}},
+{{- end }}
+    kNone,
+};
+
+/// @returns the name of the builtin function type.
+const char* str(BuiltinFn i);
+
+/// Emits the name of the builtin function type.
+template <typename STREAM, typename = traits::EnableIfIsOStream<STREAM>>
+auto& operator<<(STREAM& o, BuiltinFn i) {
+  return o << str(i);
+}
+
+}  // namespace tint::hlsl
+// \endcond
+
+#endif  // SRC_TINT_LANG_HLSL_BUILTIN_FN_H_
diff --git a/src/tint/lang/hlsl/hlsl.def b/src/tint/lang/hlsl/hlsl.def
new file mode 100644
index 0000000..6eab53a
--- /dev/null
+++ b/src/tint/lang/hlsl/hlsl.def
@@ -0,0 +1,73 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+////////////////////////////////////////////////////////////////////////////////
+// HLSL builtin definition file                                               //
+//                                                                            //
+// After modifying this file, run:                                            //
+//    tools/run gen                                                           //
+// from the Dawn source directory.                                            //
+//                                                                            //
+// See docs/tint/intrinsic_definition_files.md for syntax                     //
+////////////////////////////////////////////////////////////////////////////////
+
+////////////////////////////////////////////////////////////////////////////////
+// Types                                                                      //
+////////////////////////////////////////////////////////////////////////////////
+
+type i32
+type u32
+type f32
+type f16
+type vec2<T>
+@display("vec{N}<{T}>")     type vec<N: num, T>
+
+////////////////////////////////////////////////////////////////////////////////
+// Type matchers                                                              //
+////////////////////////////////////////////////////////////////////////////////
+
+match iu32: i32 | u32
+match f32_u32: f32 | u32
+match f32_i32: f32 | i32
+
+////////////////////////////////////////////////////////////////////////////////
+// Builtin Functions                                                          //
+////////////////////////////////////////////////////////////////////////////////
+fn asint[T: f32_u32](T) -> i32
+fn asint[T: f32_u32, N: num](vec<N, T>) -> vec<N, i32>
+
+fn asuint[T: f32_i32](T) -> u32
+fn asuint[T: f32_i32, N: num](vec<N, T>) -> vec<N, u32>
+
+fn asfloat[T: iu32](T) -> f32
+fn asfloat[T: iu32, N: num](vec<N, T>) -> vec<N, f32>
+
+fn f32tof16(f32) -> u32
+fn f32tof16[N: num](vec<N, f32>) -> vec<N, u32>
+
+fn f16tof32(u32) -> f32
+fn f16tof32[N: num](vec<N, u32>) -> vec<N, f32>
diff --git a/src/tint/lang/hlsl/intrinsic/BUILD.bazel b/src/tint/lang/hlsl/intrinsic/BUILD.bazel
new file mode 100644
index 0000000..d26a7e8
--- /dev/null
+++ b/src/tint/lang/hlsl/intrinsic/BUILD.bazel
@@ -0,0 +1,70 @@
+# Copyright 2024 The Dawn & Tint Authors
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions and the following disclaimer in the documentation
+#    and/or other materials provided with the distribution.
+#
+# 3. Neither the name of the copyright holder nor the names of its
+#    contributors may be used to endorse or promote products derived from
+#    this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+################################################################################
+# 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 = "intrinsic",
+  srcs = [
+    "data.cc",
+  ],
+  hdrs = [
+    "dialect.h",
+  ],
+  deps = [
+    "//src/tint/lang/core",
+    "//src/tint/lang/core/constant",
+    "//src/tint/lang/core/intrinsic",
+    "//src/tint/lang/core/type",
+    "//src/tint/lang/hlsl",
+    "//src/tint/utils/containers",
+    "//src/tint/utils/diagnostic",
+    "//src/tint/utils/ice",
+    "//src/tint/utils/id",
+    "//src/tint/utils/macros",
+    "//src/tint/utils/math",
+    "//src/tint/utils/memory",
+    "//src/tint/utils/reflection",
+    "//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/hlsl/intrinsic/BUILD.cmake b/src/tint/lang/hlsl/intrinsic/BUILD.cmake
new file mode 100644
index 0000000..da93e6a
--- /dev/null
+++ b/src/tint/lang/hlsl/intrinsic/BUILD.cmake
@@ -0,0 +1,65 @@
+# Copyright 2024 The Dawn & Tint Authors
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions and the following disclaimer in the documentation
+#    and/or other materials provided with the distribution.
+#
+# 3. Neither the name of the copyright holder nor the names of its
+#    contributors may be used to endorse or promote products derived from
+#    this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+################################################################################
+# 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_hlsl_intrinsic
+# Kind:      lib
+################################################################################
+tint_add_target(tint_lang_hlsl_intrinsic lib
+  lang/hlsl/intrinsic/data.cc
+  lang/hlsl/intrinsic/dialect.h
+)
+
+tint_target_add_dependencies(tint_lang_hlsl_intrinsic lib
+  tint_lang_core
+  tint_lang_core_constant
+  tint_lang_core_intrinsic
+  tint_lang_core_type
+  tint_lang_hlsl
+  tint_utils_containers
+  tint_utils_diagnostic
+  tint_utils_ice
+  tint_utils_id
+  tint_utils_macros
+  tint_utils_math
+  tint_utils_memory
+  tint_utils_reflection
+  tint_utils_result
+  tint_utils_rtti
+  tint_utils_symbol
+  tint_utils_text
+  tint_utils_traits
+)
diff --git a/src/tint/lang/hlsl/intrinsic/BUILD.gn b/src/tint/lang/hlsl/intrinsic/BUILD.gn
new file mode 100644
index 0000000..6f36df6
--- /dev/null
+++ b/src/tint/lang/hlsl/intrinsic/BUILD.gn
@@ -0,0 +1,66 @@
+# Copyright 2024 The Dawn & Tint Authors
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions and the following disclaimer in the documentation
+#    and/or other materials provided with the distribution.
+#
+# 3. Neither the name of the copyright holder nor the names of its
+#    contributors may be used to endorse or promote products derived from
+#    this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+################################################################################
+# 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("intrinsic") {
+  sources = [
+    "data.cc",
+    "dialect.h",
+  ]
+  deps = [
+    "${tint_src_dir}/lang/core",
+    "${tint_src_dir}/lang/core/constant",
+    "${tint_src_dir}/lang/core/intrinsic",
+    "${tint_src_dir}/lang/core/type",
+    "${tint_src_dir}/lang/hlsl",
+    "${tint_src_dir}/utils/containers",
+    "${tint_src_dir}/utils/diagnostic",
+    "${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/reflection",
+    "${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/hlsl/intrinsic/data.cc b/src/tint/lang/hlsl/intrinsic/data.cc
new file mode 100644
index 0000000..e15f8d5
--- /dev/null
+++ b/src/tint/lang/hlsl/intrinsic/data.cc
@@ -0,0 +1,551 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+////////////////////////////////////////////////////////////////////////////////
+// File generated by 'tools/src/cmd/gen' using the template:
+//   src/tint/lang/hlsl/intrinsic/data.cc.tmpl
+//
+// To regenerate run: './tools/run gen'
+//
+//                       Do not modify this file directly
+////////////////////////////////////////////////////////////////////////////////
+
+#include <limits>
+#include <string>
+
+#include "src/tint/lang/core/intrinsic/type_matchers.h"
+#include "src/tint/lang/hlsl/intrinsic/dialect.h"
+#include "src/tint/utils/text/string_stream.h"
+
+namespace tint::hlsl::intrinsic {
+
+using namespace tint::core::intrinsic;  // NOLINT(build/namespaces)
+
+namespace {
+
+using ConstEvalFunctionIndex = tint::core::intrinsic::ConstEvalFunctionIndex;
+using IntrinsicInfo = tint::core::intrinsic::IntrinsicInfo;
+using MatcherIndicesIndex = tint::core::intrinsic::MatcherIndicesIndex;
+using MatchState = tint::core::intrinsic::MatchState;
+using Number = tint::core::intrinsic::Number;
+using NumberMatcher = tint::core::intrinsic::NumberMatcher;
+using NumberMatcherIndex = tint::core::intrinsic::NumberMatcherIndex;
+using OverloadFlag = tint::core::intrinsic::OverloadFlag;
+using OverloadFlags = tint::core::intrinsic::OverloadFlags;
+using OverloadIndex = tint::core::intrinsic::OverloadIndex;
+using OverloadInfo = tint::core::intrinsic::OverloadInfo;
+using ParameterIndex = tint::core::intrinsic::ParameterIndex;
+using ParameterInfo = tint::core::intrinsic::ParameterInfo;
+using StringStream = tint::StringStream;
+using TemplateIndex = tint::core::intrinsic::TemplateIndex;
+using Type = tint::core::type::Type;
+using TypeMatcher = tint::core::intrinsic::TypeMatcher;
+using TypeMatcherIndex = tint::core::intrinsic::TypeMatcherIndex;
+
+template <size_t N>
+using TemplateNumberMatcher = tint::core::intrinsic::TemplateNumberMatcher<N>;
+
+template <size_t N>
+using TemplateTypeMatcher = tint::core::intrinsic::TemplateTypeMatcher<N>;
+
+// clang-format off
+
+/// TypeMatcher for 'type i32'
+constexpr TypeMatcher kI32Matcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+    if (!MatchI32(state, ty)) {
+      return nullptr;
+    }
+    return BuildI32(state, ty);
+  },
+/* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {
+    out << style::Type("i32");
+  }
+};
+
+
+/// TypeMatcher for 'type u32'
+constexpr TypeMatcher kU32Matcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+    if (!MatchU32(state, ty)) {
+      return nullptr;
+    }
+    return BuildU32(state, ty);
+  },
+/* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {
+    out << style::Type("u32");
+  }
+};
+
+
+/// TypeMatcher for 'type f32'
+constexpr TypeMatcher kF32Matcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+    if (!MatchF32(state, ty)) {
+      return nullptr;
+    }
+    return BuildF32(state, ty);
+  },
+/* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {
+    out << style::Type("f32");
+  }
+};
+
+
+/// TypeMatcher for 'type f16'
+constexpr TypeMatcher kF16Matcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+    if (!MatchF16(state, ty)) {
+      return nullptr;
+    }
+    return BuildF16(state, ty);
+  },
+/* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {
+    out << style::Type("f16");
+  }
+};
+
+
+/// TypeMatcher for 'type vec2'
+constexpr TypeMatcher kVec2Matcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+  const Type* T = nullptr;
+    if (!MatchVec2(state, ty, T)) {
+      return nullptr;
+    }
+    T = state.Type(T);
+    if (T == nullptr) {
+      return nullptr;
+    }
+    return BuildVec2(state, ty, T);
+  },
+/* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
+  state->PrintType(T);
+    out << style::Type("vec2", "<", T, ">");
+  }
+};
+
+
+/// TypeMatcher for 'type vec'
+constexpr TypeMatcher kVecMatcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+  Number N = Number::invalid;
+  const Type* T = nullptr;
+    if (!MatchVec(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 BuildVec(state, ty, N, T);
+  },
+/* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText N;
+  state->PrintNum(N);StyledText T;
+  state->PrintType(T);
+    out << style::Type("vec", N, "<", T, ">");
+  }
+};
+
+
+/// TypeMatcher for 'match iu32'
+constexpr TypeMatcher kIu32Matcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+    if (MatchI32(state, ty)) {
+      return BuildI32(state, ty);
+    }
+    if (MatchU32(state, ty)) {
+      return BuildU32(state, ty);
+    }
+    return nullptr;
+  },
+/* print */ [](MatchState*, StyledText& out) {
+    // Note: We pass nullptr to the Matcher.print() functions, as matchers do not support
+    // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
+ kI32Matcher.print(nullptr, out); out << style::Plain(" or "); kU32Matcher.print(nullptr, out);}
+};
+
+/// TypeMatcher for 'match f32_u32'
+constexpr TypeMatcher kF32U32Matcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+    if (MatchF32(state, ty)) {
+      return BuildF32(state, ty);
+    }
+    if (MatchU32(state, ty)) {
+      return BuildU32(state, ty);
+    }
+    return nullptr;
+  },
+/* print */ [](MatchState*, StyledText& out) {
+    // Note: We pass nullptr to the Matcher.print() functions, as matchers do not support
+    // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
+ kF32Matcher.print(nullptr, out); out << style::Plain(" or "); kU32Matcher.print(nullptr, out);}
+};
+
+/// TypeMatcher for 'match f32_i32'
+constexpr TypeMatcher kF32I32Matcher {
+/* match */ [](MatchState& state, const Type* ty) -> const Type* {
+    if (MatchF32(state, ty)) {
+      return BuildF32(state, ty);
+    }
+    if (MatchI32(state, ty)) {
+      return BuildI32(state, ty);
+    }
+    return nullptr;
+  },
+/* print */ [](MatchState*, StyledText& out) {
+    // Note: We pass nullptr to the Matcher.print() functions, as matchers do not support
+    // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
+ kF32Matcher.print(nullptr, out); out << style::Plain(" or "); kI32Matcher.print(nullptr, out);}
+};
+
+/// Type and number matchers
+
+/// The template types, types, and type matchers
+constexpr TypeMatcher kTypeMatchers[] = {
+  /* [0] */ TemplateTypeMatcher<0>::matcher,
+  /* [1] */ TemplateTypeMatcher<1>::matcher,
+  /* [2] */ kI32Matcher,
+  /* [3] */ kU32Matcher,
+  /* [4] */ kF32Matcher,
+  /* [5] */ kF16Matcher,
+  /* [6] */ kVec2Matcher,
+  /* [7] */ kVecMatcher,
+  /* [8] */ kIu32Matcher,
+  /* [9] */ kF32U32Matcher,
+  /* [10] */ kF32I32Matcher,
+};
+
+/// The template numbers, and number matchers
+constexpr NumberMatcher kNumberMatchers[] = {
+  /* [0] */ TemplateNumberMatcher<0>::matcher,
+  /* [1] */ TemplateNumberMatcher<1>::matcher,
+};
+
+constexpr MatcherIndex kMatcherIndices[] = {
+  /* [0] */ MatcherIndex(7),
+  /* [1] */ MatcherIndex(1),
+  /* [2] */ MatcherIndex(2),
+  /* [3] */ MatcherIndex(7),
+  /* [4] */ MatcherIndex(1),
+  /* [5] */ MatcherIndex(0),
+  /* [6] */ MatcherIndex(7),
+  /* [7] */ MatcherIndex(1),
+  /* [8] */ MatcherIndex(3),
+  /* [9] */ MatcherIndex(7),
+  /* [10] */ MatcherIndex(1),
+  /* [11] */ MatcherIndex(4),
+  /* [12] */ MatcherIndex(7),
+  /* [13] */ MatcherIndex(0),
+  /* [14] */ MatcherIndex(3),
+  /* [15] */ MatcherIndex(7),
+  /* [16] */ MatcherIndex(0),
+  /* [17] */ MatcherIndex(4),
+  /* [18] */ MatcherIndex(9),
+  /* [19] */ MatcherIndex(10),
+  /* [20] */ MatcherIndex(8),
+};
+
+static_assert(MatcherIndicesIndex::CanIndex(kMatcherIndices),
+              "MatcherIndicesIndex is not large enough to index kMatcherIndices");
+
+constexpr ParameterInfo kParameters[] = {
+  {
+    /* [0] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* matcher_indices */ MatcherIndicesIndex(5),
+  },
+  {
+    /* [1] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* matcher_indices */ MatcherIndicesIndex(3),
+  },
+  {
+    /* [2] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* matcher_indices */ MatcherIndicesIndex(11),
+  },
+  {
+    /* [3] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* matcher_indices */ MatcherIndicesIndex(15),
+  },
+  {
+    /* [4] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* matcher_indices */ MatcherIndicesIndex(8),
+  },
+  {
+    /* [5] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* matcher_indices */ MatcherIndicesIndex(12),
+  },
+};
+
+static_assert(ParameterIndex::CanIndex(kParameters),
+              "ParameterIndex is not large enough to index kParameters");
+
+constexpr TemplateInfo kTemplates[] = {
+  {
+    /* [0] */
+    /* name */ "T",
+    /* matcher_indices */ MatcherIndicesIndex(18),
+    /* kind */ TemplateInfo::Kind::kType,
+  },
+  {
+    /* [1] */
+    /* name */ "N",
+    /* matcher_indices */ MatcherIndicesIndex(/* invalid */),
+    /* kind */ TemplateInfo::Kind::kNumber,
+  },
+  {
+    /* [2] */
+    /* name */ "T",
+    /* matcher_indices */ MatcherIndicesIndex(19),
+    /* kind */ TemplateInfo::Kind::kType,
+  },
+  {
+    /* [3] */
+    /* name */ "N",
+    /* matcher_indices */ MatcherIndicesIndex(/* invalid */),
+    /* kind */ TemplateInfo::Kind::kNumber,
+  },
+  {
+    /* [4] */
+    /* name */ "T",
+    /* matcher_indices */ MatcherIndicesIndex(20),
+    /* kind */ TemplateInfo::Kind::kType,
+  },
+  {
+    /* [5] */
+    /* name */ "N",
+    /* matcher_indices */ MatcherIndicesIndex(/* invalid */),
+    /* kind */ TemplateInfo::Kind::kNumber,
+  },
+};
+
+static_assert(TemplateIndex::CanIndex(kTemplates),
+              "TemplateIndex is not large enough to index kTemplates");
+
+constexpr OverloadInfo kOverloads[] = {
+  {
+    /* [0] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 1,
+    /* num_explicit_templates */ 0,
+    /* num_templates   */ 1,
+    /* templates */ TemplateIndex(0),
+    /* parameters */ ParameterIndex(0),
+    /* return_matcher_indices */ MatcherIndicesIndex(2),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [1] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 1,
+    /* num_explicit_templates */ 0,
+    /* num_templates   */ 2,
+    /* templates */ TemplateIndex(0),
+    /* parameters */ ParameterIndex(1),
+    /* return_matcher_indices */ MatcherIndicesIndex(0),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [2] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 1,
+    /* num_explicit_templates */ 0,
+    /* num_templates   */ 1,
+    /* templates */ TemplateIndex(2),
+    /* parameters */ ParameterIndex(0),
+    /* return_matcher_indices */ MatcherIndicesIndex(8),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [3] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 1,
+    /* num_explicit_templates */ 0,
+    /* num_templates   */ 2,
+    /* templates */ TemplateIndex(2),
+    /* parameters */ ParameterIndex(1),
+    /* return_matcher_indices */ MatcherIndicesIndex(6),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [4] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 1,
+    /* num_explicit_templates */ 0,
+    /* num_templates   */ 1,
+    /* templates */ TemplateIndex(4),
+    /* parameters */ ParameterIndex(0),
+    /* return_matcher_indices */ MatcherIndicesIndex(11),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [5] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 1,
+    /* num_explicit_templates */ 0,
+    /* num_templates   */ 2,
+    /* templates */ TemplateIndex(4),
+    /* parameters */ ParameterIndex(1),
+    /* return_matcher_indices */ MatcherIndicesIndex(9),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [6] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 1,
+    /* num_explicit_templates */ 0,
+    /* num_templates   */ 0,
+    /* templates */ TemplateIndex(/* invalid */),
+    /* parameters */ ParameterIndex(2),
+    /* return_matcher_indices */ MatcherIndicesIndex(8),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [7] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 1,
+    /* num_explicit_templates */ 0,
+    /* num_templates   */ 1,
+    /* templates */ TemplateIndex(1),
+    /* parameters */ ParameterIndex(3),
+    /* return_matcher_indices */ MatcherIndicesIndex(12),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [8] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 1,
+    /* num_explicit_templates */ 0,
+    /* num_templates   */ 0,
+    /* templates */ TemplateIndex(/* invalid */),
+    /* parameters */ ParameterIndex(4),
+    /* return_matcher_indices */ MatcherIndicesIndex(11),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [9] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 1,
+    /* num_explicit_templates */ 0,
+    /* num_templates   */ 1,
+    /* templates */ TemplateIndex(1),
+    /* parameters */ ParameterIndex(5),
+    /* return_matcher_indices */ MatcherIndicesIndex(15),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+};
+
+static_assert(OverloadIndex::CanIndex(kOverloads),
+              "OverloadIndex is not large enough to index kOverloads");
+
+constexpr IntrinsicInfo kBuiltins[] = {
+  {
+    /* [0] */
+    /* fn asint[T : f32_u32](T) -> i32 */
+    /* fn asint[T : f32_u32, N : num](vec<N, T>) -> vec<N, i32> */
+    /* num overloads */ 2,
+    /* overloads */ OverloadIndex(0),
+  },
+  {
+    /* [1] */
+    /* fn asuint[T : f32_i32](T) -> u32 */
+    /* fn asuint[T : f32_i32, N : num](vec<N, T>) -> vec<N, u32> */
+    /* num overloads */ 2,
+    /* overloads */ OverloadIndex(2),
+  },
+  {
+    /* [2] */
+    /* fn asfloat[T : iu32](T) -> f32 */
+    /* fn asfloat[T : iu32, N : num](vec<N, T>) -> vec<N, f32> */
+    /* num overloads */ 2,
+    /* overloads */ OverloadIndex(4),
+  },
+  {
+    /* [3] */
+    /* fn f32tof16(f32) -> u32 */
+    /* fn f32tof16[N : num](vec<N, f32>) -> vec<N, u32> */
+    /* num overloads */ 2,
+    /* overloads */ OverloadIndex(6),
+  },
+  {
+    /* [4] */
+    /* fn f16tof32(u32) -> f32 */
+    /* fn f16tof32[N : num](vec<N, u32>) -> vec<N, f32> */
+    /* num overloads */ 2,
+    /* overloads */ OverloadIndex(8),
+  },
+};
+
+// clang-format on
+
+}  // anonymous namespace
+
+const core::intrinsic::TableData Dialect::kData{
+    /* templates */ kTemplates,
+    /* type_matcher_indices */ kMatcherIndices,
+    /* type_matchers */ kTypeMatchers,
+    /* number_matchers */ kNumberMatchers,
+    /* parameters */ kParameters,
+    /* overloads */ kOverloads,
+    /* const_eval_functions */ Empty,
+    /* ctor_conv */ Empty,
+    /* builtins */ kBuiltins,
+    /* binary '+' */ tint::core::intrinsic::kNoOverloads,
+    /* binary '-' */ tint::core::intrinsic::kNoOverloads,
+    /* binary '*' */ tint::core::intrinsic::kNoOverloads,
+    /* binary '/' */ tint::core::intrinsic::kNoOverloads,
+    /* binary '%' */ tint::core::intrinsic::kNoOverloads,
+    /* binary '^' */ tint::core::intrinsic::kNoOverloads,
+    /* binary '&' */ tint::core::intrinsic::kNoOverloads,
+    /* binary '|' */ tint::core::intrinsic::kNoOverloads,
+    /* binary '&&' */ tint::core::intrinsic::kNoOverloads,
+    /* binary '||' */ tint::core::intrinsic::kNoOverloads,
+    /* binary '==' */ tint::core::intrinsic::kNoOverloads,
+    /* binary '!=' */ tint::core::intrinsic::kNoOverloads,
+    /* binary '<' */ tint::core::intrinsic::kNoOverloads,
+    /* binary '>' */ tint::core::intrinsic::kNoOverloads,
+    /* binary '<=' */ tint::core::intrinsic::kNoOverloads,
+    /* binary '>=' */ tint::core::intrinsic::kNoOverloads,
+    /* binary '<<' */ tint::core::intrinsic::kNoOverloads,
+    /* binary '>>' */ tint::core::intrinsic::kNoOverloads,
+    /* unary '!' */ tint::core::intrinsic::kNoOverloads,
+    /* unary '~' */ tint::core::intrinsic::kNoOverloads,
+    /* unary '-' */ tint::core::intrinsic::kNoOverloads,
+    /* unary '*' */ tint::core::intrinsic::kNoOverloads,
+    /* unary '&' */ tint::core::intrinsic::kNoOverloads,
+};
+
+}  // namespace tint::hlsl::intrinsic
diff --git a/src/tint/lang/hlsl/intrinsic/data.cc.tmpl b/src/tint/lang/hlsl/intrinsic/data.cc.tmpl
new file mode 100644
index 0000000..68d95c1
--- /dev/null
+++ b/src/tint/lang/hlsl/intrinsic/data.cc.tmpl
@@ -0,0 +1,34 @@
+{{- /*
+--------------------------------------------------------------------------------
+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/hlsl/hlsl.def" -}}
+
+#include <limits>
+#include <string>
+
+#include "src/tint/lang/core/intrinsic/type_matchers.h"
+#include "src/tint/lang/hlsl/intrinsic/dialect.h"
+#include "src/tint/utils/text/string_stream.h"
+
+namespace tint::hlsl::intrinsic {
+
+using namespace tint::core::intrinsic;  // NOLINT(build/namespaces)
+
+{{ Eval "Data"
+  "Intrinsics" $I
+  "Name"       "Dialect::kData" -}}
+
+}  // namespace tint::hlsl::intrinsic
diff --git a/src/tint/lang/hlsl/intrinsic/dialect.h b/src/tint/lang/hlsl/intrinsic/dialect.h
new file mode 100644
index 0000000..d647f46
--- /dev/null
+++ b/src/tint/lang/hlsl/intrinsic/dialect.h
@@ -0,0 +1,51 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef SRC_TINT_LANG_HLSL_INTRINSIC_DIALECT_H_
+#define SRC_TINT_LANG_HLSL_INTRINSIC_DIALECT_H_
+
+#include "src/tint/lang/core/intrinsic/table_data.h"
+#include "src/tint/lang/hlsl/builtin_fn.h"
+
+namespace tint::hlsl::intrinsic {
+
+/// Dialect holds the intrinsic table data and types for the HLSL dialect
+struct Dialect {
+    /// The dialect's intrinsic table data
+    static const core::intrinsic::TableData kData;
+
+    /// The dialect's builtin function enumerator
+    using BuiltinFn = hlsl::BuiltinFn;
+
+    /// @returns the name of the builtin function @p fn
+    /// @param fn the builtin function
+    static std::string_view ToString(BuiltinFn fn) { return str(fn); }
+};
+
+}  // namespace tint::hlsl::intrinsic
+
+#endif  // SRC_TINT_LANG_HLSL_INTRINSIC_DIALECT_H_
diff --git a/src/tint/lang/hlsl/ir/BUILD.bazel b/src/tint/lang/hlsl/ir/BUILD.bazel
new file mode 100644
index 0000000..758aef2
--- /dev/null
+++ b/src/tint/lang/hlsl/ir/BUILD.bazel
@@ -0,0 +1,108 @@
+# Copyright 2024 The Dawn & Tint Authors
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions and the following disclaimer in the documentation
+#    and/or other materials provided with the distribution.
+#
+# 3. Neither the name of the copyright holder nor the names of its
+#    contributors may be used to endorse or promote products derived from
+#    this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+################################################################################
+# 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 = "ir",
+  srcs = [
+    "builtin_call.cc",
+  ],
+  hdrs = [
+    "builtin_call.h",
+  ],
+  deps = [
+    "//src/tint/api/common",
+    "//src/tint/lang/core",
+    "//src/tint/lang/core/constant",
+    "//src/tint/lang/core/intrinsic",
+    "//src/tint/lang/core/ir",
+    "//src/tint/lang/core/type",
+    "//src/tint/lang/hlsl",
+    "//src/tint/lang/hlsl/intrinsic",
+    "//src/tint/utils/containers",
+    "//src/tint/utils/diagnostic",
+    "//src/tint/utils/ice",
+    "//src/tint/utils/id",
+    "//src/tint/utils/macros",
+    "//src/tint/utils/math",
+    "//src/tint/utils/memory",
+    "//src/tint/utils/reflection",
+    "//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"],
+)
+cc_library(
+  name = "test",
+  alwayslink = True,
+  srcs = [
+    "builtin_call_test.cc",
+  ],
+  deps = [
+    "//src/tint/api/common",
+    "//src/tint/lang/core",
+    "//src/tint/lang/core/constant",
+    "//src/tint/lang/core/intrinsic",
+    "//src/tint/lang/core/ir",
+    "//src/tint/lang/core/ir:test",
+    "//src/tint/lang/core/type",
+    "//src/tint/lang/hlsl",
+    "//src/tint/lang/hlsl/intrinsic",
+    "//src/tint/lang/hlsl/ir",
+    "//src/tint/utils/containers",
+    "//src/tint/utils/diagnostic",
+    "//src/tint/utils/ice",
+    "//src/tint/utils/id",
+    "//src/tint/utils/macros",
+    "//src/tint/utils/math",
+    "//src/tint/utils/memory",
+    "//src/tint/utils/reflection",
+    "//src/tint/utils/result",
+    "//src/tint/utils/rtti",
+    "//src/tint/utils/symbol",
+    "//src/tint/utils/text",
+    "//src/tint/utils/traits",
+    "@gtest",
+  ],
+  copts = COPTS,
+  visibility = ["//visibility:public"],
+)
+
diff --git a/src/tint/lang/hlsl/ir/BUILD.cmake b/src/tint/lang/hlsl/ir/BUILD.cmake
new file mode 100644
index 0000000..c94d5f4
--- /dev/null
+++ b/src/tint/lang/hlsl/ir/BUILD.cmake
@@ -0,0 +1,106 @@
+# Copyright 2024 The Dawn & Tint Authors
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions and the following disclaimer in the documentation
+#    and/or other materials provided with the distribution.
+#
+# 3. Neither the name of the copyright holder nor the names of its
+#    contributors may be used to endorse or promote products derived from
+#    this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+################################################################################
+# 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_hlsl_ir
+# Kind:      lib
+################################################################################
+tint_add_target(tint_lang_hlsl_ir lib
+  lang/hlsl/ir/builtin_call.cc
+  lang/hlsl/ir/builtin_call.h
+)
+
+tint_target_add_dependencies(tint_lang_hlsl_ir lib
+  tint_api_common
+  tint_lang_core
+  tint_lang_core_constant
+  tint_lang_core_intrinsic
+  tint_lang_core_ir
+  tint_lang_core_type
+  tint_lang_hlsl
+  tint_lang_hlsl_intrinsic
+  tint_utils_containers
+  tint_utils_diagnostic
+  tint_utils_ice
+  tint_utils_id
+  tint_utils_macros
+  tint_utils_math
+  tint_utils_memory
+  tint_utils_reflection
+  tint_utils_result
+  tint_utils_rtti
+  tint_utils_symbol
+  tint_utils_text
+  tint_utils_traits
+)
+
+################################################################################
+# Target:    tint_lang_hlsl_ir_test
+# Kind:      test
+################################################################################
+tint_add_target(tint_lang_hlsl_ir_test test
+  lang/hlsl/ir/builtin_call_test.cc
+)
+
+tint_target_add_dependencies(tint_lang_hlsl_ir_test test
+  tint_api_common
+  tint_lang_core
+  tint_lang_core_constant
+  tint_lang_core_intrinsic
+  tint_lang_core_ir
+  tint_lang_core_ir_test
+  tint_lang_core_type
+  tint_lang_hlsl
+  tint_lang_hlsl_intrinsic
+  tint_lang_hlsl_ir
+  tint_utils_containers
+  tint_utils_diagnostic
+  tint_utils_ice
+  tint_utils_id
+  tint_utils_macros
+  tint_utils_math
+  tint_utils_memory
+  tint_utils_reflection
+  tint_utils_result
+  tint_utils_rtti
+  tint_utils_symbol
+  tint_utils_text
+  tint_utils_traits
+)
+
+tint_target_add_external_dependencies(tint_lang_hlsl_ir_test test
+  "gtest"
+)
diff --git a/src/tint/lang/hlsl/ir/BUILD.gn b/src/tint/lang/hlsl/ir/BUILD.gn
new file mode 100644
index 0000000..2cb3e39
--- /dev/null
+++ b/src/tint/lang/hlsl/ir/BUILD.gn
@@ -0,0 +1,104 @@
+# Copyright 2024 The Dawn & Tint Authors
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions and the following disclaimer in the documentation
+#    and/or other materials provided with the distribution.
+#
+# 3. Neither the name of the copyright holder nor the names of its
+#    contributors may be used to endorse or promote products derived from
+#    this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+################################################################################
+# 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")
+
+if (tint_build_unittests || tint_build_benchmarks) {
+  import("//testing/test.gni")
+}
+
+libtint_source_set("ir") {
+  sources = [
+    "builtin_call.cc",
+    "builtin_call.h",
+  ]
+  deps = [
+    "${tint_src_dir}/api/common",
+    "${tint_src_dir}/lang/core",
+    "${tint_src_dir}/lang/core/constant",
+    "${tint_src_dir}/lang/core/intrinsic",
+    "${tint_src_dir}/lang/core/ir",
+    "${tint_src_dir}/lang/core/type",
+    "${tint_src_dir}/lang/hlsl",
+    "${tint_src_dir}/lang/hlsl/intrinsic",
+    "${tint_src_dir}/utils/containers",
+    "${tint_src_dir}/utils/diagnostic",
+    "${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/reflection",
+    "${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",
+  ]
+}
+if (tint_build_unittests) {
+  tint_unittests_source_set("unittests") {
+    sources = [ "builtin_call_test.cc" ]
+    deps = [
+      "${tint_src_dir}:gmock_and_gtest",
+      "${tint_src_dir}/api/common",
+      "${tint_src_dir}/lang/core",
+      "${tint_src_dir}/lang/core/constant",
+      "${tint_src_dir}/lang/core/intrinsic",
+      "${tint_src_dir}/lang/core/ir",
+      "${tint_src_dir}/lang/core/ir:unittests",
+      "${tint_src_dir}/lang/core/type",
+      "${tint_src_dir}/lang/hlsl",
+      "${tint_src_dir}/lang/hlsl/intrinsic",
+      "${tint_src_dir}/lang/hlsl/ir",
+      "${tint_src_dir}/utils/containers",
+      "${tint_src_dir}/utils/diagnostic",
+      "${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/reflection",
+      "${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/hlsl/ir/builtin_call.cc b/src/tint/lang/hlsl/ir/builtin_call.cc
new file mode 100644
index 0000000..2101f71
--- /dev/null
+++ b/src/tint/lang/hlsl/ir/builtin_call.cc
@@ -0,0 +1,56 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "src/tint/lang/hlsl/ir/builtin_call.h"
+
+#include <utility>
+
+#include "src/tint/lang/core/ir/clone_context.h"
+#include "src/tint/lang/core/ir/module.h"
+#include "src/tint/utils/ice/ice.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::hlsl::ir::BuiltinCall);
+
+namespace tint::hlsl::ir {
+
+BuiltinCall::BuiltinCall(core::ir::InstructionResult* result,
+                         BuiltinFn func,
+                         VectorRef<core::ir::Value*> arguments)
+    : Base(result, arguments), func_(func) {
+    flags_.Add(Flag::kSequenced);
+    TINT_ASSERT(func != BuiltinFn::kNone);
+}
+
+BuiltinCall::~BuiltinCall() = default;
+
+BuiltinCall* BuiltinCall::Clone(core::ir::CloneContext& ctx) {
+    auto* new_result = ctx.Clone(Result(0));
+    auto new_args = ctx.Clone<BuiltinCall::kDefaultNumOperands>(Args());
+    return ctx.ir.allocators.instructions.Create<BuiltinCall>(new_result, func_, new_args);
+}
+
+}  // namespace tint::hlsl::ir
diff --git a/src/tint/lang/hlsl/ir/builtin_call.h b/src/tint/lang/hlsl/ir/builtin_call.h
new file mode 100644
index 0000000..9416c5b
--- /dev/null
+++ b/src/tint/lang/hlsl/ir/builtin_call.h
@@ -0,0 +1,76 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef SRC_TINT_LANG_HLSL_IR_BUILTIN_CALL_H_
+#define SRC_TINT_LANG_HLSL_IR_BUILTIN_CALL_H_
+
+#include <string>
+
+#include "src/tint/lang/core/intrinsic/table_data.h"
+#include "src/tint/lang/core/ir/builtin_call.h"
+#include "src/tint/lang/hlsl/builtin_fn.h"
+#include "src/tint/lang/hlsl/intrinsic/dialect.h"
+#include "src/tint/utils/rtti/castable.h"
+
+namespace tint::hlsl::ir {
+
+/// A HLSL builtin call instruction in the IR.
+class BuiltinCall final : public Castable<BuiltinCall, core::ir::BuiltinCall> {
+  public:
+    /// Constructor
+    /// @param result the result value
+    /// @param func the builtin function
+    /// @param args the conversion arguments
+    BuiltinCall(core::ir::InstructionResult* result,
+                BuiltinFn func,
+                VectorRef<core::ir::Value*> args = tint::Empty);
+    ~BuiltinCall() override;
+
+    /// @copydoc core::ir::Instruction::Clone()
+    BuiltinCall* Clone(core::ir::CloneContext& ctx) override;
+
+    /// @returns the builtin function
+    BuiltinFn Func() const { return func_; }
+
+    /// @returns the identifier for the function
+    size_t FuncId() const override { return static_cast<size_t>(func_); }
+
+    /// @returns the friendly name for the instruction
+    std::string FriendlyName() const override { return std::string("hlsl.") + str(func_); }
+
+    /// @returns the table data to validate this builtin
+    const core::intrinsic::TableData& TableData() const override {
+        return hlsl::intrinsic::Dialect::kData;
+    }
+
+  private:
+    BuiltinFn func_;
+};
+
+}  // namespace tint::hlsl::ir
+
+#endif  // SRC_TINT_LANG_HLSL_IR_BUILTIN_CALL_H_
diff --git a/src/tint/lang/hlsl/ir/builtin_call_test.cc b/src/tint/lang/hlsl/ir/builtin_call_test.cc
new file mode 100644
index 0000000..8d6233c
--- /dev/null
+++ b/src/tint/lang/hlsl/ir/builtin_call_test.cc
@@ -0,0 +1,62 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "src/tint/lang/hlsl/ir/builtin_call.h"
+
+#include "gtest/gtest.h"
+#include "src/tint/lang/core/ir/ir_helper_test.h"
+#include "src/tint/lang/core/ir/validator.h"
+
+using namespace tint::core::fluent_types;  // NOLINT
+
+namespace tint::hlsl::ir {
+namespace {
+
+using namespace tint::core::number_suffixes;  // NOLINT
+                                              //
+using IR_HlslBuiltinCallTest = core::ir::IRTestHelper;
+
+TEST_F(IR_HlslBuiltinCallTest, Clone) {
+    auto* builtin = b.Call<BuiltinCall>(mod.Types().u32(), BuiltinFn::kF32Tof16, 0_f);
+
+    auto* new_b = clone_ctx.Clone(builtin);
+
+    EXPECT_NE(builtin, new_b);
+    EXPECT_NE(builtin->Result(0), new_b->Result(0));
+    EXPECT_EQ(mod.Types().u32(), new_b->Result(0)->Type());
+
+    EXPECT_EQ(BuiltinFn::kF32Tof16, new_b->Func());
+
+    auto args = new_b->Args();
+    EXPECT_EQ(1u, args.Length());
+
+    auto* val0 = args[0]->As<core::ir::Constant>()->Value();
+    EXPECT_EQ(0_f, val0->As<core::constant::Scalar<core::f32>>()->ValueAs<core::f32>());
+}
+
+}  // namespace
+}  // namespace tint::hlsl::ir
diff --git a/src/tint/lang/hlsl/writer/bitcast_test.cc b/src/tint/lang/hlsl/writer/bitcast_test.cc
index 14b4cee..81ad93d 100644
--- a/src/tint/lang/hlsl/writer/bitcast_test.cc
+++ b/src/tint/lang/hlsl/writer/bitcast_test.cc
@@ -140,22 +140,22 @@
     });
 
     ASSERT_TRUE(Generate()) << err_ << output_.hlsl;
-    EXPECT_EQ(output_.hlsl, R"(int tint_bitcast_from_f16(vector<float16_t, 2> src) {
+    EXPECT_EQ(output_.hlsl, R"(
+uint tint_bitcast_from_f16_2(vector<float16_t, 2> src) {
   uint2 r = f32tof16(float2(src));
-  return asint(uint((r.x & 0xffff) | ((r.y & 0xffff) << 16)));
+  return ((r.x & 65535u) | ((r.y & 65535u) << 16u));
 }
 
 float tint_bitcast_from_f16_1(vector<float16_t, 2> src) {
   uint2 r = f32tof16(float2(src));
-  return asfloat(uint((r.x & 0xffff) | ((r.y & 0xffff) << 16)));
+  return asfloat(((r.x & 65535u) | ((r.y & 65535u) << 16u)));
 }
 
-uint tint_bitcast_from_f16_2(vector<float16_t, 2> src) {
+int tint_bitcast_from_f16(vector<float16_t, 2> src) {
   uint2 r = f32tof16(float2(src));
-  return asuint(uint((r.x & 0xffff) | ((r.y & 0xffff) << 16)));
+  return asint(((r.x & 65535u) | ((r.y & 65535u) << 16u)));
 }
 
-
 void foo() {
   vector<float16_t, 2> a = vector<float16_t, 2>(float16_t(1.0h), float16_t(2.0h));
   int b = tint_bitcast_from_f16(a);
@@ -181,28 +181,28 @@
     });
 
     ASSERT_TRUE(Generate()) << err_ << output_.hlsl;
-    EXPECT_EQ(output_.hlsl, R"(vector<float16_t, 2> tint_bitcast_to_f16(int src) {
-  uint v = asuint(src);
-  float t_low = f16tof32(v & 0xffff);
-  float t_high = f16tof32((v >> 16) & 0xffff);
+    EXPECT_EQ(output_.hlsl, R"(
+vector<float16_t, 2> tint_bitcast_to_f16_2(uint src) {
+  uint v = src;
+  float t_low = f16tof32((v & 65535u));
+  float t_high = f16tof32(((v >> 16u) & 65535u));
   return vector<float16_t, 2>(t_low.x, t_high.x);
 }
 
 vector<float16_t, 2> tint_bitcast_to_f16_1(float src) {
   uint v = asuint(src);
-  float t_low = f16tof32(v & 0xffff);
-  float t_high = f16tof32((v >> 16) & 0xffff);
+  float t_low = f16tof32((v & 65535u));
+  float t_high = f16tof32(((v >> 16u) & 65535u));
   return vector<float16_t, 2>(t_low.x, t_high.x);
 }
 
-vector<float16_t, 2> tint_bitcast_to_f16_2(uint src) {
+vector<float16_t, 2> tint_bitcast_to_f16(int src) {
   uint v = asuint(src);
-  float t_low = f16tof32(v & 0xffff);
-  float t_high = f16tof32((v >> 16) & 0xffff);
+  float t_low = f16tof32((v & 65535u));
+  float t_high = f16tof32(((v >> 16u) & 65535u));
   return vector<float16_t, 2>(t_low.x, t_high.x);
 }
 
-
 void foo() {
   int a = 1;
   vector<float16_t, 2> b = tint_bitcast_to_f16(a);
@@ -227,22 +227,22 @@
     });
 
     ASSERT_TRUE(Generate()) << err_ << output_.hlsl;
-    EXPECT_EQ(output_.hlsl, R"(int2 tint_bitcast_from_f16(vector<float16_t, 4> src) {
+    EXPECT_EQ(output_.hlsl, R"(
+uint2 tint_bitcast_from_f16_2(vector<float16_t, 4> src) {
   uint4 r = f32tof16(float4(src));
-  return asint(uint2((r.x & 0xffff) | ((r.y & 0xffff) << 16), (r.z & 0xffff) | ((r.w & 0xffff) << 16)));
+  return uint2(((r.x & 65535u) | ((r.y & 65535u) << 16u)), ((r.z & 65535u) | ((r.w & 65535u) << 16u)));
 }
 
 float2 tint_bitcast_from_f16_1(vector<float16_t, 4> src) {
   uint4 r = f32tof16(float4(src));
-  return asfloat(uint2((r.x & 0xffff) | ((r.y & 0xffff) << 16), (r.z & 0xffff) | ((r.w & 0xffff) << 16)));
+  return asfloat(uint2(((r.x & 65535u) | ((r.y & 65535u) << 16u)), ((r.z & 65535u) | ((r.w & 65535u) << 16u))));
 }
 
-uint2 tint_bitcast_from_f16_2(vector<float16_t, 4> src) {
+int2 tint_bitcast_from_f16(vector<float16_t, 4> src) {
   uint4 r = f32tof16(float4(src));
-  return asuint(uint2((r.x & 0xffff) | ((r.y & 0xffff) << 16), (r.z & 0xffff) | ((r.w & 0xffff) << 16)));
+  return asint(uint2(((r.x & 65535u) | ((r.y & 65535u) << 16u)), ((r.z & 65535u) | ((r.w & 65535u) << 16u))));
 }
 
-
 void foo() {
   vector<float16_t, 4> a = vector<float16_t, 4>(float16_t(1.0h), float16_t(2.0h), float16_t(3.0h), float16_t(4.0h));
   int2 b = tint_bitcast_from_f16(a);
@@ -268,28 +268,34 @@
     });
 
     ASSERT_TRUE(Generate()) << err_ << output_.hlsl;
-    EXPECT_EQ(output_.hlsl, R"(vector<float16_t, 4> tint_bitcast_to_f16(int2 src) {
-  uint2 v = asuint(src);
-  float2 t_low = f16tof32(v & 0xffff);
-  float2 t_high = f16tof32((v >> 16) & 0xffff);
+    EXPECT_EQ(output_.hlsl, R"(
+vector<float16_t, 4> tint_bitcast_to_f16_2(uint2 src) {
+  uint2 v = src;
+  uint2 mask = (65535u).xx;
+  uint2 shift = (16u).xx;
+  float2 t_low = f16tof32((v & mask));
+  float2 t_high = f16tof32(((v >> shift) & mask));
   return vector<float16_t, 4>(t_low.x, t_high.x, t_low.y, t_high.y);
 }
 
 vector<float16_t, 4> tint_bitcast_to_f16_1(float2 src) {
   uint2 v = asuint(src);
-  float2 t_low = f16tof32(v & 0xffff);
-  float2 t_high = f16tof32((v >> 16) & 0xffff);
+  uint2 mask = (65535u).xx;
+  uint2 shift = (16u).xx;
+  float2 t_low = f16tof32((v & mask));
+  float2 t_high = f16tof32(((v >> shift) & mask));
   return vector<float16_t, 4>(t_low.x, t_high.x, t_low.y, t_high.y);
 }
 
-vector<float16_t, 4> tint_bitcast_to_f16_2(uint2 src) {
+vector<float16_t, 4> tint_bitcast_to_f16(int2 src) {
   uint2 v = asuint(src);
-  float2 t_low = f16tof32(v & 0xffff);
-  float2 t_high = f16tof32((v >> 16) & 0xffff);
+  uint2 mask = (65535u).xx;
+  uint2 shift = (16u).xx;
+  float2 t_low = f16tof32((v & mask));
+  float2 t_high = f16tof32(((v >> shift) & mask));
   return vector<float16_t, 4>(t_low.x, t_high.x, t_low.y, t_high.y);
 }
 
-
 void foo() {
   int2 a = int2(1, 2);
   vector<float16_t, 4> b = tint_bitcast_to_f16(a);
diff --git a/src/tint/lang/hlsl/writer/printer/BUILD.bazel b/src/tint/lang/hlsl/writer/printer/BUILD.bazel
index abb8a5e..69bea38 100644
--- a/src/tint/lang/hlsl/writer/printer/BUILD.bazel
+++ b/src/tint/lang/hlsl/writer/printer/BUILD.bazel
@@ -51,6 +51,9 @@
     "//src/tint/lang/core/intrinsic",
     "//src/tint/lang/core/ir",
     "//src/tint/lang/core/type",
+    "//src/tint/lang/hlsl",
+    "//src/tint/lang/hlsl/intrinsic",
+    "//src/tint/lang/hlsl/ir",
     "//src/tint/utils/containers",
     "//src/tint/utils/diagnostic",
     "//src/tint/utils/generator",
diff --git a/src/tint/lang/hlsl/writer/printer/BUILD.cmake b/src/tint/lang/hlsl/writer/printer/BUILD.cmake
index 8379d9e..4fcaeb4 100644
--- a/src/tint/lang/hlsl/writer/printer/BUILD.cmake
+++ b/src/tint/lang/hlsl/writer/printer/BUILD.cmake
@@ -50,6 +50,9 @@
   tint_lang_core_intrinsic
   tint_lang_core_ir
   tint_lang_core_type
+  tint_lang_hlsl
+  tint_lang_hlsl_intrinsic
+  tint_lang_hlsl_ir
   tint_utils_containers
   tint_utils_diagnostic
   tint_utils_generator
diff --git a/src/tint/lang/hlsl/writer/printer/BUILD.gn b/src/tint/lang/hlsl/writer/printer/BUILD.gn
index 426f658..eb14752 100644
--- a/src/tint/lang/hlsl/writer/printer/BUILD.gn
+++ b/src/tint/lang/hlsl/writer/printer/BUILD.gn
@@ -50,6 +50,9 @@
     "${tint_src_dir}/lang/core/intrinsic",
     "${tint_src_dir}/lang/core/ir",
     "${tint_src_dir}/lang/core/type",
+    "${tint_src_dir}/lang/hlsl",
+    "${tint_src_dir}/lang/hlsl/intrinsic",
+    "${tint_src_dir}/lang/hlsl/ir",
     "${tint_src_dir}/utils/containers",
     "${tint_src_dir}/utils/diagnostic",
     "${tint_src_dir}/utils/generator",
diff --git a/src/tint/lang/hlsl/writer/printer/printer.cc b/src/tint/lang/hlsl/writer/printer/printer.cc
index e4be041..79847cb 100644
--- a/src/tint/lang/hlsl/writer/printer/printer.cc
+++ b/src/tint/lang/hlsl/writer/printer/printer.cc
@@ -102,13 +102,13 @@
 #include "src/tint/lang/core/type/u32.h"
 #include "src/tint/lang/core/type/vector.h"
 #include "src/tint/lang/core/type/void.h"
+#include "src/tint/lang/hlsl/ir/builtin_call.h"
 #include "src/tint/utils/containers/hashmap.h"
 #include "src/tint/utils/containers/map.h"
 #include "src/tint/utils/generator/text_generator.h"
 #include "src/tint/utils/ice/ice.h"
 #include "src/tint/utils/macros/compiler.h"
 #include "src/tint/utils/macros/scoped_assignment.h"
-#include "src/tint/utils/math/hash.h"
 #include "src/tint/utils/rtti/switch.h"
 #include "src/tint/utils/strconv/float_to_string.h"
 #include "src/tint/utils/text/string.h"
@@ -194,13 +194,6 @@
     /// Block to emit for a continuing
     std::function<void()> emit_continuing_;
 
-    using BinaryType =
-        tint::UnorderedKeyWrapper<std::tuple<const core::type::Type*, const core::type::Type*>>;
-
-    // Polyfill functions for bitcast expression, BinaryType indicates the source type and the
-    // destination type.
-    std::unordered_map<BinaryType, std::string> bitcast_funcs_;
-
     /// Emit the root block.
     /// @param root_block the root block to emit
     void EmitRootBlock(core::ir::Block* root_block) {
@@ -617,7 +610,6 @@
                 Switch(
                     r->Instruction(),                                                          //
                     [&](const core::ir::Access* a) { EmitAccess(out, a); },                    //
-                    [&](const core::ir::Bitcast* b) { EmitBitcast(out, b); },                  //
                     [&](const core::ir::Construct* c) { EmitConstruct(out, c); },              //
                     [&](const core::ir::Convert* c) { EmitConvert(out, c); },                  //
                     [&](const core::ir::CoreBinary* b) { EmitBinary(out, b); },                //
@@ -631,164 +623,28 @@
                     [&](const core::ir::UserCall* c) { EmitUserCall(out, c); },        //
                     [&](const core::ir::Swizzle* s) { EmitSwizzle(out, s); },          //
                     [&](const core::ir::Var* var) { out << NameOf(var->Result(0)); },  //
+
+                    [&](const hlsl::ir::BuiltinCall* c) { EmitHlslBuiltinCall(out, c); },  //
+
                     TINT_ICE_ON_NO_MATCH);
             },
             [&](const core::ir::FunctionParam* p) { out << NameOf(p); },  //
             TINT_ICE_ON_NO_MATCH);
     }
 
-    /// Emit a bitcast instruction
-    void EmitBitcast(StringStream& out, const core::ir::Bitcast* b) {
-        auto* src_type = b->Val()->Type();
-        auto* dst_type = b->Result(0)->Type();
-
-        // Identity transform
-        if (src_type == dst_type) {
-            EmitValue(out, b->Val());
-            return;
+    void EmitHlslBuiltinCall(StringStream& out, const hlsl::ir::BuiltinCall* c) {
+        out << c->Func() << "(";
+        bool needs_comma = false;
+        for (const auto* arg : c->Args()) {
+            if (needs_comma) {
+                out << ", ";
+            }
+            EmitValue(out, arg);
+            needs_comma = true;
         }
-
-        if (src_type->DeepestElement()->As<core::type::F16>()) {
-            out << EmitBitcastFromF16(src_type, dst_type);
-        } else if (dst_type->DeepestElement()->As<core::type::F16>()) {
-            out << EmitBitcastToF16(src_type, dst_type);
-        } else {
-            out << "as";
-            EmitType(out, dst_type);
-        }
-        out << "(";
-        EmitValue(out, b->Val());
         out << ")";
     }
 
-    // Bitcast f16 types to others by converting the given f16 value to f32 and call
-    // f32tof16 to get the bits. This should be safe, because the conversion is precise
-    // for finite and infinite f16 value as they are exactly representable by f32.
-    std::string EmitBitcastFromF16(const core::type::Type* src_type,
-                                   const core::type::Type* dst_type) {
-        return tint::GetOrAdd(
-            bitcast_funcs_, BinaryType{{src_type, dst_type}}, [&]() -> std::string {
-                TextBuffer b;
-                auto fn_name = UniqueIdentifier(std::string("tint_bitcast_from_f16"));
-                {
-                    auto decl = Line(&b);
-                    EmitTypeAndName(decl, dst_type, core::AddressSpace::kUndefined,
-                                    core::Access::kUndefined, fn_name);
-                    {
-                        const ScopedParen sp(decl);
-                        EmitTypeAndName(decl, src_type, core::AddressSpace::kUndefined,
-                                        core::Access::kUndefined, "src");
-                    }
-                    decl << " {";
-                }
-                {
-                    auto* src_vec = src_type->As<core::type::Vector>();
-
-                    const ScopedIndent si(&b);
-                    {
-                        Line(&b) << "uint" << src_vec->Width() << " r = f32tof16(float"
-                                 << src_vec->Width() << "(src));";
-
-                        {
-                            auto* dst_el_type = dst_type->DeepestElement();
-
-                            auto s = Line(&b);
-                            s << "return as";
-                            EmitType(s, dst_el_type, core::AddressSpace::kUndefined,
-                                     core::Access::kReadWrite, "");
-                            s << "(";
-                            switch (src_vec->Width()) {
-                                case 2: {
-                                    s << "uint((r.x & 0xffff) | ((r.y & 0xffff) << 16))";
-                                    break;
-                                }
-                                case 4: {
-                                    s << "uint2((r.x & 0xffff) | ((r.y & 0xffff) << 16), "
-                                         "(r.z & 0xffff) | ((r.w & 0xffff) << 16))";
-                                    break;
-                                }
-                                default: {
-                                    TINT_UNREACHABLE();
-                                }
-                            }
-                            s << ");";
-                        }
-                    }
-                }
-                Line(&b) << "}";
-                Line(&b);
-
-                preamble_buffer_.Append(b);
-                return fn_name;
-            });
-    }
-
-    // Bitcast other types to f16 types by reinterpreting their bits as f16 using
-    // f16tof32, and convert the result f32 to f16. This should be safe, because the
-    // conversion is precise for finite and infinite f16 result value as they are
-    // exactly representable by f32.
-    std::string EmitBitcastToF16(const core::type::Type* src_type,
-                                 const core::type::Type* dst_type) {
-        return tint::GetOrAdd(
-            bitcast_funcs_, BinaryType{{src_type, dst_type}}, [&]() -> std::string {
-                TextBuffer b;
-                auto fn_name = UniqueIdentifier(std::string("tint_bitcast_to_f16"));
-                {
-                    auto decl = Line(&b);
-                    EmitTypeAndName(decl, dst_type, core::AddressSpace::kUndefined,
-                                    core::Access::kUndefined, fn_name);
-                    {
-                        const ScopedParen sp(decl);
-                        EmitTypeAndName(decl, src_type, core::AddressSpace::kUndefined,
-                                        core::Access::kUndefined, "src");
-                    }
-                    decl << " {";
-                }
-                {
-                    const ScopedIndent si(&b);
-                    {
-                        auto* dst_vec = dst_type->As<core::type::Vector>();
-                        auto* src_vec = src_type->As<core::type::Vector>();
-                        const std::string src_type_suffix = (src_vec ? "2" : "");
-
-                        // Convert the source to uint for f16tof32.
-                        Line(&b) << "uint" << src_type_suffix << " v = asuint(src);";
-                        // Reinterpret the low 16 bits and high 16 bits
-                        Line(&b) << "float" << src_type_suffix << " t_low = f16tof32(v & 0xffff);";
-                        Line(&b) << "float" << src_type_suffix
-                                 << " t_high = f16tof32((v >> 16) & 0xffff);";
-                        // Construct the result f16 vector
-                        {
-                            auto s = Line(&b);
-                            s << "return ";
-                            EmitType(s, dst_type, core::AddressSpace::kUndefined,
-                                     core::Access::kReadWrite, "");
-                            s << "(";
-                            switch (dst_vec->Width()) {
-                                case 2: {
-                                    s << "t_low.x, t_high.x";
-                                    break;
-                                }
-                                case 4: {
-                                    s << "t_low.x, t_high.x, t_low.y, t_high.y";
-                                    break;
-                                }
-                                default: {
-                                    TINT_UNREACHABLE();
-                                }
-                            }
-                            s << ");";
-                        }
-                    }
-                }
-                Line(&b) << "}";
-                Line(&b);
-
-                preamble_buffer_.Append(b);
-                return fn_name;
-            });
-    }
-
     /// Emit a convert instruction
     void EmitConvert(StringStream& out, const core::ir::Convert* c) {
         EmitType(out, c->Result(0)->Type());
diff --git a/src/tint/lang/hlsl/writer/raise/BUILD.bazel b/src/tint/lang/hlsl/writer/raise/BUILD.bazel
index f989717..b716ce7 100644
--- a/src/tint/lang/hlsl/writer/raise/BUILD.bazel
+++ b/src/tint/lang/hlsl/writer/raise/BUILD.bazel
@@ -39,10 +39,12 @@
 cc_library(
   name = "raise",
   srcs = [
+    "builtin_polyfill.cc",
     "fxc_polyfill.cc",
     "raise.cc",
   ],
   hdrs = [
+    "builtin_polyfill.h",
     "fxc_polyfill.h",
     "raise.h",
   ],
@@ -54,6 +56,9 @@
     "//src/tint/lang/core/ir",
     "//src/tint/lang/core/ir/transform",
     "//src/tint/lang/core/type",
+    "//src/tint/lang/hlsl",
+    "//src/tint/lang/hlsl/intrinsic",
+    "//src/tint/lang/hlsl/ir",
     "//src/tint/lang/hlsl/writer/common",
     "//src/tint/utils/containers",
     "//src/tint/utils/diagnostic",
@@ -76,6 +81,7 @@
   name = "test",
   alwayslink = True,
   srcs = [
+    "builtin_polyfill_test.cc",
     "fxc_polyfill_test.cc",
   ],
   deps = [
diff --git a/src/tint/lang/hlsl/writer/raise/BUILD.cmake b/src/tint/lang/hlsl/writer/raise/BUILD.cmake
index 47e9ce4..fd8ae66 100644
--- a/src/tint/lang/hlsl/writer/raise/BUILD.cmake
+++ b/src/tint/lang/hlsl/writer/raise/BUILD.cmake
@@ -39,6 +39,8 @@
 # Kind:      lib
 ################################################################################
 tint_add_target(tint_lang_hlsl_writer_raise lib
+  lang/hlsl/writer/raise/builtin_polyfill.cc
+  lang/hlsl/writer/raise/builtin_polyfill.h
   lang/hlsl/writer/raise/fxc_polyfill.cc
   lang/hlsl/writer/raise/fxc_polyfill.h
   lang/hlsl/writer/raise/raise.cc
@@ -53,6 +55,9 @@
   tint_lang_core_ir
   tint_lang_core_ir_transform
   tint_lang_core_type
+  tint_lang_hlsl
+  tint_lang_hlsl_intrinsic
+  tint_lang_hlsl_ir
   tint_lang_hlsl_writer_common
   tint_utils_containers
   tint_utils_diagnostic
@@ -74,6 +79,7 @@
 # Kind:      test
 ################################################################################
 tint_add_target(tint_lang_hlsl_writer_raise_test test
+  lang/hlsl/writer/raise/builtin_polyfill_test.cc
   lang/hlsl/writer/raise/fxc_polyfill_test.cc
 )
 
diff --git a/src/tint/lang/hlsl/writer/raise/BUILD.gn b/src/tint/lang/hlsl/writer/raise/BUILD.gn
index 0d755f2..ff211e5 100644
--- a/src/tint/lang/hlsl/writer/raise/BUILD.gn
+++ b/src/tint/lang/hlsl/writer/raise/BUILD.gn
@@ -44,6 +44,8 @@
 
 libtint_source_set("raise") {
   sources = [
+    "builtin_polyfill.cc",
+    "builtin_polyfill.h",
     "fxc_polyfill.cc",
     "fxc_polyfill.h",
     "raise.cc",
@@ -57,6 +59,9 @@
     "${tint_src_dir}/lang/core/ir",
     "${tint_src_dir}/lang/core/ir/transform",
     "${tint_src_dir}/lang/core/type",
+    "${tint_src_dir}/lang/hlsl",
+    "${tint_src_dir}/lang/hlsl/intrinsic",
+    "${tint_src_dir}/lang/hlsl/ir",
     "${tint_src_dir}/lang/hlsl/writer/common",
     "${tint_src_dir}/utils/containers",
     "${tint_src_dir}/utils/diagnostic",
@@ -75,7 +80,10 @@
 }
 if (tint_build_unittests) {
   tint_unittests_source_set("unittests") {
-    sources = [ "fxc_polyfill_test.cc" ]
+    sources = [
+      "builtin_polyfill_test.cc",
+      "fxc_polyfill_test.cc",
+    ]
     deps = [
       "${tint_src_dir}:gmock_and_gtest",
       "${tint_src_dir}/api/common",
diff --git a/src/tint/lang/hlsl/writer/raise/builtin_polyfill.cc b/src/tint/lang/hlsl/writer/raise/builtin_polyfill.cc
new file mode 100644
index 0000000..eddb32c
--- /dev/null
+++ b/src/tint/lang/hlsl/writer/raise/builtin_polyfill.cc
@@ -0,0 +1,312 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "src/tint/lang/hlsl/writer/raise/builtin_polyfill.h"
+
+#include <string>
+#include <tuple>
+
+#include "src/tint/lang/core/fluent_types.h"  // IWYU pragma: export
+#include "src/tint/lang/core/ir/builder.h"
+#include "src/tint/lang/core/ir/module.h"
+#include "src/tint/lang/core/ir/validator.h"
+#include "src/tint/lang/core/type/manager.h"
+#include "src/tint/lang/hlsl/builtin_fn.h"
+#include "src/tint/lang/hlsl/ir/builtin_call.h"
+#include "src/tint/utils/containers/hashmap.h"
+#include "src/tint/utils/math/hash.h"
+
+namespace tint::hlsl::writer::raise {
+namespace {
+
+using namespace tint::core::fluent_types;     // NOLINT
+using namespace tint::core::number_suffixes;  // NOLINT
+
+/// PIMPL state for the transform.
+struct State {
+    /// The IR module.
+    core::ir::Module& ir;
+
+    /// The IR builder.
+    core::ir::Builder b{ir};
+
+    /// The type manager.
+    core::type::Manager& ty{ir.Types()};
+
+    using BinaryType =
+        tint::UnorderedKeyWrapper<std::tuple<const core::type::Type*, const core::type::Type*>>;
+
+    // Polyfill functions for bitcast expression, BinaryType indicates the source type and the
+    // destination type.
+    Hashmap<BinaryType, core::ir::Function*, 4> bitcast_funcs_{};
+
+    /// Process the module.
+    void Process() {
+        // Find the bitcasts that need replacing.
+        Vector<core::ir::Bitcast*, 4> bitcast_worklist;
+        for (auto* inst : ir.Instructions()) {
+            if (auto* bitcast = inst->As<core::ir::Bitcast>()) {
+                bitcast_worklist.Push(bitcast);
+            }
+        }
+
+        // Replace the bitcasts that we found.
+        for (auto* bitcast : bitcast_worklist) {
+            auto* src_type = bitcast->Val()->Type();
+            auto* dst_type = bitcast->Result(0)->Type();
+            auto* dst_deepest = dst_type->DeepestElement();
+
+            if (src_type == dst_type) {
+                ReplaceBitcastWithValue(bitcast);
+            } else if (src_type->DeepestElement()->Is<core::type::F16>()) {
+                ReplaceBitcastWithFromF16Polyfill(bitcast);
+            } else if (dst_deepest->Is<core::type::F16>()) {
+                ReplaceBitcastWithToF16Polyfill(bitcast);
+            } else {
+                ReplaceBitcastWithAs(bitcast);
+            }
+        }
+    }
+
+    /// Replaces an identity bitcast result with the value.
+    void ReplaceBitcastWithValue(core::ir::Bitcast* bitcast) {
+        bitcast->Result(0)->ReplaceAllUsesWith(bitcast->Val());
+        bitcast->Destroy();
+    }
+
+    void ReplaceBitcastWithAs(core::ir::Bitcast* bitcast) {
+        auto* dst_type = bitcast->Result(0)->Type();
+        auto* dst_deepest = dst_type->DeepestElement();
+
+        BuiltinFn fn = BuiltinFn::kNone;
+        tint::Switch(
+            dst_deepest,                                                //
+            [&](const core::type::I32*) { fn = BuiltinFn::kAsint; },    //
+            [&](const core::type::U32*) { fn = BuiltinFn::kAsuint; },   //
+            [&](const core::type::F32*) { fn = BuiltinFn::kAsfloat; },  //
+            TINT_ICE_ON_NO_MATCH);
+
+        b.InsertBefore(bitcast, [&] {
+            b.CallWithResult<hlsl::ir::BuiltinCall>(bitcast->DetachResult(), fn, bitcast->Val());
+        });
+        bitcast->Destroy();
+    }
+
+    // Bitcast f16 types to others by converting the given f16 value to f32 and call
+    // f32tof16 to get the bits. This should be safe, because the conversion is precise
+    // for finite and infinite f16 value as they are exactly representable by f32.
+    core::ir::Function* CreateBitcastFromF16(const core::type::Type* src_type,
+                                             const core::type::Type* dst_type) {
+        return bitcast_funcs_.GetOrAdd(
+            BinaryType{{src_type, dst_type}}, [&]() -> core::ir::Function* {
+                TINT_ASSERT(src_type->Is<core::type::Vector>());
+
+                // Generate a helper function that performs the following (in HLSL):
+                //
+                // uint tint_bitcast_from_f16(vector<float16_t, 2> src) {
+                //   uint2 r = f32tof16(float2(src));
+                //   return uint((r.x & 65535u) | ((r.y & 65535u) << 16u));
+                // }
+
+                auto fn_name = b.ir.symbols.New(std::string("tint_bitcast_from_f16")).Name();
+
+                auto* f = b.Function(fn_name, dst_type);
+                auto* src = b.FunctionParam("src", src_type);
+                f->SetParams({src});
+
+                b.Append(f->Block(), [&] {
+                    auto* src_vec = src_type->As<core::type::Vector>();
+
+                    auto* cast = b.Convert(ty.vec(ty.f32(), src_vec->Width()), src);
+                    auto* r =
+                        b.Let("r", b.Call<hlsl::ir::BuiltinCall>(ty.vec(ty.u32(), src_vec->Width()),
+                                                                 hlsl::BuiltinFn::kF32Tof16, cast));
+
+                    auto* x = b.And(ty.u32(), b.Swizzle(ty.u32(), r, {0_u}), 0xffff_u);
+                    auto* y = b.ShiftLeft(
+                        ty.u32(), b.And(ty.u32(), b.Swizzle(ty.u32(), r, {1_u}), 0xffff_u), 16_u);
+
+                    auto* s = b.Or(ty.u32(), x, y);
+                    core::ir::InstructionResult* result = nullptr;
+
+                    switch (src_vec->Width()) {
+                        case 2: {
+                            result = s->Result(0);
+                            break;
+                        }
+                        case 4: {
+                            auto* z = b.And(ty.u32(), b.Swizzle(ty.u32(), r, {2_u}), 0xffff_u);
+                            auto* w = b.ShiftLeft(
+                                ty.u32(), b.And(ty.u32(), b.Swizzle(ty.u32(), r, {3_u}), 0xffff_u),
+                                16_u);
+
+                            auto* t = b.Or(ty.u32(), z, w);
+                            auto* cons = b.Construct(ty.vec2<u32>(), s, t);
+                            result = cons->Result(0);
+                            break;
+                        }
+                        default:
+                            TINT_UNREACHABLE();
+                    }
+
+                    tint::Switch(
+                        dst_type->DeepestElement(),  //
+                        [&](const core::type::F32*) {
+                            b.Return(f, b.Call<hlsl::ir::BuiltinCall>(dst_type, BuiltinFn::kAsfloat,
+                                                                      result));
+                        },
+                        [&](const core::type::I32*) {
+                            b.Return(f, b.Call<hlsl::ir::BuiltinCall>(dst_type, BuiltinFn::kAsint,
+                                                                      result));
+                        },
+                        [&](const core::type::U32*) { b.Return(f, result); },  //
+                        TINT_ICE_ON_NO_MATCH);
+                });
+                return f;
+            });
+    }
+
+    /// Replaces a bitcast with a call to the FromF16 polyfill for the given types
+    void ReplaceBitcastWithFromF16Polyfill(core::ir::Bitcast* bitcast) {
+        auto* src_type = bitcast->Val()->Type();
+        auto* dst_type = bitcast->Result(0)->Type();
+
+        auto* f = CreateBitcastFromF16(src_type, dst_type);
+        b.InsertBefore(bitcast,
+                       [&] { b.CallWithResult(bitcast->DetachResult(), f, bitcast->Args()[0]); });
+        bitcast->Destroy();
+    }
+
+    // Bitcast other types to f16 types by reinterpreting their bits as f16 using
+    // f16tof32, and convert the result f32 to f16. This should be safe, because the
+    // conversion is precise for finite and infinite f16 result value as they are
+    // exactly representable by f32.
+    core::ir::Function* CreateBitcastToF16(const core::type::Type* src_type,
+                                           const core::type::Type* dst_type) {
+        return bitcast_funcs_.GetOrAdd(
+            BinaryType{{src_type, dst_type}}, [&]() -> core::ir::Function* {
+                TINT_ASSERT(dst_type->Is<core::type::Vector>());
+
+                // Generate a helper function that performs the following (in HLSL):
+                //
+                // vector<float16_t, 2> tint_bitcast_to_f16(float src) {
+                //   uint v = asuint(src);
+                //   float t_low = f16tof32(v & 65535u);
+                //   float t_high = f16tof32((v >> 16u) & 65535u);
+                //   return vector<float16_t, 2>(t_low.x, t_high.x);
+                // }
+
+                auto fn_name = b.ir.symbols.New(std::string("tint_bitcast_to_f16")).Name();
+
+                auto* f = b.Function(fn_name, dst_type);
+                auto* src = b.FunctionParam("src", src_type);
+                f->SetParams({src});
+                b.Append(f->Block(), [&] {
+                    const core::type::Type* uint_ty = nullptr;
+                    const core::type::Type* float_ty = nullptr;
+
+                    auto* src_vec = src_type->As<core::type::Vector>();
+                    if (src_vec) {
+                        uint_ty = ty.vec(ty.u32(), src_vec->Width());
+                        float_ty = ty.vec(ty.f32(), src_vec->Width());
+                    } else {
+                        uint_ty = ty.u32();
+                        float_ty = ty.f32();
+                    }
+
+                    core::ir::Instruction* v = nullptr;
+                    tint::Switch(
+                        src_type->DeepestElement(),                            //
+                        [&](const core::type::U32*) { v = b.Let("v", src); },  //
+                        [&](const core::type::I32*) {
+                            v = b.Let("v", b.Call<hlsl::ir::BuiltinCall>(uint_ty,
+                                                                         BuiltinFn::kAsuint, src));
+                        },
+                        [&](const core::type::F32*) {
+                            v = b.Let("v", b.Call<hlsl::ir::BuiltinCall>(uint_ty,
+                                                                         BuiltinFn::kAsuint, src));
+                        },
+                        TINT_ICE_ON_NO_MATCH);
+
+                    core::ir::Value* mask = nullptr;
+                    core::ir::Value* shift = nullptr;
+                    if (src_vec) {
+                        mask = b.Let("mask", b.Splat(uint_ty, 0xffff_u))->Result(0);
+                        shift = b.Let("shift", b.Splat(uint_ty, 16_u))->Result(0);
+                    } else {
+                        mask = b.Value(b.Constant(0xffff_u));
+                        shift = b.Value(b.Constant(16_u));
+                    }
+
+                    auto* l = b.And(uint_ty, v, mask);
+                    auto* t_low = b.Let(
+                        "t_low", b.Call<hlsl::ir::BuiltinCall>(float_ty, BuiltinFn::kF16Tof32, l));
+
+                    auto* h = b.And(uint_ty, b.ShiftRight(uint_ty, v, shift), mask);
+                    auto* t_high = b.Let(
+                        "t_high", b.Call<hlsl::ir::BuiltinCall>(float_ty, BuiltinFn::kF16Tof32, h));
+
+                    auto* x = b.Swizzle(ty.f16(), t_low, {0_u});
+                    auto* y = b.Swizzle(ty.f16(), t_high, {0_u});
+                    if (dst_type->As<core::type::Vector>()->Width() == 2) {
+                        b.Return(f, b.Construct(dst_type, x, y));
+                    } else {
+                        auto* z = b.Swizzle(ty.f16(), t_low, {1_u});
+                        auto* w = b.Swizzle(ty.f16(), t_high, {1_u});
+                        b.Return(f, b.Construct(dst_type, x, y, z, w));
+                    }
+                });
+                return f;
+            });
+    }
+
+    /// Replaces a bitcast with a call to the ToF16 polyfill for the given types
+    void ReplaceBitcastWithToF16Polyfill(core::ir::Bitcast* bitcast) {
+        auto* src_type = bitcast->Val()->Type();
+        auto* dst_type = bitcast->Result(0)->Type();
+
+        auto* f = CreateBitcastToF16(src_type, dst_type);
+        b.InsertBefore(bitcast,
+                       [&] { b.CallWithResult(bitcast->DetachResult(), f, bitcast->Args()[0]); });
+        bitcast->Destroy();
+    }
+};
+
+}  // namespace
+
+Result<SuccessType> BuiltinPolyfill(core::ir::Module& ir) {
+    auto result = ValidateAndDumpIfNeeded(ir, "BuiltinPolyfill transform");
+    if (result != Success) {
+        return result.Failure();
+    }
+
+    State{ir}.Process();
+
+    return Success;
+}
+
+}  // namespace tint::hlsl::writer::raise
diff --git a/src/tint/lang/hlsl/writer/raise/builtin_polyfill.h b/src/tint/lang/hlsl/writer/raise/builtin_polyfill.h
new file mode 100644
index 0000000..c161f05
--- /dev/null
+++ b/src/tint/lang/hlsl/writer/raise/builtin_polyfill.h
@@ -0,0 +1,49 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef SRC_TINT_LANG_HLSL_WRITER_RAISE_BUILTIN_POLYFILL_H_
+#define SRC_TINT_LANG_HLSL_WRITER_RAISE_BUILTIN_POLYFILL_H_
+
+#include "src/tint/utils/result/result.h"
+
+// Forward declarations.
+namespace tint::core::ir {
+class Module;
+class Texture;
+}  // namespace tint::core::ir
+
+namespace tint::hlsl::writer::raise {
+
+/// BuiltinPolyfill is a transform that replaces calls to builtins with polyfills and calls to
+/// HLSL polyfilled or backend intrinsic functions.
+/// @param module the module to transform
+/// @returns success or failure
+Result<SuccessType> BuiltinPolyfill(core::ir::Module& module);
+
+}  // namespace tint::hlsl::writer::raise
+
+#endif  // SRC_TINT_LANG_HLSL_WRITER_RAISE_BUILTIN_POLYFILL_H_
diff --git a/src/tint/lang/hlsl/writer/raise/builtin_polyfill_test.cc b/src/tint/lang/hlsl/writer/raise/builtin_polyfill_test.cc
new file mode 100644
index 0000000..f9545d6
--- /dev/null
+++ b/src/tint/lang/hlsl/writer/raise/builtin_polyfill_test.cc
@@ -0,0 +1,395 @@
+// Copyright 2023 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "src/tint/lang/hlsl/writer/raise/builtin_polyfill.h"
+
+#include "gtest/gtest.h"
+#include "src/tint/lang/core/fluent_types.h"
+#include "src/tint/lang/core/ir/transform/helper_test.h"
+#include "src/tint/lang/core/number.h"
+#include "src/tint/lang/core/type/builtin_structs.h"
+#include "src/tint/lang/core/type/storage_texture.h"
+
+using namespace tint::core::fluent_types;     // NOLINT
+using namespace tint::core::number_suffixes;  // NOLINT
+
+namespace tint::hlsl::writer::raise {
+namespace {
+
+using HlslWriter_BuiltinPolyfillTest = core::ir::transform::TransformTest;
+
+TEST_F(HlslWriter_BuiltinPolyfillTest, BitcastIdentity) {
+    auto* a = b.FunctionParam<i32>("a");
+    auto* func = b.Function("foo", ty.i32());
+    func->SetParams({a});
+    b.Append(func->Block(), [&] { b.Return(func, b.Bitcast<i32>(a)); });
+
+    auto* src = R"(
+%foo = func(%a:i32):i32 {
+  $B1: {
+    %3:i32 = bitcast %a
+    ret %3
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+%foo = func(%a:i32):i32 {
+  $B1: {
+    ret %a
+  }
+}
+)";
+
+    Run(BuiltinPolyfill);
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(HlslWriter_BuiltinPolyfillTest, Asuint) {
+    auto* a = b.FunctionParam<i32>("a");
+    auto* func = b.Function("foo", ty.u32());
+    func->SetParams({a});
+    b.Append(func->Block(), [&] { b.Return(func, b.Bitcast<u32>(a)); });
+
+    auto* src = R"(
+%foo = func(%a:i32):u32 {
+  $B1: {
+    %3:u32 = bitcast %a
+    ret %3
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+%foo = func(%a:i32):u32 {
+  $B1: {
+    %3:u32 = hlsl.asuint %a
+    ret %3
+  }
+}
+)";
+
+    Run(BuiltinPolyfill);
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(HlslWriter_BuiltinPolyfillTest, Asint) {
+    auto* a = b.FunctionParam<u32>("a");
+    auto* func = b.Function("foo", ty.i32());
+    func->SetParams({a});
+    b.Append(func->Block(), [&] { b.Return(func, b.Bitcast<i32>(a)); });
+
+    auto* src = R"(
+%foo = func(%a:u32):i32 {
+  $B1: {
+    %3:i32 = bitcast %a
+    ret %3
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+%foo = func(%a:u32):i32 {
+  $B1: {
+    %3:i32 = hlsl.asint %a
+    ret %3
+  }
+}
+)";
+
+    Run(BuiltinPolyfill);
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(HlslWriter_BuiltinPolyfillTest, Asfloat) {
+    auto* a = b.FunctionParam<i32>("a");
+    auto* func = b.Function("foo", ty.f32());
+    func->SetParams({a});
+    b.Append(func->Block(), [&] { b.Return(func, b.Bitcast<f32>(a)); });
+
+    auto* src = R"(
+%foo = func(%a:i32):f32 {
+  $B1: {
+    %3:f32 = bitcast %a
+    ret %3
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+%foo = func(%a:i32):f32 {
+  $B1: {
+    %3:f32 = hlsl.asfloat %a
+    ret %3
+  }
+}
+)";
+
+    Run(BuiltinPolyfill);
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(HlslWriter_BuiltinPolyfillTest, AsfloatVec) {
+    auto* a = b.FunctionParam<vec3<i32>>("a");
+    auto* func = b.Function("foo", ty.vec<f32, 3>());
+    func->SetParams({a});
+    b.Append(func->Block(), [&] { b.Return(func, b.Bitcast(ty.vec(ty.f32(), 3), a)); });
+
+    auto* src = R"(
+%foo = func(%a:vec3<i32>):vec3<f32> {
+  $B1: {
+    %3:vec3<f32> = bitcast %a
+    ret %3
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+%foo = func(%a:vec3<i32>):vec3<f32> {
+  $B1: {
+    %3:vec3<f32> = hlsl.asfloat %a
+    ret %3
+  }
+}
+)";
+
+    Run(BuiltinPolyfill);
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(HlslWriter_BuiltinPolyfillTest, BitcastFromF16) {
+    auto* a = b.FunctionParam<vec2<f16>>("a");
+    auto* func = b.Function("foo", ty.f32());
+    func->SetParams({a});
+    b.Append(func->Block(), [&] { b.Return(func, b.Bitcast(ty.f32(), a)); });
+
+    auto* src = R"(
+%foo = func(%a:vec2<f16>):f32 {
+  $B1: {
+    %3:f32 = bitcast %a
+    ret %3
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+%foo = func(%a:vec2<f16>):f32 {
+  $B1: {
+    %3:f32 = call %tint_bitcast_from_f16, %a
+    ret %3
+  }
+}
+%tint_bitcast_from_f16 = func(%src:vec2<f16>):f32 {
+  $B2: {
+    %6:vec2<f32> = convert %src
+    %7:vec2<u32> = hlsl.f32tof16 %6
+    %r:vec2<u32> = let %7
+    %9:u32 = swizzle %r, x
+    %10:u32 = and %9, 65535u
+    %11:u32 = swizzle %r, y
+    %12:u32 = and %11, 65535u
+    %13:u32 = shl %12, 16u
+    %14:u32 = or %10, %13
+    %15:f32 = hlsl.asfloat %14
+    ret %15
+  }
+}
+)";
+
+    Run(BuiltinPolyfill);
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(HlslWriter_BuiltinPolyfillTest, BitcastToF16) {
+    auto* a = b.FunctionParam<f32>("a");
+    auto* func = b.Function("foo", ty.vec2<f16>());
+    func->SetParams({a});
+    b.Append(func->Block(), [&] { b.Return(func, b.Bitcast(ty.vec2<f16>(), a)); });
+
+    auto* src = R"(
+%foo = func(%a:f32):vec2<f16> {
+  $B1: {
+    %3:vec2<f16> = bitcast %a
+    ret %3
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+%foo = func(%a:f32):vec2<f16> {
+  $B1: {
+    %3:vec2<f16> = call %tint_bitcast_to_f16, %a
+    ret %3
+  }
+}
+%tint_bitcast_to_f16 = func(%src:f32):vec2<f16> {
+  $B2: {
+    %6:u32 = hlsl.asuint %src
+    %v:u32 = let %6
+    %8:u32 = and %v, 65535u
+    %9:f32 = hlsl.f16tof32 %8
+    %t_low:f32 = let %9
+    %11:u32 = shr %v, 16u
+    %12:u32 = and %11, 65535u
+    %13:f32 = hlsl.f16tof32 %12
+    %t_high:f32 = let %13
+    %15:f16 = swizzle %t_low, x
+    %16:f16 = swizzle %t_high, x
+    %17:vec2<f16> = construct %15, %16
+    ret %17
+  }
+}
+)";
+
+    Run(BuiltinPolyfill);
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(HlslWriter_BuiltinPolyfillTest, BitcastFromVec2F16) {
+    auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
+    b.Append(func->Block(), [&] {
+        auto* a = b.Var("a", b.Construct<vec2<f16>>(1_h, 2_h));
+        auto* z = b.Load(a);
+        b.Let("b", b.Bitcast<i32>(z));
+        b.Return(func);
+    });
+
+    auto* src = R"(
+%foo = @fragment func():void {
+  $B1: {
+    %2:vec2<f16> = construct 1.0h, 2.0h
+    %a:ptr<function, vec2<f16>, read_write> = var, %2
+    %4:vec2<f16> = load %a
+    %5:i32 = bitcast %4
+    %b:i32 = let %5
+    ret
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+%foo = @fragment func():void {
+  $B1: {
+    %2:vec2<f16> = construct 1.0h, 2.0h
+    %a:ptr<function, vec2<f16>, read_write> = var, %2
+    %4:vec2<f16> = load %a
+    %5:i32 = call %tint_bitcast_from_f16, %4
+    %b:i32 = let %5
+    ret
+  }
+}
+%tint_bitcast_from_f16 = func(%src:vec2<f16>):i32 {
+  $B2: {
+    %9:vec2<f32> = convert %src
+    %10:vec2<u32> = hlsl.f32tof16 %9
+    %r:vec2<u32> = let %10
+    %12:u32 = swizzle %r, x
+    %13:u32 = and %12, 65535u
+    %14:u32 = swizzle %r, y
+    %15:u32 = and %14, 65535u
+    %16:u32 = shl %15, 16u
+    %17:u32 = or %13, %16
+    %18:i32 = hlsl.asint %17
+    ret %18
+  }
+}
+)";
+
+    Run(BuiltinPolyfill);
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(HlslWriter_BuiltinPolyfillTest, BitcastToVec4F16) {
+    auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
+    b.Append(func->Block(), [&] {
+        auto* a = b.Var("a", b.Construct<vec2<i32>>(1_i, 2_i));
+        b.Let("b", b.Bitcast<vec4<f16>>(b.Load(a)));
+        b.Return(func);
+    });
+
+    auto* src = R"(
+%foo = @fragment func():void {
+  $B1: {
+    %2:vec2<i32> = construct 1i, 2i
+    %a:ptr<function, vec2<i32>, read_write> = var, %2
+    %4:vec2<i32> = load %a
+    %5:vec4<f16> = bitcast %4
+    %b:vec4<f16> = let %5
+    ret
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+%foo = @fragment func():void {
+  $B1: {
+    %2:vec2<i32> = construct 1i, 2i
+    %a:ptr<function, vec2<i32>, read_write> = var, %2
+    %4:vec2<i32> = load %a
+    %5:vec4<f16> = call %tint_bitcast_to_f16, %4
+    %b:vec4<f16> = let %5
+    ret
+  }
+}
+%tint_bitcast_to_f16 = func(%src:vec2<i32>):vec4<f16> {
+  $B2: {
+    %9:vec2<u32> = hlsl.asuint %src
+    %v:vec2<u32> = let %9
+    %mask:vec2<u32> = let vec2<u32>(65535u)
+    %shift:vec2<u32> = let vec2<u32>(16u)
+    %13:vec2<u32> = and %v, %mask
+    %14:vec2<f32> = hlsl.f16tof32 %13
+    %t_low:vec2<f32> = let %14
+    %16:vec2<u32> = shr %v, %shift
+    %17:vec2<u32> = and %16, %mask
+    %18:vec2<f32> = hlsl.f16tof32 %17
+    %t_high:vec2<f32> = let %18
+    %20:f16 = swizzle %t_low, x
+    %21:f16 = swizzle %t_high, x
+    %22:f16 = swizzle %t_low, y
+    %23:f16 = swizzle %t_high, y
+    %24:vec4<f16> = construct %20, %21, %22, %23
+    ret %24
+  }
+}
+)";
+
+    Run(BuiltinPolyfill);
+    EXPECT_EQ(expect, str());
+}
+
+}  // namespace
+}  // namespace tint::hlsl::writer::raise
diff --git a/src/tint/lang/hlsl/writer/raise/raise.cc b/src/tint/lang/hlsl/writer/raise/raise.cc
index da40afa..a9f0b33 100644
--- a/src/tint/lang/hlsl/writer/raise/raise.cc
+++ b/src/tint/lang/hlsl/writer/raise/raise.cc
@@ -30,6 +30,7 @@
 #include "src/tint/lang/core/ir/transform/add_empty_entry_point.h"
 #include "src/tint/lang/core/ir/transform/remove_terminator_args.h"
 #include "src/tint/lang/hlsl/writer/common/options.h"
+#include "src/tint/lang/hlsl/writer/raise/builtin_polyfill.h"
 #include "src/tint/lang/hlsl/writer/raise/fxc_polyfill.h"
 #include "src/tint/utils/result/result.h"
 
@@ -50,6 +51,8 @@
         RUN_TRANSFORM(raise::FxcPolyfill);
     }
 
+    RUN_TRANSFORM(raise::BuiltinPolyfill);
+
     // These transforms need to be run last as various transforms introduce terminator arguments,
     // naming conflicts, and expressions that need to be explicitly not inlined.
     RUN_TRANSFORM(core::ir::transform::RemoveTerminatorArgs);