Import Tint changes from Dawn

Changes:
  - ab91b3d551d93cc8db05bd4c4cf39ac8e25a2312 Move GL to use BindingRemapperOptions by dan sinclair <dsinclair@chromium.org>
  - 506fb552221652b89439451a377e470d2c415e57 [tint] Remove unnecessary calls to diag::List::str() by Ben Clayton <bclayton@google.com>
  - f50ae689bedf5d96a7fca9229ec33078d048b0b7 [tint] Replace ASSERT_TRUE() in lambda with ADD_FAILURE by Ben Clayton <bclayton@google.com>
  - 5ed5cc4e968b05bbe63452bf420007a76d123437 [tint] Pass Program by reference, not pointer. by Ben Clayton <bclayton@google.com>
  - 695020840edea14c01dceced2c44d5799f559769 [ir] Move image_write intrinsic to builtin for SPIR-V by dan sinclair <dsinclair@chromium.org>
  - 86d4f3bc4c0faf8736d71ac31b9d9211aa2e0a2b [ir] Move image sample intrinsics to builtins for SPIR-V by dan sinclair <dsinclair@chromium.org>
  - 39aef37759a479717fd40edb1069f230d15236bf [ir] Comment MultiplanarExternalTexture properly by James Price <jrprice@google.com>
  - 0a7aa0c59b9df07abb5982981fe199b9e327a12d [tint] Rename core::Builtin to BuiltinType by Ben Clayton <bclayton@google.com>
  - 9dce1d624a7bda149dfe94f31e0272fd91daa936 [tint] Rename spirv::ir::Function to spirv::BuiltinFn by Ben Clayton <bclayton@google.com>
  - d9766dca1514f240f150a5f58433aa815ed3e182 [tint] Rename core::Function to BuiltinFn by Ben Clayton <bclayton@google.com>
  - 62d9848ecc4e59ebc32d2b4555a32f250419cbd0 [tint] Move bits of core.def to common.def by Ben Clayton <bclayton@google.com>
  - 16cb4bd7dd169b5e6152ab28505ed9ebdf85a5b0 [ir] Add ability to clone in the IR. by dan sinclair <dsinclair@chromium.org>
  - eea786fd652c79be1557e946dc93771bf536da32 hlsl-writer: support subgroupBroadcast by David Neto <dneto@google.com>
GitOrigin-RevId: ab91b3d551d93cc8db05bd4c4cf39ac8e25a2312
Change-Id: I4cdf4f2718f4f6a14bd6bb9519c467b47d5d0b88
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/153001
Commit-Queue: Ben Clayton <bclayton@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/src/tint/api/tint.cc b/src/tint/api/tint.cc
index e6a8d2a..3cb2ebb 100644
--- a/src/tint/api/tint.cc
+++ b/src/tint/api/tint.cc
@@ -55,7 +55,7 @@
 void Initialize() {
 #if TINT_BUILD_WGSL_WRITER
     // Register the Program printer. This is used for debugging purposes.
-    tint::Program::printer = [](const tint::Program* program) {
+    tint::Program::printer = [](const tint::Program& program) {
         auto result = wgsl::writer::Generate(program, {});
         if (!result) {
             return "error: " + result.Failure();
diff --git a/src/tint/cmd/bench/main_bench.cc b/src/tint/cmd/bench/main_bench.cc
index eaf8546..3ee05d3 100644
--- a/src/tint/cmd/bench/main_bench.cc
+++ b/src/tint/cmd/bench/main_bench.cc
@@ -107,7 +107,7 @@
             if (!program.IsValid()) {
                 return Error{program.Diagnostics().str()};
             }
-            auto result = tint::wgsl::writer::Generate(&program, {});
+            auto result = tint::wgsl::writer::Generate(program, {});
             if (!result) {
                 return Error{result.Failure()};
             }
diff --git a/src/tint/cmd/common/generate_external_texture_bindings.cc b/src/tint/cmd/common/generate_external_texture_bindings.cc
index 0253b71..afa4e5a 100644
--- a/src/tint/cmd/common/generate_external_texture_bindings.cc
+++ b/src/tint/cmd/common/generate_external_texture_bindings.cc
@@ -26,15 +26,15 @@
 
 namespace tint::cmd {
 
-ExternalTextureOptions::BindingsMap GenerateExternalTextureBindings(const Program* program) {
+ExternalTextureOptions::BindingsMap GenerateExternalTextureBindings(const Program& program) {
     // TODO(tint:1491): Use Inspector once we can get binding info for all
     // variables, not just those referenced by entry points.
 
     // Collect next valid binding number per group
     std::unordered_map<uint32_t, uint32_t> group_to_next_binding_number;
     std::vector<tint::BindingPoint> ext_tex_bps;
-    for (auto* var : program->AST().GlobalVariables()) {
-        if (auto* sem_var = program->Sem().Get(var)->As<sem::GlobalVariable>()) {
+    for (auto* var : program.AST().GlobalVariables()) {
+        if (auto* sem_var = program.Sem().Get(var)->As<sem::GlobalVariable>()) {
             if (auto bp = sem_var->BindingPoint()) {
                 auto& n = group_to_next_binding_number[bp->group];
                 n = std::max(n, bp->binding + 1);
diff --git a/src/tint/cmd/common/generate_external_texture_bindings.h b/src/tint/cmd/common/generate_external_texture_bindings.h
index 7c3d79c..edfb68b 100644
--- a/src/tint/cmd/common/generate_external_texture_bindings.h
+++ b/src/tint/cmd/common/generate_external_texture_bindings.h
@@ -24,7 +24,7 @@
 
 namespace tint::cmd {
 
-ExternalTextureOptions::BindingsMap GenerateExternalTextureBindings(const Program* program);
+ExternalTextureOptions::BindingsMap GenerateExternalTextureBindings(const Program& program);
 
 }  // namespace tint::cmd
 
diff --git a/src/tint/cmd/common/generate_external_texture_bindings_test.cc b/src/tint/cmd/common/generate_external_texture_bindings_test.cc
index c404294..a020927 100644
--- a/src/tint/cmd/common/generate_external_texture_bindings_test.cc
+++ b/src/tint/cmd/common/generate_external_texture_bindings_test.cc
@@ -34,7 +34,7 @@
 
     tint::Program program(resolver::Resolve(b));
     ASSERT_TRUE(program.IsValid());
-    auto bindings = GenerateExternalTextureBindings(&program);
+    auto bindings = GenerateExternalTextureBindings(program);
     ASSERT_TRUE(bindings.empty());
 }
 
@@ -44,7 +44,7 @@
 
     tint::Program program(resolver::Resolve(b));
     ASSERT_TRUE(program.IsValid());
-    auto bindings = GenerateExternalTextureBindings(&program);
+    auto bindings = GenerateExternalTextureBindings(program);
     ASSERT_EQ(bindings.size(), 1u);
 
     auto to = bindings[BindingPoint{0, 0}];
@@ -61,7 +61,7 @@
 
     tint::Program program(resolver::Resolve(b));
     ASSERT_TRUE(program.IsValid());
-    auto bindings = GenerateExternalTextureBindings(&program);
+    auto bindings = GenerateExternalTextureBindings(program);
     ASSERT_EQ(bindings.size(), 2u);
 
     auto to0 = bindings[BindingPoint{0, 0}];
@@ -84,7 +84,7 @@
 
     tint::Program program(resolver::Resolve(b));
     ASSERT_TRUE(program.IsValid());
-    auto bindings = GenerateExternalTextureBindings(&program);
+    auto bindings = GenerateExternalTextureBindings(program);
     ASSERT_EQ(bindings.size(), 2u);
 
     auto to0 = bindings[BindingPoint{0, 0}];
@@ -109,8 +109,8 @@
     b.GlobalVar("v4", b.ty.i32(), b.Group(0_a), b.Binding(4_a), kUniform);
 
     tint::Program program(resolver::Resolve(b));
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
-    auto bindings = GenerateExternalTextureBindings(&program);
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
+    auto bindings = GenerateExternalTextureBindings(program);
     ASSERT_EQ(bindings.size(), 2u);
 
     auto to0 = bindings[BindingPoint{0, 1}];
diff --git a/src/tint/cmd/common/helper.cc b/src/tint/cmd/common/helper.cc
index 77b18f9..984628f 100644
--- a/src/tint/cmd/common/helper.cc
+++ b/src/tint/cmd/common/helper.cc
@@ -34,6 +34,7 @@
 #include "src/tint/utils/diagnostic/formatter.h"
 #include "src/tint/utils/diagnostic/printer.h"
 #include "src/tint/utils/text/string.h"
+#include "src/tint/utils/traits/traits.h"
 
 namespace tint::cmd {
 namespace {
@@ -45,6 +46,24 @@
     kSpirvAsm,
 };
 
+/// @param out the stream to write to
+/// @param value the InputFormat
+/// @returns @p out so calls can be chained
+template <typename STREAM, typename = traits::EnableIfIsOStream<STREAM>>
+auto& operator<<(STREAM& out, InputFormat value) {
+    switch (value) {
+        case InputFormat::kUnknown:
+            break;
+        case InputFormat::kWgsl:
+            return out << "wgsl";
+        case InputFormat::kSpirvBin:
+            return out << "spirv";
+        case InputFormat::kSpirvAsm:
+            return out << "spirv asm";
+    }
+    return out << "unknown";
+}
+
 InputFormat InputFormatFromFilename(const std::string& filename) {
     auto input_format = InputFormat::kUnknown;
     if (tint::HasSuffix(filename, ".wgsl")) {
@@ -98,7 +117,7 @@
 void PrintWGSL(std::ostream& out, const tint::Program& program) {
 #if TINT_BUILD_WGSL_WRITER
     tint::wgsl::writer::Options options;
-    auto result = tint::wgsl::writer::Generate(&program, options);
+    auto result = tint::wgsl::writer::Generate(program, options);
     if (result) {
         out << std::endl << result->wgsl << std::endl;
     } else {
@@ -111,95 +130,104 @@
 }
 
 ProgramInfo LoadProgramInfo(const LoadProgramOptions& opts) {
-    std::unique_ptr<tint::Program> program;
-    std::unique_ptr<tint::Source::File> source_file;
-
     auto input_format = InputFormatFromFilename(opts.filename);
-    switch (input_format) {
-        case InputFormat::kUnknown: {
-            std::cerr << "Unknown input format" << std::endl;
-            exit(1);
-        }
-        case InputFormat::kWgsl: {
-#if TINT_BUILD_WGSL_READER
-            std::vector<uint8_t> data;
-            if (!ReadFile<uint8_t>(opts.filename, &data)) {
-                exit(1);
-            }
-            source_file = std::make_unique<tint::Source::File>(
-                opts.filename, std::string(data.begin(), data.end()));
-            program = std::make_unique<tint::Program>(tint::wgsl::reader::Parse(source_file.get()));
-            break;
-#else
-            std::cerr << "Tint not built with the WGSL reader enabled" << std::endl;
-            exit(1);
-#endif  // TINT_BUILD_WGSL_READER
-        }
-        case InputFormat::kSpirvBin: {
-#if TINT_BUILD_SPV_READER
-            std::vector<uint32_t> data;
-            if (!ReadFile<uint32_t>(opts.filename, &data)) {
-                exit(1);
-            }
-            program = std::make_unique<tint::Program>(
-                tint::spirv::reader::Read(data, opts.spirv_reader_options));
-            break;
-#else
-            std::cerr << "Tint not built with the SPIR-V reader enabled" << std::endl;
-            exit(1);
-#endif  // TINT_BUILD_SPV_READER
-        }
-        case InputFormat::kSpirvAsm: {
-#if TINT_BUILD_SPV_READER
-            std::vector<char> text;
-            if (!ReadFile<char>(opts.filename, &text)) {
-                exit(1);
-            }
-            // Use Vulkan 1.1, since this is what Tint, internally, is expecting.
-            spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_1);
-            tools.SetMessageConsumer([](spv_message_level_t, const char*, const spv_position_t& pos,
-                                        const char* msg) {
-                std::cerr << (pos.line + 1) << ":" << (pos.column + 1) << ": " << msg << std::endl;
-            });
-            std::vector<uint32_t> data;
-            if (!tools.Assemble(text.data(), text.size(), &data,
-                                SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS)) {
-                exit(1);
-            }
-            program = std::make_unique<tint::Program>(
-                tint::spirv::reader::Read(data, opts.spirv_reader_options));
-            break;
-#else
-            std::cerr << "Tint not built with the SPIR-V reader enabled" << std::endl;
-            exit(1);
-#endif  // TINT_BUILD_SPV_READER
-        }
-    }
 
-    if (!program) {
-        std::cerr << "Failed to parse input file: " << opts.filename << std::endl;
+    auto load = [&]() -> ProgramInfo {
+        switch (input_format) {
+            case InputFormat::kUnknown:
+                break;
+
+            case InputFormat::kWgsl: {
+#if TINT_BUILD_WGSL_READER
+                std::vector<uint8_t> data;
+                if (!ReadFile<uint8_t>(opts.filename, &data)) {
+                    exit(1);
+                }
+
+                auto file = std::make_unique<tint::Source::File>(
+                    opts.filename, std::string(data.begin(), data.end()));
+
+                return ProgramInfo{
+                    /* program */ tint::wgsl::reader::Parse(file.get()),
+                    /* source_file */ std::move(file),
+                };
+#else
+                std::cerr << "Tint not built with the WGSL reader enabled" << std::endl;
+                exit(1);
+#endif  // TINT_BUILD_WGSL_READER
+            }
+            case InputFormat::kSpirvBin: {
+#if TINT_BUILD_SPV_READER
+                std::vector<uint32_t> data;
+                if (!ReadFile<uint32_t>(opts.filename, &data)) {
+                    exit(1);
+                }
+
+                return ProgramInfo{
+                    /* program */ tint::spirv::reader::Read(data, opts.spirv_reader_options),
+                    /* source_file */ nullptr,
+                };
+#else
+                std::cerr << "Tint not built with the SPIR-V reader enabled" << std::endl;
+                exit(1);
+#endif  // TINT_BUILD_SPV_READER
+            }
+            case InputFormat::kSpirvAsm: {
+#if TINT_BUILD_SPV_READER
+                std::vector<char> text;
+                if (!ReadFile<char>(opts.filename, &text)) {
+                    exit(1);
+                }
+                // Use Vulkan 1.1, since this is what Tint, internally, is expecting.
+                spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_1);
+                tools.SetMessageConsumer([](spv_message_level_t, const char*,
+                                            const spv_position_t& pos, const char* msg) {
+                    std::cerr << (pos.line + 1) << ":" << (pos.column + 1) << ": " << msg
+                              << std::endl;
+                });
+                std::vector<uint32_t> data;
+                if (!tools.Assemble(text.data(), text.size(), &data,
+                                    SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS)) {
+                    exit(1);
+                }
+
+                auto file = std::make_unique<tint::Source::File>(
+                    opts.filename, std::string(text.begin(), text.end()));
+
+                return ProgramInfo{
+                    /* program */ tint::spirv::reader::Read(data, opts.spirv_reader_options),
+                    /* source_file */ std::move(file),
+                };
+#else
+                std::cerr << "Tint not built with the SPIR-V reader enabled" << std::endl;
+                exit(1);
+#endif  // TINT_BUILD_SPV_READER
+            }
+        }
+
+        std::cerr << "Unknown input format: " << input_format << std::endl;
         exit(1);
-    }
-    if (program->Diagnostics().count() > 0) {
-        if (!program->IsValid() && input_format != InputFormat::kWgsl) {
-            // Invalid program from a non-wgsl source. Print the WGSL, to help
-            // understand the diagnostics.
-            PrintWGSL(std::cout, *program);
+    };
+
+    ProgramInfo info = load();
+
+    if (info.program.Diagnostics().count() > 0) {
+        if (!info.program.IsValid() && input_format != InputFormat::kWgsl) {
+            // Invalid program from a non-wgsl source.
+            // Print the WGSL, to help understand the diagnostics.
+            PrintWGSL(std::cout, info.program);
         }
 
         auto diag_printer = tint::diag::Printer::create(stderr, true);
         tint::diag::Formatter diag_formatter;
-        diag_formatter.format(program->Diagnostics(), diag_printer.get());
+        diag_formatter.format(info.program.Diagnostics(), diag_printer.get());
     }
 
-    if (!program->IsValid()) {
+    if (!info.program.IsValid()) {
         exit(1);
     }
 
-    return ProgramInfo{
-        std::move(program),
-        std::move(source_file),
-    };
+    return info;
 }
 
 void PrintInspectorData(tint::inspector::Inspector& inspector) {
diff --git a/src/tint/cmd/common/helper.h b/src/tint/cmd/common/helper.h
index 9b0ed81..e7de224 100644
--- a/src/tint/cmd/common/helper.h
+++ b/src/tint/cmd/common/helper.h
@@ -38,7 +38,7 @@
 /// Information on a loaded program
 struct ProgramInfo {
     /// The loaded program
-    std::unique_ptr<tint::Program> program;
+    tint::Program program;
     /// The source file information
     std::unique_ptr<tint::Source::File> source_file;
 };
@@ -70,7 +70,9 @@
 #endif
 };
 
-/// Loads the source and program information for the given file
+/// Loads the source and program information for the given file.
+/// If the file cannot be loaded then an error is printed and the program is aborted before
+/// returning.
 /// @param opts the loading options
 ProgramInfo LoadProgramInfo(const LoadProgramOptions& opts);
 
diff --git a/src/tint/cmd/info/main.cc b/src/tint/cmd/info/main.cc
index f0d64db..d3fb1fe 100644
--- a/src/tint/cmd/info/main.cc
+++ b/src/tint/cmd/info/main.cc
@@ -71,7 +71,7 @@
     return true;
 }
 
-void EmitJson(const tint::Program* program) {
+void EmitJson(const tint::Program& program) {
     tint::inspector::Inspector inspector(program);
 
     std::cout << "{" << std::endl;
@@ -223,7 +223,7 @@
     std::cout << "\"structures\": [";
 
     bool struct_first = true;
-    for (const auto* ty : program->Types()) {
+    for (const auto* ty : program.Types()) {
         if (!ty->Is<tint::core::type::Struct>()) {
             continue;
         }
@@ -277,7 +277,7 @@
     std::cout << "}" << std::endl;
 }
 
-void EmitText(const tint::Program* program) {
+void EmitText(const tint::Program& program) {
     tint::inspector::Inspector inspector(program);
     if (!inspector.GetUsedExtensionNames().empty()) {
         std::cout << "Extensions:" << std::endl;
@@ -290,7 +290,7 @@
     tint::cmd::PrintInspectorData(inspector);
 
     bool has_struct = false;
-    for (const auto* ty : program->Types()) {
+    for (const auto* ty : program.Types()) {
         if (!ty->Is<tint::core::type::Struct>()) {
             continue;
         }
@@ -300,7 +300,7 @@
 
     if (has_struct) {
         std::cout << "Structures" << std::endl;
-        for (const auto* ty : program->Types()) {
+        for (const auto* ty : program.Types()) {
             if (!ty->Is<tint::core::type::Struct>()) {
                 continue;
             }
@@ -328,25 +328,18 @@
         return 0;
     }
 
-    std::unique_ptr<tint::Program> program;
-    std::unique_ptr<tint::Source::File> source_file;
-
-    {
-        tint::cmd::LoadProgramOptions opts;
-        opts.filename = options.input_filename;
+    tint::cmd::LoadProgramOptions opts;
+    opts.filename = options.input_filename;
 #if TINT_BUILD_SPV_READER
-        opts.spirv_reader_options = options.spirv_reader_options;
+    opts.spirv_reader_options = options.spirv_reader_options;
 #endif
 
-        auto info = tint::cmd::LoadProgramInfo(opts);
-        program = std::move(info.program);
-        source_file = std::move(info.source_file);
-    }
+    auto info = tint::cmd::LoadProgramInfo(opts);
 
     if (options.emit_json) {
-        EmitJson(program.get());
+        EmitJson(info.program);
     } else {
-        EmitText(program.get());
+        EmitText(info.program);
     }
 
     return 0;
diff --git a/src/tint/cmd/loopy/main.cc b/src/tint/cmd/loopy/main.cc
index 8949dc0..e488fad 100644
--- a/src/tint/cmd/loopy/main.cc
+++ b/src/tint/cmd/loopy/main.cc
@@ -189,14 +189,14 @@
 /// Generate SPIR-V code for a program.
 /// @param program the program to generate
 /// @returns true on success
-bool GenerateSpirv(const tint::Program* program) {
+bool GenerateSpirv(const tint::Program& program) {
 #if TINT_BUILD_SPV_WRITER
     tint::spirv::writer::Options gen_options;
     gen_options.external_texture_options.bindings_map =
         tint::cmd::GenerateExternalTextureBindings(program);
     auto result = tint::spirv::writer::Generate(program, gen_options);
     if (!result) {
-        tint::cmd::PrintWGSL(std::cerr, *program);
+        tint::cmd::PrintWGSL(std::cerr, program);
         std::cerr << "Failed to generate: " << result.Failure() << std::endl;
         return false;
     }
@@ -211,7 +211,7 @@
 /// Generate WGSL code for a program.
 /// @param program the program to generate
 /// @returns true on success
-bool GenerateWgsl(const tint::Program* program) {
+bool GenerateWgsl(const tint::Program& program) {
 #if TINT_BUILD_WGSL_WRITER
     tint::wgsl::writer::Options gen_options;
     auto result = tint::wgsl::writer::Generate(program, gen_options);
@@ -231,11 +231,11 @@
 /// Generate MSL code for a program.
 /// @param program the program to generate
 /// @returns true on success
-bool GenerateMsl(const tint::Program* program) {
+bool GenerateMsl(const tint::Program& program) {
 #if TINT_BUILD_MSL_WRITER
     // Remap resource numbers to a flat namespace.
     // TODO(crbug.com/tint/1501): Do this via Options::BindingMap.
-    const tint::Program* input_program = program;
+    const tint::Program* input_program = &program;
     auto flattened = tint::writer::FlattenBindings(program);
     if (flattened) {
         input_program = &*flattened;
@@ -243,15 +243,15 @@
 
     tint::msl::writer::Options gen_options;
     gen_options.external_texture_options.bindings_map =
-        tint::cmd::GenerateExternalTextureBindings(input_program);
+        tint::cmd::GenerateExternalTextureBindings(*input_program);
     gen_options.array_length_from_uniform.ubo_binding = tint::BindingPoint{0, 30};
     gen_options.array_length_from_uniform.bindpoint_to_size_index.emplace(tint::BindingPoint{0, 0},
                                                                           0);
     gen_options.array_length_from_uniform.bindpoint_to_size_index.emplace(tint::BindingPoint{0, 1},
                                                                           1);
-    auto result = tint::msl::writer::Generate(input_program, gen_options);
+    auto result = tint::msl::writer::Generate(*input_program, gen_options);
     if (!result) {
-        tint::cmd::PrintWGSL(std::cerr, *program);
+        tint::cmd::PrintWGSL(std::cerr, program);
         std::cerr << "Failed to generate: " << result.Failure() << std::endl;
         return false;
     }
@@ -267,14 +267,14 @@
 /// Generate HLSL code for a program.
 /// @param program the program to generate
 /// @returns true on success
-bool GenerateHlsl(const tint::Program* program) {
+bool GenerateHlsl(const tint::Program& program) {
 #if TINT_BUILD_HLSL_WRITER
     tint::hlsl::writer::Options gen_options;
     gen_options.external_texture_options.bindings_map =
         tint::cmd::GenerateExternalTextureBindings(program);
     auto result = tint::hlsl::writer::Generate(program, gen_options);
     if (!result) {
-        tint::cmd::PrintWGSL(std::cerr, *program);
+        tint::cmd::PrintWGSL(std::cerr, program);
         std::cerr << "Failed to generate: " << result.Failure() << std::endl;
         return false;
     }
@@ -290,14 +290,14 @@
 /// Generate GLSL code for a program.
 /// @param program the program to generate
 /// @returns true on success
-bool GenerateGlsl(const tint::Program* program) {
+bool GenerateGlsl(const tint::Program& program) {
 #if TINT_BUILD_GLSL_WRITER
     tint::glsl::writer::Options gen_options;
     gen_options.external_texture_options.bindings_map =
         tint::cmd::GenerateExternalTextureBindings(program);
     auto result = tint::glsl::writer::Generate(program, gen_options, "");
     if (result) {
-        tint::cmd::PrintWGSL(std::cerr, *program);
+        tint::cmd::PrintWGSL(std::cerr, program);
         std::cerr << "Failed to generate: " << result.Failure() << std::endl;
         return false;
     }
@@ -377,21 +377,17 @@
     }
 
     // Load the program that will actually be used
-    {
-        tint::cmd::LoadProgramOptions opts;
-        opts.filename = options.input_filename;
+    tint::cmd::LoadProgramOptions opts;
+    opts.filename = options.input_filename;
 
-        auto info = tint::cmd::LoadProgramInfo(opts);
-        program = std::move(info.program);
-        source_file = std::move(info.source_file);
-    }
+    auto info = tint::cmd::LoadProgramInfo(opts);
     {
         uint32_t loop_count = 1;
         if (options.loop == Looper::kIRGenerate) {
             loop_count = options.loop_count;
         }
         for (uint32_t i = 0; i < loop_count; ++i) {
-            auto result = tint::wgsl::reader::ProgramToIR(program.get());
+            auto result = tint::wgsl::reader::ProgramToIR(info.program);
             if (!result) {
                 std::cerr << "Failed to build IR from program: " << result.Failure() << std::endl;
             }
@@ -408,27 +404,27 @@
         switch (options.format) {
             case Format::kSpirv:
                 for (uint32_t i = 0; i < loop_count; ++i) {
-                    success = GenerateSpirv(program.get());
+                    success = GenerateSpirv(info.program);
                 }
                 break;
             case Format::kWgsl:
                 for (uint32_t i = 0; i < loop_count; ++i) {
-                    success = GenerateWgsl(program.get());
+                    success = GenerateWgsl(info.program);
                 }
                 break;
             case Format::kMsl:
                 for (uint32_t i = 0; i < loop_count; ++i) {
-                    success = GenerateMsl(program.get());
+                    success = GenerateMsl(info.program);
                 }
                 break;
             case Format::kHlsl:
                 for (uint32_t i = 0; i < loop_count; ++i) {
-                    success = GenerateHlsl(program.get());
+                    success = GenerateHlsl(info.program);
                 }
                 break;
             case Format::kGlsl:
                 for (uint32_t i = 0; i < loop_count; ++i) {
-                    success = GenerateGlsl(program.get());
+                    success = GenerateGlsl(info.program);
                 }
                 break;
             case Format::kNone:
diff --git a/src/tint/cmd/test/BUILD.bazel b/src/tint/cmd/test/BUILD.bazel
index 51a2798..d57721c 100644
--- a/src/tint/cmd/test/BUILD.bazel
+++ b/src/tint/cmd/test/BUILD.bazel
@@ -37,6 +37,7 @@
     "//src/tint/lang/core/ir:test",
     "//src/tint/lang/core/type:test",
     "//src/tint/lang/core:test",
+    "//src/tint/lang/spirv/ir:test",
     "//src/tint/lang/wgsl/ast/transform:test",
     "//src/tint/lang/wgsl/ast:test",
     "//src/tint/lang/wgsl/helpers:test",
diff --git a/src/tint/cmd/test/BUILD.cmake b/src/tint/cmd/test/BUILD.cmake
index 2c2689d..006baed 100644
--- a/src/tint/cmd/test/BUILD.cmake
+++ b/src/tint/cmd/test/BUILD.cmake
@@ -38,6 +38,7 @@
   tint_lang_core_ir_test
   tint_lang_core_type_test
   tint_lang_core_test
+  tint_lang_spirv_ir_test
   tint_lang_wgsl_ast_transform_test
   tint_lang_wgsl_ast_test
   tint_lang_wgsl_helpers_test
diff --git a/src/tint/cmd/test/BUILD.gn b/src/tint/cmd/test/BUILD.gn
index 6649884..fd1f9bf 100644
--- a/src/tint/cmd/test/BUILD.gn
+++ b/src/tint/cmd/test/BUILD.gn
@@ -42,6 +42,7 @@
       "${tint_src_dir}/lang/core/ir:unittests",
       "${tint_src_dir}/lang/core/ir/transform:unittests",
       "${tint_src_dir}/lang/core/type:unittests",
+      "${tint_src_dir}/lang/spirv/ir:unittests",
       "${tint_src_dir}/lang/wgsl:unittests",
       "${tint_src_dir}/lang/wgsl/ast:unittests",
       "${tint_src_dir}/lang/wgsl/ast/transform:unittests",
diff --git a/src/tint/cmd/tint/main.cc b/src/tint/cmd/tint/main.cc
index 31d7286..c943041 100644
--- a/src/tint/cmd/tint/main.cc
+++ b/src/tint/cmd/tint/main.cc
@@ -573,7 +573,7 @@
 /// @param program the program to generate
 /// @param options the options that Tint was invoked with
 /// @returns true on success
-bool GenerateSpirv(const tint::Program* program, const Options& options) {
+bool GenerateSpirv(const tint::Program& program, const Options& options) {
 #if TINT_BUILD_SPV_WRITER
     // TODO(jrprice): Provide a way for the user to set non-default options.
     tint::spirv::writer::Options gen_options;
@@ -585,7 +585,7 @@
 
     auto result = tint::spirv::writer::Generate(program, gen_options);
     if (!result) {
-        tint::cmd::PrintWGSL(std::cerr, *program);
+        tint::cmd::PrintWGSL(std::cerr, program);
         std::cerr << "Failed to generate: " << result.Failure() << std::endl;
         return false;
     }
@@ -631,7 +631,7 @@
 /// @param program the program to generate
 /// @param options the options that Tint was invoked with
 /// @returns true on success
-bool GenerateWgsl(const tint::Program* program, const Options& options) {
+bool GenerateWgsl(const tint::Program& program, const Options& options) {
 #if TINT_BUILD_WGSL_WRITER
     // TODO(jrprice): Provide a way for the user to set non-default options.
     tint::wgsl::writer::Options gen_options;
@@ -675,11 +675,11 @@
 /// @param program the program to generate
 /// @param options the options that Tint was invoked with
 /// @returns true on success
-bool GenerateMsl(const tint::Program* program, const Options& options) {
+bool GenerateMsl(const tint::Program& program, const Options& options) {
 #if TINT_BUILD_MSL_WRITER
     // Remap resource numbers to a flat namespace.
     // TODO(crbug.com/tint/1501): Do this via Options::BindingMap.
-    const tint::Program* input_program = program;
+    const tint::Program* input_program = &program;
     auto flattened = tint::writer::FlattenBindings(program);
     if (flattened) {
         input_program = &*flattened;
@@ -692,15 +692,15 @@
     gen_options.disable_workgroup_init = options.disable_workgroup_init;
     gen_options.pixel_local_options = options.pixel_local_options;
     gen_options.external_texture_options.bindings_map =
-        tint::cmd::GenerateExternalTextureBindings(input_program);
+        tint::cmd::GenerateExternalTextureBindings(*input_program);
     gen_options.array_length_from_uniform.ubo_binding = tint::BindingPoint{0, 30};
     gen_options.array_length_from_uniform.bindpoint_to_size_index.emplace(tint::BindingPoint{0, 0},
                                                                           0);
     gen_options.array_length_from_uniform.bindpoint_to_size_index.emplace(tint::BindingPoint{0, 1},
                                                                           1);
-    auto result = tint::msl::writer::Generate(input_program, gen_options);
+    auto result = tint::msl::writer::Generate(*input_program, gen_options);
     if (!result) {
-        tint::cmd::PrintWGSL(std::cerr, *program);
+        tint::cmd::PrintWGSL(std::cerr, program);
         std::cerr << "Failed to generate: " << result.Failure() << std::endl;
         return false;
     }
@@ -717,7 +717,7 @@
     // Default to validating against MSL 1.2.
     // If subgroups are used, bump the version to 2.1.
     auto msl_version = tint::msl::validate::MslVersion::kMsl_1_2;
-    for (auto* enable : program->AST().Enables()) {
+    for (auto* enable : program.AST().Enables()) {
         if (enable->HasExtension(tint::wgsl::Extension::kChromiumExperimentalSubgroups)) {
             msl_version = std::max(msl_version, tint::msl::validate::MslVersion::kMsl_2_1);
         }
@@ -764,7 +764,7 @@
 /// @param program the program to generate
 /// @param options the options that Tint was invoked with
 /// @returns true on success
-bool GenerateHlsl(const tint::Program* program, const Options& options) {
+bool GenerateHlsl(const tint::Program& program, const Options& options) {
 #if TINT_BUILD_HLSL_WRITER
     // TODO(jrprice): Provide a way for the user to set non-default options.
     tint::hlsl::writer::Options gen_options;
@@ -775,7 +775,7 @@
     gen_options.root_constant_binding_point = options.hlsl_root_constant_binding_point;
     auto result = tint::hlsl::writer::Generate(program, gen_options);
     if (!result) {
-        tint::cmd::PrintWGSL(std::cerr, *program);
+        tint::cmd::PrintWGSL(std::cerr, program);
         std::cerr << "Failed to generate: " << result.Failure() << std::endl;
         return false;
     }
@@ -803,7 +803,7 @@
             if (dxc.Found()) {
                 dxc_found = true;
 
-                auto enable_list = program->AST().Enables();
+                auto enable_list = program.AST().Enables();
                 bool dxc_require_16bit_types = false;
                 for (auto* enable : enable_list) {
                     if (enable->HasExtension(tint::wgsl::Extension::kF16)) {
@@ -903,13 +903,13 @@
 /// @param program the program to generate
 /// @param options the options that Tint was invoked with
 /// @returns true on success
-bool GenerateGlsl(const tint::Program* program, const Options& options) {
+bool GenerateGlsl(const tint::Program& program, const Options& options) {
 #if TINT_BUILD_GLSL_WRITER
     if (options.validate) {
         glslang::InitializeProcess();
     }
 
-    auto generate = [&](const tint::Program* prg, const std::string entry_point_name) -> bool {
+    auto generate = [&](const tint::Program& prg, const std::string entry_point_name) -> bool {
         tint::glsl::writer::Options gen_options;
         gen_options.disable_robustness = !options.enable_robustness;
         gen_options.external_texture_options.bindings_map =
@@ -920,7 +920,7 @@
         gen_options.texture_builtins_from_uniform = std::move(textureBuiltinsFromUniform);
         auto result = tint::glsl::writer::Generate(prg, gen_options, entry_point_name);
         if (!result) {
-            tint::cmd::PrintWGSL(std::cerr, *prg);
+            tint::cmd::PrintWGSL(std::cerr, prg);
             std::cerr << "Failed to generate: " << result.Failure() << std::endl;
             return false;
         }
@@ -1083,20 +1083,13 @@
         options.format = Format::kSpvAsm;
     }
 
-    std::unique_ptr<tint::Program> program;
-    std::unique_ptr<tint::Source::File> source_file;
-
-    {
-        tint::cmd::LoadProgramOptions opts;
-        opts.filename = options.input_filename;
+    tint::cmd::LoadProgramOptions opts;
+    opts.filename = options.input_filename;
 #if TINT_BUILD_SPV_READER
-        opts.spirv_reader_options = options.spirv_reader_options;
+    opts.spirv_reader_options = options.spirv_reader_options;
 #endif
 
-        auto info = tint::cmd::LoadProgramInfo(opts);
-        program = std::move(info.program);
-        source_file = std::move(info.source_file);
-    }
+    auto info = tint::cmd::LoadProgramInfo(opts);
 
     if (options.parse_only) {
         return 1;
@@ -1106,7 +1099,7 @@
     if (options.dump_ast) {
         tint::wgsl::writer::Options gen_options;
         gen_options.use_syntax_tree_writer = true;
-        auto result = tint::wgsl::writer::Generate(program.get(), gen_options);
+        auto result = tint::wgsl::writer::Generate(info.program, gen_options);
         if (!result) {
             std::cerr << "Failed to dump AST: " << result.Failure() << std::endl;
         } else {
@@ -1117,7 +1110,7 @@
 
 #if TINT_BUILD_WGSL_READER
     if (options.dump_ir) {
-        auto result = tint::wgsl::reader::ProgramToIR(program.get());
+        auto result = tint::wgsl::reader::ProgramToIR(info.program);
         if (!result) {
             std::cerr << "Failed to build IR from program: " << result.Failure() << std::endl;
         } else {
@@ -1130,7 +1123,7 @@
     }
 #endif  // TINT_BUILD_WGSL_READER
 
-    tint::inspector::Inspector inspector(program.get());
+    tint::inspector::Inspector inspector(info.program);
     if (options.dump_inspector_bindings) {
         tint::cmd::PrintInspectorBindings(inspector);
     }
@@ -1212,32 +1205,30 @@
     }
 
     tint::ast::transform::DataMap outputs;
-    auto out = transform_manager.Run(program.get(), std::move(transform_inputs), outputs);
-    if (!out.IsValid()) {
-        tint::cmd::PrintWGSL(std::cerr, out);
-        std::cerr << out.Diagnostics().str() << std::endl;
+    auto program = transform_manager.Run(info.program, std::move(transform_inputs), outputs);
+    if (!program.IsValid()) {
+        tint::cmd::PrintWGSL(std::cerr, program);
+        std::cerr << program.Diagnostics() << std::endl;
         return 1;
     }
 
-    *program = std::move(out);
-
     bool success = false;
     switch (options.format) {
         case Format::kSpirv:
         case Format::kSpvAsm:
-            success = GenerateSpirv(program.get(), options);
+            success = GenerateSpirv(program, options);
             break;
         case Format::kWgsl:
-            success = GenerateWgsl(program.get(), options);
+            success = GenerateWgsl(program, options);
             break;
         case Format::kMsl:
-            success = GenerateMsl(program.get(), options);
+            success = GenerateMsl(program, options);
             break;
         case Format::kHlsl:
-            success = GenerateHlsl(program.get(), options);
+            success = GenerateHlsl(program, options);
             break;
         case Format::kGlsl:
-            success = GenerateGlsl(program.get(), options);
+            success = GenerateGlsl(program, options);
             break;
         case Format::kNone:
             break;
diff --git a/src/tint/fuzzers/apply_substitute_overrides.cc b/src/tint/fuzzers/apply_substitute_overrides.cc
index 16ea79c..e5ef24d 100644
--- a/src/tint/fuzzers/apply_substitute_overrides.cc
+++ b/src/tint/fuzzers/apply_substitute_overrides.cc
@@ -26,7 +26,7 @@
 
 Program ApplySubstituteOverrides(Program&& program) {
     ast::transform::SubstituteOverride::Config cfg;
-    inspector::Inspector inspector(&program);
+    inspector::Inspector inspector(program);
     auto default_values = inspector.GetOverrideDefaultValues();
     for (const auto& [override_id, scalar] : default_values) {
         // If the override is not null, then it has a default value, we can just let it use the
@@ -47,7 +47,7 @@
     mgr.append(std::make_unique<ast::transform::SubstituteOverride>());
 
     ast::transform::DataMap outputs;
-    return mgr.Run(&program, override_data, outputs);
+    return mgr.Run(program, override_data, outputs);
 }
 
 }  // namespace tint::fuzzers
diff --git a/src/tint/fuzzers/shuffle_transform.cc b/src/tint/fuzzers/shuffle_transform.cc
index d4b5fbe..e484715 100644
--- a/src/tint/fuzzers/shuffle_transform.cc
+++ b/src/tint/fuzzers/shuffle_transform.cc
@@ -25,13 +25,13 @@
 
 ShuffleTransform::ShuffleTransform(size_t seed) : seed_(seed) {}
 
-ast::transform::Transform::ApplyResult ShuffleTransform::Apply(const Program* src,
+ast::transform::Transform::ApplyResult ShuffleTransform::Apply(const Program& src,
                                                                const ast::transform::DataMap&,
                                                                ast::transform::DataMap&) const {
     ProgramBuilder b;
-    program::CloneContext ctx{&b, src, /* auto_clone_symbols */ true};
+    program::CloneContext ctx{&b, &src, /* auto_clone_symbols */ true};
 
-    auto decls = src->AST().GlobalDeclarations();
+    auto decls = src.AST().GlobalDeclarations();
     auto rng = std::mt19937_64{seed_};
     std::shuffle(std::begin(decls), std::end(decls), rng);
     for (auto* decl : decls) {
diff --git a/src/tint/fuzzers/shuffle_transform.h b/src/tint/fuzzers/shuffle_transform.h
index 213fddb..5146016 100644
--- a/src/tint/fuzzers/shuffle_transform.h
+++ b/src/tint/fuzzers/shuffle_transform.h
@@ -27,7 +27,7 @@
     explicit ShuffleTransform(size_t seed);
 
     /// @copydoc ast::transform::Transform::Apply
-    ApplyResult Apply(const Program* program,
+    ApplyResult Apply(const Program& program,
                       const ast::transform::DataMap& inputs,
                       ast::transform::DataMap& outputs) const override;
 
diff --git a/src/tint/fuzzers/tint_ast_clone_fuzzer.cc b/src/tint/fuzzers/tint_ast_clone_fuzzer.cc
index cb571ed..805b762 100644
--- a/src/tint/fuzzers/tint_ast_clone_fuzzer.cc
+++ b/src/tint/fuzzers/tint_ast_clone_fuzzer.cc
@@ -67,7 +67,7 @@
     tint::Program dst(src.Clone());
 
     // Expect the printed strings to match
-    ASSERT_EQ(tint::Program::printer(&src), tint::Program::printer(&dst));
+    ASSERT_EQ(tint::Program::printer(src), tint::Program::printer(dst));
 
     // Check that none of the AST nodes or type pointers in dst are found in src
     std::unordered_set<const tint::ast::Node*> src_nodes;
@@ -91,7 +91,7 @@
     std::string src_wgsl;
     tint::wgsl::writer::Options wgsl_options;
     {
-        auto result = tint::wgsl::writer::Generate(&src, wgsl_options);
+        auto result = tint::wgsl::writer::Generate(src, wgsl_options);
         ASSERT_TRUE(result == true);
         src_wgsl = result->wgsl;
 
@@ -104,7 +104,7 @@
     }
 
     // Print the dst program, check it matches the original source
-    auto result = tint::wgsl::writer::Generate(&dst, wgsl_options);
+    auto result = tint::wgsl::writer::Generate(dst, wgsl_options);
     ASSERT_TRUE(result == true);
     auto dst_wgsl = result->wgsl;
     ASSERT_EQ(src_wgsl, dst_wgsl);
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/expression_size_test.cc b/src/tint/fuzzers/tint_ast_fuzzer/expression_size_test.cc
index e9b22e6..c362b68 100644
--- a/src/tint/fuzzers/tint_ast_fuzzer/expression_size_test.cc
+++ b/src/tint/fuzzers/tint_ast_fuzzer/expression_size_test.cc
@@ -35,7 +35,7 @@
   )";
     Source::File file("test.wgsl", content);
     auto program = wgsl::reader::Parse(&file);
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
     ExpressionSize expression_size(program);
     for (const auto* node : program.ASTNodes().Objects()) {
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/fuzzer.cc b/src/tint/fuzzers/tint_ast_fuzzer/fuzzer.cc
index 269c3ef..dca2487 100644
--- a/src/tint/fuzzers/tint_ast_fuzzer/fuzzer.cc
+++ b/src/tint/fuzzers/tint_ast_fuzzer/fuzzer.cc
@@ -48,7 +48,7 @@
     auto program = wgsl::reader::Parse(&file);
     if (!program.IsValid()) {
         std::cout << "Trying to mutate an invalid program:" << std::endl
-                  << program.Diagnostics().str() << std::endl;
+                  << program.Diagnostics() << std::endl;
         return 0;
     }
 
@@ -61,11 +61,11 @@
     if (!program.IsValid()) {
         std::cout << "Mutator produced invalid WGSL:" << std::endl
                   << "  seed: " << seed << std::endl
-                  << program.Diagnostics().str() << std::endl;
+                  << program.Diagnostics() << std::endl;
         return 0;
     }
 
-    auto result = wgsl::writer::Generate(&program, wgsl::writer::Options());
+    auto result = wgsl::writer::Generate(program, wgsl::writer::Options());
     if (!result) {
         std::cout << "Can't generate WGSL for a valid tint::Program:" << std::endl
                   << result.Failure() << std::endl;
@@ -115,7 +115,7 @@
         fuzzer.Run(data, size);
         if (fuzzer.HasErrors()) {
             std::cout << "Fuzzing " << target.name << " produced an error" << std::endl
-                      << fuzzer.Diagnostics().str() << std::endl;
+                      << fuzzer.Diagnostics() << std::endl;
         }
     }
 
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/jump_tracker_test.cc b/src/tint/fuzzers/tint_ast_fuzzer/jump_tracker_test.cc
index 1a15298..57b2e11 100644
--- a/src/tint/fuzzers/tint_ast_fuzzer/jump_tracker_test.cc
+++ b/src/tint/fuzzers/tint_ast_fuzzer/jump_tracker_test.cc
@@ -72,7 +72,7 @@
   )";
     Source::File file("test.wgsl", content);
     auto program = wgsl::reader::Parse(&file);
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
     JumpTracker jump_tracker(program);
 
@@ -151,7 +151,7 @@
   )";
     Source::File file("test.wgsl", content);
     auto program = wgsl::reader::Parse(&file);
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
     JumpTracker jump_tracker(program);
 
@@ -215,7 +215,7 @@
   )";
     Source::File file("test.wgsl", content);
     auto program = wgsl::reader::Parse(&file);
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
     JumpTracker jump_tracker(program);
 
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutations/change_binary_operator_test.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutations/change_binary_operator_test.cc
index 00df1e0..d07df67 100644
--- a/src/tint/fuzzers/tint_ast_fuzzer/mutations/change_binary_operator_test.cc
+++ b/src/tint/fuzzers/tint_ast_fuzzer/mutations/change_binary_operator_test.cc
@@ -80,7 +80,7 @@
   )";
     Source::File file("test.wgsl", content);
     auto program = wgsl::reader::Parse(&file);
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
     NodeIdMap node_id_map(program);
 
@@ -121,7 +121,7 @@
 )";
     Source::File file("test.wgsl", shader);
     auto program = wgsl::reader::Parse(&file);
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
     NodeIdMap node_id_map(program);
 
@@ -138,11 +138,11 @@
 
     ASSERT_TRUE(MaybeApplyMutation(
         program, MutationChangeBinaryOperator(sum_expr_id, core::BinaryOp::kSubtract), node_id_map,
-        &program, &node_id_map, nullptr));
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+        program, &node_id_map, nullptr));
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
     wgsl::writer::Options options;
-    auto result = wgsl::writer::Generate(&program, options);
+    auto result = wgsl::writer::Generate(program, options);
     ASSERT_TRUE(result) << result.Failure();
 
     std::string expected_shader = R"(fn main() {
@@ -184,7 +184,7 @@
     for (auto new_operator : all_operators) {
         Source::File file("test.wgsl", shader.str());
         auto program = wgsl::reader::Parse(&file);
-        ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+        ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
         NodeIdMap node_id_map(program);
 
@@ -211,15 +211,15 @@
             if (new_operator != binary_expr->op) {
                 Source::File invalid_file("test.wgsl", expected_shader.str());
                 auto invalid_program = wgsl::reader::Parse(&invalid_file);
-                ASSERT_FALSE(invalid_program.IsValid()) << program.Diagnostics().str();
+                ASSERT_FALSE(invalid_program.IsValid()) << program.Diagnostics();
             }
         } else {
-            ASSERT_TRUE(MaybeApplyMutation(program, mutation, node_id_map, &program, &node_id_map,
-                                           nullptr));
-            ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+            ASSERT_TRUE(
+                MaybeApplyMutation(program, mutation, node_id_map, program, &node_id_map, nullptr));
+            ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
             wgsl::writer::Options options;
-            auto result = wgsl::writer::Generate(&program, options);
+            auto result = wgsl::writer::Generate(program, options);
             ASSERT_TRUE(result) << result.Failure();
 
             ASSERT_EQ(expected_shader.str(), result->wgsl);
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutations/change_unary_operator_test.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutations/change_unary_operator_test.cc
index 3a6773c..2eba328 100644
--- a/src/tint/fuzzers/tint_ast_fuzzer/mutations/change_unary_operator_test.cc
+++ b/src/tint/fuzzers/tint_ast_fuzzer/mutations/change_unary_operator_test.cc
@@ -44,7 +44,7 @@
   )";
     Source::File file("test.wgsl", content);
     auto program = wgsl::reader::Parse(&file);
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
     NodeIdMap node_id_map(program);
 
@@ -82,49 +82,49 @@
     // the operator of float types to any other.
     ASSERT_FALSE(MaybeApplyMutation(
         program, MutationChangeUnaryOperator(neg_a_id, core::UnaryOp::kComplement), node_id_map,
-        &program, &node_id_map, nullptr));
+        program, &node_id_map, nullptr));
     ASSERT_FALSE(MaybeApplyMutation(program,
                                     MutationChangeUnaryOperator(neg_a_id, core::UnaryOp::kNot),
-                                    node_id_map, &program, &node_id_map, nullptr));
+                                    node_id_map, program, &node_id_map, nullptr));
     ASSERT_FALSE(MaybeApplyMutation(program,
                                     MutationChangeUnaryOperator(neg_a_id, core::UnaryOp::kNegation),
-                                    node_id_map, &program, &node_id_map, nullptr));
+                                    node_id_map, program, &node_id_map, nullptr));
 
     auto not_b_id = node_id_map.GetId(not_b_expr);
     // Only complement and negation is allowed for signed integer type.
     ASSERT_FALSE(MaybeApplyMutation(program,
                                     MutationChangeUnaryOperator(not_b_id, core::UnaryOp::kNot),
-                                    node_id_map, &program, &node_id_map, nullptr));
+                                    node_id_map, program, &node_id_map, nullptr));
     // Cannot change to the same unary operator.
     ASSERT_FALSE(MaybeApplyMutation(
         program, MutationChangeUnaryOperator(not_b_id, core::UnaryOp::kComplement), node_id_map,
-        &program, &node_id_map, nullptr));
+        program, &node_id_map, nullptr));
 
     auto not_c_id = node_id_map.GetId(not_c_expr);
     // Only complement is allowed for unsigned integer.Cannot change
     //  // the operator of float types to any other.
     ASSERT_FALSE(MaybeApplyMutation(program,
                                     MutationChangeUnaryOperator(not_c_id, core::UnaryOp::kNot),
-                                    node_id_map, &program, &node_id_map, nullptr));
+                                    node_id_map, program, &node_id_map, nullptr));
     ASSERT_FALSE(MaybeApplyMutation(program,
                                     MutationChangeUnaryOperator(not_c_id, core::UnaryOp::kNegation),
-                                    node_id_map, &program, &node_id_map, nullptr));
+                                    node_id_map, program, &node_id_map, nullptr));
     ASSERT_FALSE(MaybeApplyMutation(
         program, MutationChangeUnaryOperator(not_c_id, core::UnaryOp::kComplement), node_id_map,
-        &program, &node_id_map, nullptr));
+        program, &node_id_map, nullptr));
 
     auto neg_d_id = node_id_map.GetId(neg_d_expr);
     // Only logical negation (not) is allowed for bool type.  Cannot change
     // the operator of float types to any other.
     ASSERT_FALSE(MaybeApplyMutation(
         program, MutationChangeUnaryOperator(neg_d_id, core::UnaryOp::kComplement), node_id_map,
-        &program, &node_id_map, nullptr));
+        program, &node_id_map, nullptr));
     ASSERT_FALSE(MaybeApplyMutation(program,
                                     MutationChangeUnaryOperator(neg_d_id, core::UnaryOp::kNegation),
-                                    node_id_map, &program, &node_id_map, nullptr));
+                                    node_id_map, program, &node_id_map, nullptr));
     ASSERT_FALSE(MaybeApplyMutation(program,
                                     MutationChangeUnaryOperator(neg_d_id, core::UnaryOp::kNot),
-                                    node_id_map, &program, &node_id_map, nullptr));
+                                    node_id_map, program, &node_id_map, nullptr));
 }
 
 TEST(ChangeUnaryOperatorTest, Signed_Integer_Types_Applicable1) {
@@ -136,7 +136,7 @@
   )";
     Source::File file("test.wgsl", content);
     auto program = wgsl::reader::Parse(&file);
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
     NodeIdMap node_id_map(program);
 
@@ -154,11 +154,11 @@
     auto comp_a_id = node_id_map.GetId(comp_a_expr);
     ASSERT_TRUE(MaybeApplyMutation(program,
                                    MutationChangeUnaryOperator(comp_a_id, core::UnaryOp::kNegation),
-                                   node_id_map, &program, &node_id_map, nullptr));
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+                                   node_id_map, program, &node_id_map, nullptr));
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
     wgsl::writer::Options options;
-    auto result = wgsl::writer::Generate(&program, options);
+    auto result = wgsl::writer::Generate(program, options);
     ASSERT_TRUE(result) << result.Failure();
 
     std::string expected_shader = R"(fn main() {
@@ -177,7 +177,7 @@
     })";
     Source::File file("test.wgsl", content);
     auto program = wgsl::reader::Parse(&file);
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
     NodeIdMap node_id_map(program);
 
@@ -195,11 +195,11 @@
     auto comp_b_id = node_id_map.GetId(comp_b_expr);
     ASSERT_TRUE(MaybeApplyMutation(program,
                                    MutationChangeUnaryOperator(comp_b_id, core::UnaryOp::kNegation),
-                                   node_id_map, &program, &node_id_map, nullptr));
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+                                   node_id_map, program, &node_id_map, nullptr));
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
     wgsl::writer::Options options;
-    auto result = wgsl::writer::Generate(&program, options);
+    auto result = wgsl::writer::Generate(program, options);
     ASSERT_TRUE(result) << result.Failure();
 
     std::string expected_shader = R"(fn main() {
@@ -220,7 +220,7 @@
   )";
     Source::File file("test.wgsl", content);
     auto program = wgsl::reader::Parse(&file);
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
     NodeIdMap node_id_map(program);
 
@@ -238,11 +238,11 @@
     auto neg_a_id = node_id_map.GetId(neg_a_expr);
     ASSERT_TRUE(MaybeApplyMutation(
         program, MutationChangeUnaryOperator(neg_a_id, core::UnaryOp::kComplement), node_id_map,
-        &program, &node_id_map, nullptr));
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+        program, &node_id_map, nullptr));
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
     wgsl::writer::Options options;
-    auto result = wgsl::writer::Generate(&program, options);
+    auto result = wgsl::writer::Generate(program, options);
     ASSERT_TRUE(result) << result.Failure();
 
     std::string expected_shader = R"(fn main() {
@@ -262,7 +262,7 @@
   )";
     Source::File file("test.wgsl", content);
     auto program = wgsl::reader::Parse(&file);
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
     NodeIdMap node_id_map(program);
 
@@ -280,11 +280,11 @@
     auto neg_b_id = node_id_map.GetId(neg_b_expr);
     ASSERT_TRUE(MaybeApplyMutation(
         program, MutationChangeUnaryOperator(neg_b_id, core::UnaryOp::kComplement), node_id_map,
-        &program, &node_id_map, nullptr));
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+        program, &node_id_map, nullptr));
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
     wgsl::writer::Options options;
-    auto result = wgsl::writer::Generate(&program, options);
+    auto result = wgsl::writer::Generate(program, options);
     ASSERT_TRUE(result) << result.Failure();
 
     std::string expected_shader = R"(fn main() {
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutations/delete_statement_test.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutations/delete_statement_test.cc
index 0be25af..d7338b5 100644
--- a/src/tint/fuzzers/tint_ast_fuzzer/mutations/delete_statement_test.cc
+++ b/src/tint/fuzzers/tint_ast_fuzzer/mutations/delete_statement_test.cc
@@ -45,8 +45,8 @@
     Source::File expected_file("expected.wgsl", expected);
     auto expected_program = wgsl::reader::Parse(&expected_file);
 
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
-    ASSERT_TRUE(expected_program.IsValid()) << expected_program.Diagnostics().str();
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
+    ASSERT_TRUE(expected_program.IsValid()) << expected_program.Diagnostics();
 
     NodeIdMap node_id_map(program);
     const auto* statement = statement_finder(program);
@@ -54,11 +54,11 @@
     auto statement_id = node_id_map.GetId(statement);
     ASSERT_NE(statement_id, 0);
     ASSERT_TRUE(MaybeApplyMutation(program, MutationDeleteStatement(statement_id), node_id_map,
-                                   &program, &node_id_map, nullptr));
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+                                   program, &node_id_map, nullptr));
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
     wgsl::writer::Options options;
-    auto transformed_result = wgsl::writer::Generate(&program, options);
-    auto expected_result = wgsl::writer::Generate(&expected_program, options);
+    auto transformed_result = wgsl::writer::Generate(program, options);
+    auto expected_result = wgsl::writer::Generate(expected_program, options);
     ASSERT_TRUE(transformed_result) << transformed_result.Failure();
     ASSERT_TRUE(expected_result) << expected_result.Failure();
     ASSERT_EQ(expected_result->wgsl, transformed_result->wgsl);
@@ -70,7 +70,7 @@
     Source::File original_file("original.wgsl", original);
     auto program = wgsl::reader::Parse(&original_file);
 
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
     NodeIdMap node_id_map(program);
     const auto* statement = statement_finder(program);
@@ -78,7 +78,7 @@
     auto statement_id = node_id_map.GetId(statement);
     ASSERT_NE(statement_id, 0);
     ASSERT_FALSE(MaybeApplyMutation(program, MutationDeleteStatement(statement_id), node_id_map,
-                                    &program, &node_id_map, nullptr));
+                                    program, &node_id_map, nullptr));
 }
 
 TEST(DeleteStatementTest, DeleteAssignStatement) {
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutations/replace_identifier_test.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutations/replace_identifier_test.cc
index 5d071f4..86abf7b 100644
--- a/src/tint/fuzzers/tint_ast_fuzzer/mutations/replace_identifier_test.cc
+++ b/src/tint/fuzzers/tint_ast_fuzzer/mutations/replace_identifier_test.cc
@@ -41,7 +41,7 @@
   )";
     Source::File file("test.wgsl", content);
     auto program = wgsl::reader::Parse(&file);
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
     NodeIdMap node_id_map(program);
 
@@ -120,7 +120,7 @@
 )";
     Source::File file("test.wgsl", shader);
     auto program = wgsl::reader::Parse(&file);
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
     NodeIdMap node_id_map(program);
 
@@ -147,7 +147,7 @@
 )";
     Source::File file("test.wgsl", shader);
     auto program = wgsl::reader::Parse(&file);
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
     NodeIdMap node_id_map(program);
 
@@ -179,7 +179,7 @@
 )";
     Source::File file("test.wgsl", shader);
     auto program = wgsl::reader::Parse(&file);
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
     NodeIdMap node_id_map(program);
 
@@ -215,7 +215,7 @@
 )";
     Source::File file("test.wgsl", shader);
     auto program = wgsl::reader::Parse(&file);
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
     NodeIdMap node_id_map(program);
 
@@ -250,7 +250,7 @@
 )";
     Source::File file("test.wgsl", shader);
     auto program = wgsl::reader::Parse(&file);
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
     NodeIdMap node_id_map(program);
 
@@ -286,7 +286,7 @@
 )";
     Source::File file("test.wgsl", shader);
     auto program = wgsl::reader::Parse(&file);
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
     NodeIdMap node_id_map(program);
 
@@ -322,7 +322,7 @@
 )";
     Source::File file("test.wgsl", shader);
     auto program = wgsl::reader::Parse(&file);
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
     NodeIdMap node_id_map(program);
 
@@ -359,7 +359,7 @@
 )";
     Source::File file("test.wgsl", shader);
     auto program = wgsl::reader::Parse(&file);
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
     NodeIdMap node_id_map(program);
 
@@ -394,7 +394,7 @@
 )";
     Source::File file("test.wgsl", shader);
     auto program = wgsl::reader::Parse(&file);
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
     NodeIdMap node_id_map(program);
 
@@ -430,7 +430,7 @@
 )";
     Source::File file("test.wgsl", shader);
     auto program = wgsl::reader::Parse(&file);
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
     NodeIdMap node_id_map(program);
 
@@ -461,7 +461,7 @@
 )";
     Source::File file("test.wgsl", shader);
     auto program = wgsl::reader::Parse(&file);
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
     NodeIdMap node_id_map(program);
 
@@ -483,11 +483,11 @@
     ASSERT_NE(replacement_id, 0);
 
     ASSERT_TRUE(MaybeApplyMutation(program, MutationReplaceIdentifier(use_id, replacement_id),
-                                   node_id_map, &program, &node_id_map, nullptr));
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+                                   node_id_map, program, &node_id_map, nullptr));
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
     wgsl::writer::Options options;
-    auto result = wgsl::writer::Generate(&program, options);
+    auto result = wgsl::writer::Generate(program, options);
     ASSERT_TRUE(result) << result.Failure();
 
     std::string expected_shader = R"(fn f() {
@@ -510,7 +510,7 @@
 )";
     Source::File file("test.wgsl", shader);
     auto program = wgsl::reader::Parse(&file);
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
     NodeIdMap node_id_map(program);
 
@@ -527,11 +527,11 @@
     ASSERT_NE(replacement_id, 0);
 
     ASSERT_TRUE(MaybeApplyMutation(program, MutationReplaceIdentifier(use_id, replacement_id),
-                                   node_id_map, &program, &node_id_map, nullptr));
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+                                   node_id_map, program, &node_id_map, nullptr));
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
     wgsl::writer::Options options;
-    auto result = wgsl::writer::Generate(&program, options);
+    auto result = wgsl::writer::Generate(program, options);
     ASSERT_TRUE(result) << result.Failure();
 
     std::string expected_shader = R"(fn f(b : ptr<function, vec2<u32>>) {
@@ -555,7 +555,7 @@
 )";
     Source::File file("test.wgsl", shader);
     auto program = wgsl::reader::Parse(&file);
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
     NodeIdMap node_id_map(program);
 
@@ -588,7 +588,7 @@
 )";
     Source::File file("test.wgsl", shader);
     auto program = wgsl::reader::Parse(&file);
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
     NodeIdMap node_id_map(program);
 
@@ -623,7 +623,7 @@
 )";
     Source::File file("test.wgsl", shader);
     auto program = wgsl::reader::Parse(&file);
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
     NodeIdMap node_id_map(program);
 
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator_test.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator_test.cc
index a411255..531bb2b 100644
--- a/src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator_test.cc
+++ b/src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator_test.cc
@@ -38,7 +38,7 @@
   )";
     Source::File file("test.wgsl", content);
     auto program = wgsl::reader::Parse(&file);
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
     NodeIdMap node_id_map(program);
 
@@ -51,11 +51,11 @@
     ASSERT_TRUE(MaybeApplyMutation(
         program,
         MutationWrapUnaryOperator(expression_id, node_id_map.TakeFreshId(), core::UnaryOp::kNot),
-        node_id_map, &program, &node_id_map, nullptr));
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+        node_id_map, program, &node_id_map, nullptr));
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
     wgsl::writer::Options options;
-    auto result = wgsl::writer::Generate(&program, options);
+    auto result = wgsl::writer::Generate(program, options);
     ASSERT_TRUE(result) << result.Failure();
 
     std::string expected_shader = R"(fn main() {
@@ -76,7 +76,7 @@
   )";
     Source::File file("test.wgsl", content);
     auto program = wgsl::reader::Parse(&file);
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
     NodeIdMap node_id_map(program);
 
@@ -92,11 +92,11 @@
     ASSERT_TRUE(MaybeApplyMutation(
         program,
         MutationWrapUnaryOperator(expression_id, node_id_map.TakeFreshId(), core::UnaryOp::kNot),
-        node_id_map, &program, &node_id_map, nullptr));
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+        node_id_map, program, &node_id_map, nullptr));
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
     wgsl::writer::Options options;
-    auto result = wgsl::writer::Generate(&program, options);
+    auto result = wgsl::writer::Generate(program, options);
     ASSERT_TRUE(result) << result.Failure();
 
     std::string expected_shader = R"(fn main() {
@@ -115,7 +115,7 @@
   )";
     Source::File file("test.wgsl", content);
     auto program = wgsl::reader::Parse(&file);
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
     NodeIdMap node_id_map(program);
 
@@ -130,11 +130,11 @@
         MaybeApplyMutation(program,
                            MutationWrapUnaryOperator(expression_id, node_id_map.TakeFreshId(),
                                                      core::UnaryOp::kComplement),
-                           node_id_map, &program, &node_id_map, nullptr));
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+                           node_id_map, program, &node_id_map, nullptr));
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
     wgsl::writer::Options options;
-    auto result = wgsl::writer::Generate(&program, options);
+    auto result = wgsl::writer::Generate(program, options);
     ASSERT_TRUE(result) << result.Failure();
 
     std::string expected_shader = R"(fn main() {
@@ -154,7 +154,7 @@
   )";
     Source::File file("test.wgsl", content);
     auto program = wgsl::reader::Parse(&file);
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
     NodeIdMap node_id_map(program);
 
@@ -172,11 +172,11 @@
         MaybeApplyMutation(program,
                            MutationWrapUnaryOperator(expression_id, node_id_map.TakeFreshId(),
                                                      core::UnaryOp::kComplement),
-                           node_id_map, &program, &node_id_map, nullptr));
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+                           node_id_map, program, &node_id_map, nullptr));
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
     wgsl::writer::Options options;
-    auto result = wgsl::writer::Generate(&program, options);
+    auto result = wgsl::writer::Generate(program, options);
     ASSERT_TRUE(result) << result.Failure();
 
     std::string expected_shader = R"(fn main() -> vec2<bool> {
@@ -195,7 +195,7 @@
   )";
     Source::File file("test.wgsl", content);
     auto program = wgsl::reader::Parse(&file);
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
     NodeIdMap node_id_map(program);
 
@@ -213,11 +213,11 @@
         MaybeApplyMutation(program,
                            MutationWrapUnaryOperator(expression_id, node_id_map.TakeFreshId(),
                                                      core::UnaryOp::kNegation),
-                           node_id_map, &program, &node_id_map, nullptr));
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+                           node_id_map, program, &node_id_map, nullptr));
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
     wgsl::writer::Options options;
-    auto result = wgsl::writer::Generate(&program, options);
+    auto result = wgsl::writer::Generate(program, options);
     ASSERT_TRUE(result) << result.Failure();
 
     std::string expected_shader = R"(fn main() {
@@ -235,7 +235,7 @@
   )";
     Source::File file("test.wgsl", content);
     auto program = wgsl::reader::Parse(&file);
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
     NodeIdMap node_id_map(program);
 
@@ -252,11 +252,11 @@
         MaybeApplyMutation(program,
                            MutationWrapUnaryOperator(expression_id, node_id_map.TakeFreshId(),
                                                      core::UnaryOp::kNegation),
-                           node_id_map, &program, &node_id_map, nullptr));
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+                           node_id_map, program, &node_id_map, nullptr));
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
     wgsl::writer::Options options;
-    auto result = wgsl::writer::Generate(&program, options);
+    auto result = wgsl::writer::Generate(program, options);
     ASSERT_TRUE(result) << result.Failure();
 
     std::string expected_shader = R"(fn main() {
@@ -277,7 +277,7 @@
   )";
     Source::File file("test.wgsl", content);
     auto program = wgsl::reader::Parse(&file);
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
     NodeIdMap node_id_map(program);
 
@@ -295,11 +295,11 @@
         MaybeApplyMutation(program,
                            MutationWrapUnaryOperator(expression_id, node_id_map.TakeFreshId(),
                                                      core::UnaryOp::kNegation),
-                           node_id_map, &program, &node_id_map, nullptr));
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+                           node_id_map, program, &node_id_map, nullptr));
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
     wgsl::writer::Options options;
-    auto result = wgsl::writer::Generate(&program, options);
+    auto result = wgsl::writer::Generate(program, options);
     ASSERT_TRUE(result) << result.Failure();
 
     std::string expected_shader = R"(fn main() {
@@ -320,7 +320,7 @@
   )";
     Source::File file("test.wgsl", content);
     auto program = wgsl::reader::Parse(&file);
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
     NodeIdMap node_id_map(program);
 
@@ -337,11 +337,11 @@
         MaybeApplyMutation(program,
                            MutationWrapUnaryOperator(expression_id, node_id_map.TakeFreshId(),
                                                      core::UnaryOp::kComplement),
-                           node_id_map, &program, &node_id_map, nullptr));
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+                           node_id_map, program, &node_id_map, nullptr));
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
     wgsl::writer::Options options;
-    auto result = wgsl::writer::Generate(&program, options);
+    auto result = wgsl::writer::Generate(program, options);
     ASSERT_TRUE(result) << result.Failure();
 
     std::string expected_shader = R"(fn main() {
@@ -359,7 +359,7 @@
   )";
     Source::File file("test.wgsl", content);
     auto program = wgsl::reader::Parse(&file);
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
     NodeIdMap node_id_map(program);
 
@@ -377,7 +377,7 @@
         MaybeApplyMutation(program,
                            MutationWrapUnaryOperator(expression_id, node_id_map.TakeFreshId(),
                                                      core::UnaryOp::kNegation),
-                           node_id_map, &program, &node_id_map, nullptr));
+                           node_id_map, program, &node_id_map, nullptr));
 }
 
 TEST(WrapUnaryOperatorTest, NotApplicable2) {
@@ -388,7 +388,7 @@
   )";
     Source::File file("test.wgsl", content);
     auto program = wgsl::reader::Parse(&file);
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
     NodeIdMap node_id_map(program);
 
@@ -405,7 +405,7 @@
     ASSERT_FALSE(MaybeApplyMutation(
         program,
         MutationWrapUnaryOperator(expression_id, node_id_map.TakeFreshId(), core::UnaryOp::kNot),
-        node_id_map, &program, &node_id_map, nullptr));
+        node_id_map, program, &node_id_map, nullptr));
 }
 
 TEST(WrapUnaryOperatorTest, NotApplicable3) {
@@ -416,7 +416,7 @@
   )";
     Source::File file("test.wgsl", content);
     auto program = wgsl::reader::Parse(&file);
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
     NodeIdMap node_id_map(program);
 
@@ -434,7 +434,7 @@
         MaybeApplyMutation(program,
                            MutationWrapUnaryOperator(expression_id, node_id_map.TakeFreshId(),
                                                      core::UnaryOp::kNegation),
-                           node_id_map, &program, &node_id_map, nullptr));
+                           node_id_map, program, &node_id_map, nullptr));
 }
 
 TEST(WrapUnaryOperatorTest, NotApplicable4) {
@@ -445,7 +445,7 @@
   )";
     Source::File file("test.wgsl", content);
     auto program = wgsl::reader::Parse(&file);
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
     NodeIdMap node_id_map(program);
 
@@ -463,7 +463,7 @@
         MaybeApplyMutation(program,
                            MutationWrapUnaryOperator(expression_id, node_id_map.TakeFreshId(),
                                                      core::UnaryOp::kComplement),
-                           node_id_map, &program, &node_id_map, nullptr));
+                           node_id_map, program, &node_id_map, nullptr));
 }
 
 TEST(WrapUnaryOperatorTest, NotApplicable5) {
@@ -474,7 +474,7 @@
   )";
     Source::File file("test.wgsl", content);
     auto program = wgsl::reader::Parse(&file);
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
     NodeIdMap node_id_map(program);
 
@@ -490,7 +490,7 @@
     // Id for the replacement expression is not fresh.
     ASSERT_FALSE(MaybeApplyMutation(
         program, MutationWrapUnaryOperator(expression_id, expression_id, core::UnaryOp::kNegation),
-        node_id_map, &program, &node_id_map, nullptr));
+        node_id_map, program, &node_id_map, nullptr));
 }
 
 TEST(WrapUnaryOperatorTest, NotApplicable6) {
@@ -501,7 +501,7 @@
   )";
     Source::File file("test.wgsl", content);
     auto program = wgsl::reader::Parse(&file);
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
     NodeIdMap node_id_map(program);
 
@@ -517,7 +517,7 @@
         MaybeApplyMutation(program,
                            MutationWrapUnaryOperator(statement_id, node_id_map.TakeFreshId(),
                                                      core::UnaryOp::kNegation),
-                           node_id_map, &program, &node_id_map, nullptr));
+                           node_id_map, program, &node_id_map, nullptr));
 }
 
 TEST(WrapUnaryOperatorTest, NotApplicable_CallStmt) {
@@ -531,7 +531,7 @@
   )";
     Source::File file("test.wgsl", content);
     auto program = wgsl::reader::Parse(&file);
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
     NodeIdMap node_id_map(program);
 
@@ -546,7 +546,7 @@
     // The id provided for the expression is not a valid expression type.
     ASSERT_FALSE(MaybeApplyMutation(
         program, MutationWrapUnaryOperator(expr_id, node_id_map.TakeFreshId(), core::UnaryOp::kNot),
-        node_id_map, &program, &node_id_map, nullptr));
+        node_id_map, program, &node_id_map, nullptr));
 }
 
 }  // namespace
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutator.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutator.cc
index eab45ca..a7b4702 100644
--- a/src/tint/fuzzers/tint_ast_fuzzer/mutator.cc
+++ b/src/tint/fuzzers/tint_ast_fuzzer/mutator.cc
@@ -65,10 +65,9 @@
 bool MaybeApplyMutation(const tint::Program& program,
                         const Mutation& mutation,
                         const NodeIdMap& node_id_map,
-                        tint::Program* out_program,
+                        tint::Program& out_program,
                         NodeIdMap* out_node_id_map,
                         protobufs::MutationSequence* mutation_sequence) {
-    assert(out_program && "`out_program` may not be a nullptr");
     assert(out_node_id_map && "`out_node_id_map` may not be a nullptr");
 
     if (!mutation.IsApplicable(program, node_id_map)) {
@@ -93,7 +92,7 @@
     }
 
     clone_context.Clone();
-    *out_program = tint::resolver::Resolve(mutated);
+    out_program = tint::resolver::Resolve(mutated);
     *out_node_id_map = std::move(new_node_id_map);
     return true;
 }
@@ -105,7 +104,7 @@
     for (const auto& mutation_message : mutation_sequence.mutation()) {
         auto mutation = Mutation::FromMessage(mutation_message);
         auto status =
-            MaybeApplyMutation(program, *mutation, node_id_map, &program, &node_id_map, nullptr);
+            MaybeApplyMutation(program, *mutation, node_id_map, program, &node_id_map, nullptr);
         (void)status;  // `status` will be unused in release mode.
         assert(status && "`mutation` is inapplicable - it's most likely a bug");
         if (!program.IsValid()) {
@@ -156,7 +155,7 @@
                 continue;
             }
 
-            if (!MaybeApplyMutation(program, *mutation, node_id_map, &program, &node_id_map,
+            if (!MaybeApplyMutation(program, *mutation, node_id_map, program, &node_id_map,
                                     mutation_sequence)) {
                 // This `mutation` is inapplicable. This may happen if some of the
                 // earlier mutations cancelled this one.
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutator.h b/src/tint/fuzzers/tint_ast_fuzzer/mutator.h
index 649781a..b1c23c4 100644
--- a/src/tint/fuzzers/tint_ast_fuzzer/mutator.h
+++ b/src/tint/fuzzers/tint_ast_fuzzer/mutator.h
@@ -52,7 +52,7 @@
 bool MaybeApplyMutation(const tint::Program& program,
                         const Mutation& mutation,
                         const NodeIdMap& node_id_map,
-                        tint::Program* out_program,
+                        tint::Program& out_program,
                         NodeIdMap* out_node_id_map,
                         protobufs::MutationSequence* mutation_sequence);
 
diff --git a/src/tint/fuzzers/tint_common_fuzzer.cc b/src/tint/fuzzers/tint_common_fuzzer.cc
index 93ec9ea..a9e2e83 100644
--- a/src/tint/fuzzers/tint_common_fuzzer.cc
+++ b/src/tint/fuzzers/tint_common_fuzzer.cc
@@ -68,14 +68,14 @@
 // Wrapping in a macro, so it can be a one-liner in the code, but not
 // introduce another level in the stack trace. This will help with de-duping
 // ClusterFuzz issues.
-#define CHECK_INSPECTOR(program, inspector)                                                  \
-    do {                                                                                     \
-        if ((inspector).has_error()) {                                                       \
-            if (!enforce_validity) {                                                         \
-                return;                                                                      \
-            }                                                                                \
-            FATAL_ERROR(program->Diagnostics(), "Inspector failed: " + (inspector).error()); \
-        }                                                                                    \
+#define CHECK_INSPECTOR(program, inspector)                                                 \
+    do {                                                                                    \
+        if ((inspector).has_error()) {                                                      \
+            if (!enforce_validity) {                                                        \
+                return;                                                                     \
+            }                                                                               \
+            FATAL_ERROR(program.Diagnostics(), "Inspector failed: " + (inspector).error()); \
+        }                                                                                   \
     } while (false)
 
 // Wrapping in a macro to make code more readable and help with issue de-duping.
@@ -205,7 +205,7 @@
     }
 #endif  // TINT_BUILD_SPV_READER
 
-    RunInspector(&program);
+    RunInspector(program);
     diagnostics_ = program.Diagnostics();
 
     auto validate_program = [&](auto& out) {
@@ -224,13 +224,13 @@
         }
 
         program = std::move(out);
-        RunInspector(&program);
+        RunInspector(program);
         return 1;
     };
 
     if (transform_manager_) {
         ast::transform::DataMap outputs;
-        auto out = transform_manager_->Run(&program, *transform_inputs_, outputs);
+        auto out = transform_manager_->Run(program, *transform_inputs_, outputs);
         if (!validate_program(out)) {  // Will move: program <- out on success
             return 0;
         }
@@ -293,7 +293,7 @@
     switch (output_) {
         case OutputFormat::kWGSL: {
 #if TINT_BUILD_WGSL_WRITER
-            (void)wgsl::writer::Generate(&program, options_wgsl_);
+            (void)wgsl::writer::Generate(program, options_wgsl_);
 #endif  // TINT_BUILD_WGSL_WRITER
             break;
         }
@@ -306,7 +306,7 @@
                 return 0;
             }
 
-            auto result = spirv::writer::Generate(&program, options_spirv_);
+            auto result = spirv::writer::Generate(program, options_spirv_);
             if (result) {
                 generated_spirv_ = std::move(result->spirv);
 
@@ -321,7 +321,7 @@
         }
         case OutputFormat::kHLSL: {
 #if TINT_BUILD_HLSL_WRITER
-            (void)hlsl::writer::Generate(&program, options_hlsl_);
+            (void)hlsl::writer::Generate(program, options_hlsl_);
 #endif  // TINT_BUILD_HLSL_WRITER
             break;
         }
@@ -335,13 +335,11 @@
 
             // Remap resource numbers to a flat namespace.
             // TODO(crbug.com/tint/1501): Do this via Options::BindingMap.
-            auto input_program = &program;
-            auto flattened = tint::writer::FlattenBindings(&program);
-            if (flattened) {
-                input_program = &*flattened;
+            if (auto flattened = tint::writer::FlattenBindings(program)) {
+                program = std::move(*flattened);
             }
 
-            (void)msl::writer::Generate(input_program, options_msl_);
+            (void)msl::writer::Generate(program, options_msl_);
 #endif  // TINT_BUILD_MSL_WRITER
             break;
         }
@@ -350,11 +348,11 @@
     return 0;
 }
 
-void CommonFuzzer::RunInspector(Program* program) {
+void CommonFuzzer::RunInspector(Program& program) {
     inspector::Inspector inspector(program);
-    diagnostics_ = program->Diagnostics();
+    diagnostics_ = program.Diagnostics();
 
-    if (!program->IsValid()) {
+    if (!program.IsValid()) {
         // It's not safe to use the inspector on invalid programs.
         return;
     }
diff --git a/src/tint/fuzzers/tint_common_fuzzer.h b/src/tint/fuzzers/tint_common_fuzzer.h
index 7683066..f0acc37 100644
--- a/src/tint/fuzzers/tint_common_fuzzer.h
+++ b/src/tint/fuzzers/tint_common_fuzzer.h
@@ -142,7 +142,7 @@
 #endif  // TINT_BUILD_WGSL_READER
 
     /// Runs a series of reflection operations to exercise the Inspector API.
-    void RunInspector(Program* program);
+    void RunInspector(Program& program);
 };
 
 }  // namespace tint::fuzzers
diff --git a/src/tint/fuzzers/tint_concurrency_fuzzer.cc b/src/tint/fuzzers/tint_concurrency_fuzzer.cc
index 1d97c8d..9301399 100644
--- a/src/tint/fuzzers/tint_concurrency_fuzzer.cc
+++ b/src/tint/fuzzers/tint_concurrency_fuzzer.cc
@@ -52,7 +52,7 @@
         return 0;
     }
 
-    tint::inspector::Inspector inspector(&program);
+    tint::inspector::Inspector inspector(program);
     auto entry_points = inspector.GetEntryPoints();
     std::string entry_point = entry_points.empty() ? "" : entry_points.front().name;
 
@@ -81,28 +81,28 @@
             switch (static_cast<Writer>(thread_idx % static_cast<size_t>(Writer::kCount))) {
 #if TINT_BUILD_WGSL_WRITER
                 case Writer::kWGSL: {
-                    (void)tint::wgsl::writer::Generate(&program, {});
+                    (void)tint::wgsl::writer::Generate(program, {});
                     break;
                 }
 #endif  // TINT_BUILD_WGSL_WRITER
 
 #if TINT_BUILD_SPV_WRITER
                 case Writer::kSPIRV: {
-                    (void)tint::spirv::writer::Generate(&program, {});
+                    (void)tint::spirv::writer::Generate(program, {});
                     break;
                 }
 #endif  // TINT_BUILD_SPV_WRITER
 
 #if TINT_BUILD_HLSL_WRITER
                 case Writer::kHLSL: {
-                    (void)tint::hlsl::writer::Generate(&program, {});
+                    (void)tint::hlsl::writer::Generate(program, {});
                     break;
                 }
 #endif  // TINT_BUILD_HLSL_WRITER
 
 #if TINT_BUILD_GLSL_WRITER
                 case Writer::kGLSL: {
-                    (void)tint::glsl::writer::Generate(&program, {}, entry_point);
+                    (void)tint::glsl::writer::Generate(program, {}, entry_point);
                     break;
                 }
 #endif  // TINT_BUILD_GLSL_WRITER
@@ -110,8 +110,8 @@
 #if TINT_BUILD_MSL_WRITER
                 case Writer::kMSL: {
                     // Remap resource numbers to a flat namespace.
-                    if (auto flattened = tint::writer::FlattenBindings(&program)) {
-                        (void)tint::msl::writer::Generate(&flattened.value(), {});
+                    if (auto flattened = tint::writer::FlattenBindings(program)) {
+                        (void)tint::msl::writer::Generate(flattened.value(), {});
                     }
                     break;
                 }
diff --git a/src/tint/fuzzers/tint_ir_roundtrip_fuzzer.cc b/src/tint/fuzzers/tint_ir_roundtrip_fuzzer.cc
index 74e198f..58cd879 100644
--- a/src/tint/fuzzers/tint_ir_roundtrip_fuzzer.cc
+++ b/src/tint/fuzzers/tint_ir_roundtrip_fuzzer.cc
@@ -71,7 +71,7 @@
         return 0;
     }
 
-    auto ir = tint::wgsl::reader::ProgramToIR(&src);
+    auto ir = tint::wgsl::reader::ProgramToIR(src);
     if (!ir) {
         std::cerr << ir.Failure() << std::endl;
         __builtin_trap();
@@ -80,7 +80,7 @@
     auto dst = tint::wgsl::writer::IRToProgram(ir.Get());
     if (!dst.IsValid()) {
 #if TINT_BUILD_WGSL_WRITER
-        if (auto result = tint::wgsl::writer::Generate(&dst, {}); result) {
+        if (auto result = tint::wgsl::writer::Generate(dst, {}); result) {
             std::cerr << result->wgsl << std::endl << std::endl;
         }
 #endif
diff --git a/src/tint/lang/core/BUILD.bazel b/src/tint/lang/core/BUILD.bazel
index 8f69c1d..be870fb 100644
--- a/src/tint/lang/core/BUILD.bazel
+++ b/src/tint/lang/core/BUILD.bazel
@@ -30,9 +30,9 @@
     "address_space.cc",
     "attribute.cc",
     "binary_op.cc",
-    "builtin.cc",
+    "builtin_fn.cc",
+    "builtin_type.cc",
     "builtin_value.cc",
-    "function.cc",
     "interpolation_sampling.cc",
     "interpolation_type.cc",
     "number.cc",
@@ -45,11 +45,11 @@
     "address_space.h",
     "attribute.h",
     "binary_op.h",
-    "builtin.h",
+    "builtin_fn.h",
+    "builtin_type.h",
     "builtin_value.h",
     "evaluation_stage.h",
     "fluent_types.h",
-    "function.h",
     "interpolation.h",
     "interpolation_sampling.h",
     "interpolation_type.h",
@@ -76,7 +76,7 @@
     "access_test.cc",
     "address_space_test.cc",
     "attribute_test.cc",
-    "builtin_test.cc",
+    "builtin_type_test.cc",
     "builtin_value_test.cc",
     "interpolation_sampling_test.cc",
     "interpolation_type_test.cc",
@@ -116,7 +116,7 @@
     "access_bench.cc",
     "address_space_bench.cc",
     "attribute_bench.cc",
-    "builtin_bench.cc",
+    "builtin_type_bench.cc",
     "builtin_value_bench.cc",
     "interpolation_sampling_bench.cc",
     "interpolation_type_bench.cc",
diff --git a/src/tint/lang/core/BUILD.cmake b/src/tint/lang/core/BUILD.cmake
index 5e02dac..8c85ad5 100644
--- a/src/tint/lang/core/BUILD.cmake
+++ b/src/tint/lang/core/BUILD.cmake
@@ -39,14 +39,14 @@
   lang/core/attribute.h
   lang/core/binary_op.cc
   lang/core/binary_op.h
-  lang/core/builtin.cc
-  lang/core/builtin.h
+  lang/core/builtin_fn.cc
+  lang/core/builtin_fn.h
+  lang/core/builtin_type.cc
+  lang/core/builtin_type.h
   lang/core/builtin_value.cc
   lang/core/builtin_value.h
   lang/core/evaluation_stage.h
   lang/core/fluent_types.h
-  lang/core/function.cc
-  lang/core/function.h
   lang/core/interpolation.h
   lang/core/interpolation_sampling.cc
   lang/core/interpolation_sampling.h
@@ -79,7 +79,7 @@
   lang/core/access_test.cc
   lang/core/address_space_test.cc
   lang/core/attribute_test.cc
-  lang/core/builtin_test.cc
+  lang/core/builtin_type_test.cc
   lang/core/builtin_value_test.cc
   lang/core/interpolation_sampling_test.cc
   lang/core/interpolation_type_test.cc
@@ -123,7 +123,7 @@
   lang/core/access_bench.cc
   lang/core/address_space_bench.cc
   lang/core/attribute_bench.cc
-  lang/core/builtin_bench.cc
+  lang/core/builtin_type_bench.cc
   lang/core/builtin_value_bench.cc
   lang/core/interpolation_sampling_bench.cc
   lang/core/interpolation_type_bench.cc
diff --git a/src/tint/lang/core/BUILD.gn b/src/tint/lang/core/BUILD.gn
index c096493..59a0588 100644
--- a/src/tint/lang/core/BUILD.gn
+++ b/src/tint/lang/core/BUILD.gn
@@ -39,14 +39,14 @@
     "attribute.h",
     "binary_op.cc",
     "binary_op.h",
-    "builtin.cc",
-    "builtin.h",
+    "builtin_fn.cc",
+    "builtin_fn.h",
+    "builtin_type.cc",
+    "builtin_type.h",
     "builtin_value.cc",
     "builtin_value.h",
     "evaluation_stage.h",
     "fluent_types.h",
-    "function.cc",
-    "function.h",
     "interpolation.h",
     "interpolation_sampling.cc",
     "interpolation_sampling.h",
@@ -77,7 +77,7 @@
       "access_test.cc",
       "address_space_test.cc",
       "attribute_test.cc",
-      "builtin_test.cc",
+      "builtin_type_test.cc",
       "builtin_value_test.cc",
       "interpolation_sampling_test.cc",
       "interpolation_type_test.cc",
diff --git a/src/tint/lang/core/access.def b/src/tint/lang/core/access.def
new file mode 100644
index 0000000..c6a77a1
--- /dev/null
+++ b/src/tint/lang/core/access.def
@@ -0,0 +1,6 @@
+// https://gpuweb.github.io/gpuweb/wgsl/#memory-access-mode
+enum access {
+  read
+  write
+  read_write
+}
diff --git a/src/tint/lang/core/address_space.def b/src/tint/lang/core/address_space.def
new file mode 100644
index 0000000..66a928a
--- /dev/null
+++ b/src/tint/lang/core/address_space.def
@@ -0,0 +1,13 @@
+// https://gpuweb.github.io/gpuweb/wgsl/#storage-class
+enum address_space {
+  function
+  private
+  workgroup
+  uniform
+  storage
+  push_constant
+  pixel_local
+  __in
+  __out
+  @internal handle
+}
diff --git a/src/tint/lang/core/builtin.cc b/src/tint/lang/core/builtin.cc
deleted file mode 100644
index 073b70d..0000000
--- a/src/tint/lang/core/builtin.cc
+++ /dev/null
@@ -1,523 +0,0 @@
-// Copyright 2022 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-////////////////////////////////////////////////////////////////////////////////
-// File generated by 'tools/src/cmd/gen' using the template:
-//   src/tint/lang/core/builtin.cc.tmpl
-//
-// To regenerate run: './tools/run gen'
-//
-//                       Do not modify this file directly
-////////////////////////////////////////////////////////////////////////////////
-
-#include "src/tint/lang/core/builtin.h"
-
-namespace tint::core {
-
-/// ParseBuiltin parses a Builtin from a string.
-/// @param str the string to parse
-/// @returns the parsed enum, or Builtin::kUndefined if the string could not be parsed.
-Builtin ParseBuiltin(std::string_view str) {
-    if (str == "__atomic_compare_exchange_result_i32") {
-        return Builtin::kAtomicCompareExchangeResultI32;
-    }
-    if (str == "__atomic_compare_exchange_result_u32") {
-        return Builtin::kAtomicCompareExchangeResultU32;
-    }
-    if (str == "__frexp_result_abstract") {
-        return Builtin::kFrexpResultAbstract;
-    }
-    if (str == "__frexp_result_f16") {
-        return Builtin::kFrexpResultF16;
-    }
-    if (str == "__frexp_result_f32") {
-        return Builtin::kFrexpResultF32;
-    }
-    if (str == "__frexp_result_vec2_abstract") {
-        return Builtin::kFrexpResultVec2Abstract;
-    }
-    if (str == "__frexp_result_vec2_f16") {
-        return Builtin::kFrexpResultVec2F16;
-    }
-    if (str == "__frexp_result_vec2_f32") {
-        return Builtin::kFrexpResultVec2F32;
-    }
-    if (str == "__frexp_result_vec3_abstract") {
-        return Builtin::kFrexpResultVec3Abstract;
-    }
-    if (str == "__frexp_result_vec3_f16") {
-        return Builtin::kFrexpResultVec3F16;
-    }
-    if (str == "__frexp_result_vec3_f32") {
-        return Builtin::kFrexpResultVec3F32;
-    }
-    if (str == "__frexp_result_vec4_abstract") {
-        return Builtin::kFrexpResultVec4Abstract;
-    }
-    if (str == "__frexp_result_vec4_f16") {
-        return Builtin::kFrexpResultVec4F16;
-    }
-    if (str == "__frexp_result_vec4_f32") {
-        return Builtin::kFrexpResultVec4F32;
-    }
-    if (str == "__modf_result_abstract") {
-        return Builtin::kModfResultAbstract;
-    }
-    if (str == "__modf_result_f16") {
-        return Builtin::kModfResultF16;
-    }
-    if (str == "__modf_result_f32") {
-        return Builtin::kModfResultF32;
-    }
-    if (str == "__modf_result_vec2_abstract") {
-        return Builtin::kModfResultVec2Abstract;
-    }
-    if (str == "__modf_result_vec2_f16") {
-        return Builtin::kModfResultVec2F16;
-    }
-    if (str == "__modf_result_vec2_f32") {
-        return Builtin::kModfResultVec2F32;
-    }
-    if (str == "__modf_result_vec3_abstract") {
-        return Builtin::kModfResultVec3Abstract;
-    }
-    if (str == "__modf_result_vec3_f16") {
-        return Builtin::kModfResultVec3F16;
-    }
-    if (str == "__modf_result_vec3_f32") {
-        return Builtin::kModfResultVec3F32;
-    }
-    if (str == "__modf_result_vec4_abstract") {
-        return Builtin::kModfResultVec4Abstract;
-    }
-    if (str == "__modf_result_vec4_f16") {
-        return Builtin::kModfResultVec4F16;
-    }
-    if (str == "__modf_result_vec4_f32") {
-        return Builtin::kModfResultVec4F32;
-    }
-    if (str == "__packed_vec3") {
-        return Builtin::kPackedVec3;
-    }
-    if (str == "array") {
-        return Builtin::kArray;
-    }
-    if (str == "atomic") {
-        return Builtin::kAtomic;
-    }
-    if (str == "bool") {
-        return Builtin::kBool;
-    }
-    if (str == "f16") {
-        return Builtin::kF16;
-    }
-    if (str == "f32") {
-        return Builtin::kF32;
-    }
-    if (str == "i32") {
-        return Builtin::kI32;
-    }
-    if (str == "mat2x2") {
-        return Builtin::kMat2X2;
-    }
-    if (str == "mat2x2f") {
-        return Builtin::kMat2X2F;
-    }
-    if (str == "mat2x2h") {
-        return Builtin::kMat2X2H;
-    }
-    if (str == "mat2x3") {
-        return Builtin::kMat2X3;
-    }
-    if (str == "mat2x3f") {
-        return Builtin::kMat2X3F;
-    }
-    if (str == "mat2x3h") {
-        return Builtin::kMat2X3H;
-    }
-    if (str == "mat2x4") {
-        return Builtin::kMat2X4;
-    }
-    if (str == "mat2x4f") {
-        return Builtin::kMat2X4F;
-    }
-    if (str == "mat2x4h") {
-        return Builtin::kMat2X4H;
-    }
-    if (str == "mat3x2") {
-        return Builtin::kMat3X2;
-    }
-    if (str == "mat3x2f") {
-        return Builtin::kMat3X2F;
-    }
-    if (str == "mat3x2h") {
-        return Builtin::kMat3X2H;
-    }
-    if (str == "mat3x3") {
-        return Builtin::kMat3X3;
-    }
-    if (str == "mat3x3f") {
-        return Builtin::kMat3X3F;
-    }
-    if (str == "mat3x3h") {
-        return Builtin::kMat3X3H;
-    }
-    if (str == "mat3x4") {
-        return Builtin::kMat3X4;
-    }
-    if (str == "mat3x4f") {
-        return Builtin::kMat3X4F;
-    }
-    if (str == "mat3x4h") {
-        return Builtin::kMat3X4H;
-    }
-    if (str == "mat4x2") {
-        return Builtin::kMat4X2;
-    }
-    if (str == "mat4x2f") {
-        return Builtin::kMat4X2F;
-    }
-    if (str == "mat4x2h") {
-        return Builtin::kMat4X2H;
-    }
-    if (str == "mat4x3") {
-        return Builtin::kMat4X3;
-    }
-    if (str == "mat4x3f") {
-        return Builtin::kMat4X3F;
-    }
-    if (str == "mat4x3h") {
-        return Builtin::kMat4X3H;
-    }
-    if (str == "mat4x4") {
-        return Builtin::kMat4X4;
-    }
-    if (str == "mat4x4f") {
-        return Builtin::kMat4X4F;
-    }
-    if (str == "mat4x4h") {
-        return Builtin::kMat4X4H;
-    }
-    if (str == "ptr") {
-        return Builtin::kPtr;
-    }
-    if (str == "sampler") {
-        return Builtin::kSampler;
-    }
-    if (str == "sampler_comparison") {
-        return Builtin::kSamplerComparison;
-    }
-    if (str == "texture_1d") {
-        return Builtin::kTexture1D;
-    }
-    if (str == "texture_2d") {
-        return Builtin::kTexture2D;
-    }
-    if (str == "texture_2d_array") {
-        return Builtin::kTexture2DArray;
-    }
-    if (str == "texture_3d") {
-        return Builtin::kTexture3D;
-    }
-    if (str == "texture_cube") {
-        return Builtin::kTextureCube;
-    }
-    if (str == "texture_cube_array") {
-        return Builtin::kTextureCubeArray;
-    }
-    if (str == "texture_depth_2d") {
-        return Builtin::kTextureDepth2D;
-    }
-    if (str == "texture_depth_2d_array") {
-        return Builtin::kTextureDepth2DArray;
-    }
-    if (str == "texture_depth_cube") {
-        return Builtin::kTextureDepthCube;
-    }
-    if (str == "texture_depth_cube_array") {
-        return Builtin::kTextureDepthCubeArray;
-    }
-    if (str == "texture_depth_multisampled_2d") {
-        return Builtin::kTextureDepthMultisampled2D;
-    }
-    if (str == "texture_external") {
-        return Builtin::kTextureExternal;
-    }
-    if (str == "texture_multisampled_2d") {
-        return Builtin::kTextureMultisampled2D;
-    }
-    if (str == "texture_storage_1d") {
-        return Builtin::kTextureStorage1D;
-    }
-    if (str == "texture_storage_2d") {
-        return Builtin::kTextureStorage2D;
-    }
-    if (str == "texture_storage_2d_array") {
-        return Builtin::kTextureStorage2DArray;
-    }
-    if (str == "texture_storage_3d") {
-        return Builtin::kTextureStorage3D;
-    }
-    if (str == "u32") {
-        return Builtin::kU32;
-    }
-    if (str == "vec2") {
-        return Builtin::kVec2;
-    }
-    if (str == "vec2f") {
-        return Builtin::kVec2F;
-    }
-    if (str == "vec2h") {
-        return Builtin::kVec2H;
-    }
-    if (str == "vec2i") {
-        return Builtin::kVec2I;
-    }
-    if (str == "vec2u") {
-        return Builtin::kVec2U;
-    }
-    if (str == "vec3") {
-        return Builtin::kVec3;
-    }
-    if (str == "vec3f") {
-        return Builtin::kVec3F;
-    }
-    if (str == "vec3h") {
-        return Builtin::kVec3H;
-    }
-    if (str == "vec3i") {
-        return Builtin::kVec3I;
-    }
-    if (str == "vec3u") {
-        return Builtin::kVec3U;
-    }
-    if (str == "vec4") {
-        return Builtin::kVec4;
-    }
-    if (str == "vec4f") {
-        return Builtin::kVec4F;
-    }
-    if (str == "vec4h") {
-        return Builtin::kVec4H;
-    }
-    if (str == "vec4i") {
-        return Builtin::kVec4I;
-    }
-    if (str == "vec4u") {
-        return Builtin::kVec4U;
-    }
-    return Builtin::kUndefined;
-}
-
-std::string_view ToString(Builtin value) {
-    switch (value) {
-        case Builtin::kUndefined:
-            return "undefined";
-        case Builtin::kAtomicCompareExchangeResultI32:
-            return "__atomic_compare_exchange_result_i32";
-        case Builtin::kAtomicCompareExchangeResultU32:
-            return "__atomic_compare_exchange_result_u32";
-        case Builtin::kFrexpResultAbstract:
-            return "__frexp_result_abstract";
-        case Builtin::kFrexpResultF16:
-            return "__frexp_result_f16";
-        case Builtin::kFrexpResultF32:
-            return "__frexp_result_f32";
-        case Builtin::kFrexpResultVec2Abstract:
-            return "__frexp_result_vec2_abstract";
-        case Builtin::kFrexpResultVec2F16:
-            return "__frexp_result_vec2_f16";
-        case Builtin::kFrexpResultVec2F32:
-            return "__frexp_result_vec2_f32";
-        case Builtin::kFrexpResultVec3Abstract:
-            return "__frexp_result_vec3_abstract";
-        case Builtin::kFrexpResultVec3F16:
-            return "__frexp_result_vec3_f16";
-        case Builtin::kFrexpResultVec3F32:
-            return "__frexp_result_vec3_f32";
-        case Builtin::kFrexpResultVec4Abstract:
-            return "__frexp_result_vec4_abstract";
-        case Builtin::kFrexpResultVec4F16:
-            return "__frexp_result_vec4_f16";
-        case Builtin::kFrexpResultVec4F32:
-            return "__frexp_result_vec4_f32";
-        case Builtin::kModfResultAbstract:
-            return "__modf_result_abstract";
-        case Builtin::kModfResultF16:
-            return "__modf_result_f16";
-        case Builtin::kModfResultF32:
-            return "__modf_result_f32";
-        case Builtin::kModfResultVec2Abstract:
-            return "__modf_result_vec2_abstract";
-        case Builtin::kModfResultVec2F16:
-            return "__modf_result_vec2_f16";
-        case Builtin::kModfResultVec2F32:
-            return "__modf_result_vec2_f32";
-        case Builtin::kModfResultVec3Abstract:
-            return "__modf_result_vec3_abstract";
-        case Builtin::kModfResultVec3F16:
-            return "__modf_result_vec3_f16";
-        case Builtin::kModfResultVec3F32:
-            return "__modf_result_vec3_f32";
-        case Builtin::kModfResultVec4Abstract:
-            return "__modf_result_vec4_abstract";
-        case Builtin::kModfResultVec4F16:
-            return "__modf_result_vec4_f16";
-        case Builtin::kModfResultVec4F32:
-            return "__modf_result_vec4_f32";
-        case Builtin::kPackedVec3:
-            return "__packed_vec3";
-        case Builtin::kArray:
-            return "array";
-        case Builtin::kAtomic:
-            return "atomic";
-        case Builtin::kBool:
-            return "bool";
-        case Builtin::kF16:
-            return "f16";
-        case Builtin::kF32:
-            return "f32";
-        case Builtin::kI32:
-            return "i32";
-        case Builtin::kMat2X2:
-            return "mat2x2";
-        case Builtin::kMat2X2F:
-            return "mat2x2f";
-        case Builtin::kMat2X2H:
-            return "mat2x2h";
-        case Builtin::kMat2X3:
-            return "mat2x3";
-        case Builtin::kMat2X3F:
-            return "mat2x3f";
-        case Builtin::kMat2X3H:
-            return "mat2x3h";
-        case Builtin::kMat2X4:
-            return "mat2x4";
-        case Builtin::kMat2X4F:
-            return "mat2x4f";
-        case Builtin::kMat2X4H:
-            return "mat2x4h";
-        case Builtin::kMat3X2:
-            return "mat3x2";
-        case Builtin::kMat3X2F:
-            return "mat3x2f";
-        case Builtin::kMat3X2H:
-            return "mat3x2h";
-        case Builtin::kMat3X3:
-            return "mat3x3";
-        case Builtin::kMat3X3F:
-            return "mat3x3f";
-        case Builtin::kMat3X3H:
-            return "mat3x3h";
-        case Builtin::kMat3X4:
-            return "mat3x4";
-        case Builtin::kMat3X4F:
-            return "mat3x4f";
-        case Builtin::kMat3X4H:
-            return "mat3x4h";
-        case Builtin::kMat4X2:
-            return "mat4x2";
-        case Builtin::kMat4X2F:
-            return "mat4x2f";
-        case Builtin::kMat4X2H:
-            return "mat4x2h";
-        case Builtin::kMat4X3:
-            return "mat4x3";
-        case Builtin::kMat4X3F:
-            return "mat4x3f";
-        case Builtin::kMat4X3H:
-            return "mat4x3h";
-        case Builtin::kMat4X4:
-            return "mat4x4";
-        case Builtin::kMat4X4F:
-            return "mat4x4f";
-        case Builtin::kMat4X4H:
-            return "mat4x4h";
-        case Builtin::kPtr:
-            return "ptr";
-        case Builtin::kSampler:
-            return "sampler";
-        case Builtin::kSamplerComparison:
-            return "sampler_comparison";
-        case Builtin::kTexture1D:
-            return "texture_1d";
-        case Builtin::kTexture2D:
-            return "texture_2d";
-        case Builtin::kTexture2DArray:
-            return "texture_2d_array";
-        case Builtin::kTexture3D:
-            return "texture_3d";
-        case Builtin::kTextureCube:
-            return "texture_cube";
-        case Builtin::kTextureCubeArray:
-            return "texture_cube_array";
-        case Builtin::kTextureDepth2D:
-            return "texture_depth_2d";
-        case Builtin::kTextureDepth2DArray:
-            return "texture_depth_2d_array";
-        case Builtin::kTextureDepthCube:
-            return "texture_depth_cube";
-        case Builtin::kTextureDepthCubeArray:
-            return "texture_depth_cube_array";
-        case Builtin::kTextureDepthMultisampled2D:
-            return "texture_depth_multisampled_2d";
-        case Builtin::kTextureExternal:
-            return "texture_external";
-        case Builtin::kTextureMultisampled2D:
-            return "texture_multisampled_2d";
-        case Builtin::kTextureStorage1D:
-            return "texture_storage_1d";
-        case Builtin::kTextureStorage2D:
-            return "texture_storage_2d";
-        case Builtin::kTextureStorage2DArray:
-            return "texture_storage_2d_array";
-        case Builtin::kTextureStorage3D:
-            return "texture_storage_3d";
-        case Builtin::kU32:
-            return "u32";
-        case Builtin::kVec2:
-            return "vec2";
-        case Builtin::kVec2F:
-            return "vec2f";
-        case Builtin::kVec2H:
-            return "vec2h";
-        case Builtin::kVec2I:
-            return "vec2i";
-        case Builtin::kVec2U:
-            return "vec2u";
-        case Builtin::kVec3:
-            return "vec3";
-        case Builtin::kVec3F:
-            return "vec3f";
-        case Builtin::kVec3H:
-            return "vec3h";
-        case Builtin::kVec3I:
-            return "vec3i";
-        case Builtin::kVec3U:
-            return "vec3u";
-        case Builtin::kVec4:
-            return "vec4";
-        case Builtin::kVec4F:
-            return "vec4f";
-        case Builtin::kVec4H:
-            return "vec4h";
-        case Builtin::kVec4I:
-            return "vec4i";
-        case Builtin::kVec4U:
-            return "vec4u";
-    }
-    return "<unknown>";
-}
-
-}  // namespace tint::core
diff --git a/src/tint/lang/core/builtin_fn.cc b/src/tint/lang/core/builtin_fn.cc
new file mode 100644
index 0000000..c74963f
--- /dev/null
+++ b/src/tint/lang/core/builtin_fn.cc
@@ -0,0 +1,713 @@
+// Copyright 2023 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+////////////////////////////////////////////////////////////////////////////////
+// File generated by 'tools/src/cmd/gen' using the template:
+//   src/tint/lang/core/builtin_fn.cc.tmpl
+//
+// To regenerate run: './tools/run gen'
+//
+//                       Do not modify this file directly
+////////////////////////////////////////////////////////////////////////////////
+
+#include "src/tint/lang/core/builtin_fn.h"
+
+namespace tint::core {
+
+BuiltinFn ParseBuiltinFn(std::string_view name) {
+    if (name == "abs") {
+        return BuiltinFn::kAbs;
+    }
+    if (name == "acos") {
+        return BuiltinFn::kAcos;
+    }
+    if (name == "acosh") {
+        return BuiltinFn::kAcosh;
+    }
+    if (name == "all") {
+        return BuiltinFn::kAll;
+    }
+    if (name == "any") {
+        return BuiltinFn::kAny;
+    }
+    if (name == "arrayLength") {
+        return BuiltinFn::kArrayLength;
+    }
+    if (name == "asin") {
+        return BuiltinFn::kAsin;
+    }
+    if (name == "asinh") {
+        return BuiltinFn::kAsinh;
+    }
+    if (name == "atan") {
+        return BuiltinFn::kAtan;
+    }
+    if (name == "atan2") {
+        return BuiltinFn::kAtan2;
+    }
+    if (name == "atanh") {
+        return BuiltinFn::kAtanh;
+    }
+    if (name == "ceil") {
+        return BuiltinFn::kCeil;
+    }
+    if (name == "clamp") {
+        return BuiltinFn::kClamp;
+    }
+    if (name == "cos") {
+        return BuiltinFn::kCos;
+    }
+    if (name == "cosh") {
+        return BuiltinFn::kCosh;
+    }
+    if (name == "countLeadingZeros") {
+        return BuiltinFn::kCountLeadingZeros;
+    }
+    if (name == "countOneBits") {
+        return BuiltinFn::kCountOneBits;
+    }
+    if (name == "countTrailingZeros") {
+        return BuiltinFn::kCountTrailingZeros;
+    }
+    if (name == "cross") {
+        return BuiltinFn::kCross;
+    }
+    if (name == "degrees") {
+        return BuiltinFn::kDegrees;
+    }
+    if (name == "determinant") {
+        return BuiltinFn::kDeterminant;
+    }
+    if (name == "distance") {
+        return BuiltinFn::kDistance;
+    }
+    if (name == "dot") {
+        return BuiltinFn::kDot;
+    }
+    if (name == "dot4I8Packed") {
+        return BuiltinFn::kDot4I8Packed;
+    }
+    if (name == "dot4U8Packed") {
+        return BuiltinFn::kDot4U8Packed;
+    }
+    if (name == "dpdx") {
+        return BuiltinFn::kDpdx;
+    }
+    if (name == "dpdxCoarse") {
+        return BuiltinFn::kDpdxCoarse;
+    }
+    if (name == "dpdxFine") {
+        return BuiltinFn::kDpdxFine;
+    }
+    if (name == "dpdy") {
+        return BuiltinFn::kDpdy;
+    }
+    if (name == "dpdyCoarse") {
+        return BuiltinFn::kDpdyCoarse;
+    }
+    if (name == "dpdyFine") {
+        return BuiltinFn::kDpdyFine;
+    }
+    if (name == "exp") {
+        return BuiltinFn::kExp;
+    }
+    if (name == "exp2") {
+        return BuiltinFn::kExp2;
+    }
+    if (name == "extractBits") {
+        return BuiltinFn::kExtractBits;
+    }
+    if (name == "faceForward") {
+        return BuiltinFn::kFaceForward;
+    }
+    if (name == "firstLeadingBit") {
+        return BuiltinFn::kFirstLeadingBit;
+    }
+    if (name == "firstTrailingBit") {
+        return BuiltinFn::kFirstTrailingBit;
+    }
+    if (name == "floor") {
+        return BuiltinFn::kFloor;
+    }
+    if (name == "fma") {
+        return BuiltinFn::kFma;
+    }
+    if (name == "fract") {
+        return BuiltinFn::kFract;
+    }
+    if (name == "frexp") {
+        return BuiltinFn::kFrexp;
+    }
+    if (name == "fwidth") {
+        return BuiltinFn::kFwidth;
+    }
+    if (name == "fwidthCoarse") {
+        return BuiltinFn::kFwidthCoarse;
+    }
+    if (name == "fwidthFine") {
+        return BuiltinFn::kFwidthFine;
+    }
+    if (name == "insertBits") {
+        return BuiltinFn::kInsertBits;
+    }
+    if (name == "inverseSqrt") {
+        return BuiltinFn::kInverseSqrt;
+    }
+    if (name == "ldexp") {
+        return BuiltinFn::kLdexp;
+    }
+    if (name == "length") {
+        return BuiltinFn::kLength;
+    }
+    if (name == "log") {
+        return BuiltinFn::kLog;
+    }
+    if (name == "log2") {
+        return BuiltinFn::kLog2;
+    }
+    if (name == "max") {
+        return BuiltinFn::kMax;
+    }
+    if (name == "min") {
+        return BuiltinFn::kMin;
+    }
+    if (name == "mix") {
+        return BuiltinFn::kMix;
+    }
+    if (name == "modf") {
+        return BuiltinFn::kModf;
+    }
+    if (name == "normalize") {
+        return BuiltinFn::kNormalize;
+    }
+    if (name == "pack2x16float") {
+        return BuiltinFn::kPack2X16Float;
+    }
+    if (name == "pack2x16snorm") {
+        return BuiltinFn::kPack2X16Snorm;
+    }
+    if (name == "pack2x16unorm") {
+        return BuiltinFn::kPack2X16Unorm;
+    }
+    if (name == "pack4x8snorm") {
+        return BuiltinFn::kPack4X8Snorm;
+    }
+    if (name == "pack4x8unorm") {
+        return BuiltinFn::kPack4X8Unorm;
+    }
+    if (name == "pow") {
+        return BuiltinFn::kPow;
+    }
+    if (name == "quantizeToF16") {
+        return BuiltinFn::kQuantizeToF16;
+    }
+    if (name == "radians") {
+        return BuiltinFn::kRadians;
+    }
+    if (name == "reflect") {
+        return BuiltinFn::kReflect;
+    }
+    if (name == "refract") {
+        return BuiltinFn::kRefract;
+    }
+    if (name == "reverseBits") {
+        return BuiltinFn::kReverseBits;
+    }
+    if (name == "round") {
+        return BuiltinFn::kRound;
+    }
+    if (name == "saturate") {
+        return BuiltinFn::kSaturate;
+    }
+    if (name == "select") {
+        return BuiltinFn::kSelect;
+    }
+    if (name == "sign") {
+        return BuiltinFn::kSign;
+    }
+    if (name == "sin") {
+        return BuiltinFn::kSin;
+    }
+    if (name == "sinh") {
+        return BuiltinFn::kSinh;
+    }
+    if (name == "smoothstep") {
+        return BuiltinFn::kSmoothstep;
+    }
+    if (name == "sqrt") {
+        return BuiltinFn::kSqrt;
+    }
+    if (name == "step") {
+        return BuiltinFn::kStep;
+    }
+    if (name == "storageBarrier") {
+        return BuiltinFn::kStorageBarrier;
+    }
+    if (name == "tan") {
+        return BuiltinFn::kTan;
+    }
+    if (name == "tanh") {
+        return BuiltinFn::kTanh;
+    }
+    if (name == "transpose") {
+        return BuiltinFn::kTranspose;
+    }
+    if (name == "trunc") {
+        return BuiltinFn::kTrunc;
+    }
+    if (name == "unpack2x16float") {
+        return BuiltinFn::kUnpack2X16Float;
+    }
+    if (name == "unpack2x16snorm") {
+        return BuiltinFn::kUnpack2X16Snorm;
+    }
+    if (name == "unpack2x16unorm") {
+        return BuiltinFn::kUnpack2X16Unorm;
+    }
+    if (name == "unpack4x8snorm") {
+        return BuiltinFn::kUnpack4X8Snorm;
+    }
+    if (name == "unpack4x8unorm") {
+        return BuiltinFn::kUnpack4X8Unorm;
+    }
+    if (name == "workgroupBarrier") {
+        return BuiltinFn::kWorkgroupBarrier;
+    }
+    if (name == "workgroupUniformLoad") {
+        return BuiltinFn::kWorkgroupUniformLoad;
+    }
+    if (name == "textureBarrier") {
+        return BuiltinFn::kTextureBarrier;
+    }
+    if (name == "textureDimensions") {
+        return BuiltinFn::kTextureDimensions;
+    }
+    if (name == "textureGather") {
+        return BuiltinFn::kTextureGather;
+    }
+    if (name == "textureGatherCompare") {
+        return BuiltinFn::kTextureGatherCompare;
+    }
+    if (name == "textureNumLayers") {
+        return BuiltinFn::kTextureNumLayers;
+    }
+    if (name == "textureNumLevels") {
+        return BuiltinFn::kTextureNumLevels;
+    }
+    if (name == "textureNumSamples") {
+        return BuiltinFn::kTextureNumSamples;
+    }
+    if (name == "textureSample") {
+        return BuiltinFn::kTextureSample;
+    }
+    if (name == "textureSampleBias") {
+        return BuiltinFn::kTextureSampleBias;
+    }
+    if (name == "textureSampleCompare") {
+        return BuiltinFn::kTextureSampleCompare;
+    }
+    if (name == "textureSampleCompareLevel") {
+        return BuiltinFn::kTextureSampleCompareLevel;
+    }
+    if (name == "textureSampleGrad") {
+        return BuiltinFn::kTextureSampleGrad;
+    }
+    if (name == "textureSampleLevel") {
+        return BuiltinFn::kTextureSampleLevel;
+    }
+    if (name == "textureSampleBaseClampToEdge") {
+        return BuiltinFn::kTextureSampleBaseClampToEdge;
+    }
+    if (name == "textureStore") {
+        return BuiltinFn::kTextureStore;
+    }
+    if (name == "textureLoad") {
+        return BuiltinFn::kTextureLoad;
+    }
+    if (name == "atomicLoad") {
+        return BuiltinFn::kAtomicLoad;
+    }
+    if (name == "atomicStore") {
+        return BuiltinFn::kAtomicStore;
+    }
+    if (name == "atomicAdd") {
+        return BuiltinFn::kAtomicAdd;
+    }
+    if (name == "atomicSub") {
+        return BuiltinFn::kAtomicSub;
+    }
+    if (name == "atomicMax") {
+        return BuiltinFn::kAtomicMax;
+    }
+    if (name == "atomicMin") {
+        return BuiltinFn::kAtomicMin;
+    }
+    if (name == "atomicAnd") {
+        return BuiltinFn::kAtomicAnd;
+    }
+    if (name == "atomicOr") {
+        return BuiltinFn::kAtomicOr;
+    }
+    if (name == "atomicXor") {
+        return BuiltinFn::kAtomicXor;
+    }
+    if (name == "atomicExchange") {
+        return BuiltinFn::kAtomicExchange;
+    }
+    if (name == "atomicCompareExchangeWeak") {
+        return BuiltinFn::kAtomicCompareExchangeWeak;
+    }
+    if (name == "subgroupBallot") {
+        return BuiltinFn::kSubgroupBallot;
+    }
+    if (name == "subgroupBroadcast") {
+        return BuiltinFn::kSubgroupBroadcast;
+    }
+    if (name == "_tint_materialize") {
+        return BuiltinFn::kTintMaterialize;
+    }
+    return BuiltinFn::kNone;
+}
+
+const char* str(BuiltinFn i) {
+    switch (i) {
+        case BuiltinFn::kNone:
+            return "<none>";
+        case BuiltinFn::kAbs:
+            return "abs";
+        case BuiltinFn::kAcos:
+            return "acos";
+        case BuiltinFn::kAcosh:
+            return "acosh";
+        case BuiltinFn::kAll:
+            return "all";
+        case BuiltinFn::kAny:
+            return "any";
+        case BuiltinFn::kArrayLength:
+            return "arrayLength";
+        case BuiltinFn::kAsin:
+            return "asin";
+        case BuiltinFn::kAsinh:
+            return "asinh";
+        case BuiltinFn::kAtan:
+            return "atan";
+        case BuiltinFn::kAtan2:
+            return "atan2";
+        case BuiltinFn::kAtanh:
+            return "atanh";
+        case BuiltinFn::kCeil:
+            return "ceil";
+        case BuiltinFn::kClamp:
+            return "clamp";
+        case BuiltinFn::kCos:
+            return "cos";
+        case BuiltinFn::kCosh:
+            return "cosh";
+        case BuiltinFn::kCountLeadingZeros:
+            return "countLeadingZeros";
+        case BuiltinFn::kCountOneBits:
+            return "countOneBits";
+        case BuiltinFn::kCountTrailingZeros:
+            return "countTrailingZeros";
+        case BuiltinFn::kCross:
+            return "cross";
+        case BuiltinFn::kDegrees:
+            return "degrees";
+        case BuiltinFn::kDeterminant:
+            return "determinant";
+        case BuiltinFn::kDistance:
+            return "distance";
+        case BuiltinFn::kDot:
+            return "dot";
+        case BuiltinFn::kDot4I8Packed:
+            return "dot4I8Packed";
+        case BuiltinFn::kDot4U8Packed:
+            return "dot4U8Packed";
+        case BuiltinFn::kDpdx:
+            return "dpdx";
+        case BuiltinFn::kDpdxCoarse:
+            return "dpdxCoarse";
+        case BuiltinFn::kDpdxFine:
+            return "dpdxFine";
+        case BuiltinFn::kDpdy:
+            return "dpdy";
+        case BuiltinFn::kDpdyCoarse:
+            return "dpdyCoarse";
+        case BuiltinFn::kDpdyFine:
+            return "dpdyFine";
+        case BuiltinFn::kExp:
+            return "exp";
+        case BuiltinFn::kExp2:
+            return "exp2";
+        case BuiltinFn::kExtractBits:
+            return "extractBits";
+        case BuiltinFn::kFaceForward:
+            return "faceForward";
+        case BuiltinFn::kFirstLeadingBit:
+            return "firstLeadingBit";
+        case BuiltinFn::kFirstTrailingBit:
+            return "firstTrailingBit";
+        case BuiltinFn::kFloor:
+            return "floor";
+        case BuiltinFn::kFma:
+            return "fma";
+        case BuiltinFn::kFract:
+            return "fract";
+        case BuiltinFn::kFrexp:
+            return "frexp";
+        case BuiltinFn::kFwidth:
+            return "fwidth";
+        case BuiltinFn::kFwidthCoarse:
+            return "fwidthCoarse";
+        case BuiltinFn::kFwidthFine:
+            return "fwidthFine";
+        case BuiltinFn::kInsertBits:
+            return "insertBits";
+        case BuiltinFn::kInverseSqrt:
+            return "inverseSqrt";
+        case BuiltinFn::kLdexp:
+            return "ldexp";
+        case BuiltinFn::kLength:
+            return "length";
+        case BuiltinFn::kLog:
+            return "log";
+        case BuiltinFn::kLog2:
+            return "log2";
+        case BuiltinFn::kMax:
+            return "max";
+        case BuiltinFn::kMin:
+            return "min";
+        case BuiltinFn::kMix:
+            return "mix";
+        case BuiltinFn::kModf:
+            return "modf";
+        case BuiltinFn::kNormalize:
+            return "normalize";
+        case BuiltinFn::kPack2X16Float:
+            return "pack2x16float";
+        case BuiltinFn::kPack2X16Snorm:
+            return "pack2x16snorm";
+        case BuiltinFn::kPack2X16Unorm:
+            return "pack2x16unorm";
+        case BuiltinFn::kPack4X8Snorm:
+            return "pack4x8snorm";
+        case BuiltinFn::kPack4X8Unorm:
+            return "pack4x8unorm";
+        case BuiltinFn::kPow:
+            return "pow";
+        case BuiltinFn::kQuantizeToF16:
+            return "quantizeToF16";
+        case BuiltinFn::kRadians:
+            return "radians";
+        case BuiltinFn::kReflect:
+            return "reflect";
+        case BuiltinFn::kRefract:
+            return "refract";
+        case BuiltinFn::kReverseBits:
+            return "reverseBits";
+        case BuiltinFn::kRound:
+            return "round";
+        case BuiltinFn::kSaturate:
+            return "saturate";
+        case BuiltinFn::kSelect:
+            return "select";
+        case BuiltinFn::kSign:
+            return "sign";
+        case BuiltinFn::kSin:
+            return "sin";
+        case BuiltinFn::kSinh:
+            return "sinh";
+        case BuiltinFn::kSmoothstep:
+            return "smoothstep";
+        case BuiltinFn::kSqrt:
+            return "sqrt";
+        case BuiltinFn::kStep:
+            return "step";
+        case BuiltinFn::kStorageBarrier:
+            return "storageBarrier";
+        case BuiltinFn::kTan:
+            return "tan";
+        case BuiltinFn::kTanh:
+            return "tanh";
+        case BuiltinFn::kTranspose:
+            return "transpose";
+        case BuiltinFn::kTrunc:
+            return "trunc";
+        case BuiltinFn::kUnpack2X16Float:
+            return "unpack2x16float";
+        case BuiltinFn::kUnpack2X16Snorm:
+            return "unpack2x16snorm";
+        case BuiltinFn::kUnpack2X16Unorm:
+            return "unpack2x16unorm";
+        case BuiltinFn::kUnpack4X8Snorm:
+            return "unpack4x8snorm";
+        case BuiltinFn::kUnpack4X8Unorm:
+            return "unpack4x8unorm";
+        case BuiltinFn::kWorkgroupBarrier:
+            return "workgroupBarrier";
+        case BuiltinFn::kWorkgroupUniformLoad:
+            return "workgroupUniformLoad";
+        case BuiltinFn::kTextureBarrier:
+            return "textureBarrier";
+        case BuiltinFn::kTextureDimensions:
+            return "textureDimensions";
+        case BuiltinFn::kTextureGather:
+            return "textureGather";
+        case BuiltinFn::kTextureGatherCompare:
+            return "textureGatherCompare";
+        case BuiltinFn::kTextureNumLayers:
+            return "textureNumLayers";
+        case BuiltinFn::kTextureNumLevels:
+            return "textureNumLevels";
+        case BuiltinFn::kTextureNumSamples:
+            return "textureNumSamples";
+        case BuiltinFn::kTextureSample:
+            return "textureSample";
+        case BuiltinFn::kTextureSampleBias:
+            return "textureSampleBias";
+        case BuiltinFn::kTextureSampleCompare:
+            return "textureSampleCompare";
+        case BuiltinFn::kTextureSampleCompareLevel:
+            return "textureSampleCompareLevel";
+        case BuiltinFn::kTextureSampleGrad:
+            return "textureSampleGrad";
+        case BuiltinFn::kTextureSampleLevel:
+            return "textureSampleLevel";
+        case BuiltinFn::kTextureSampleBaseClampToEdge:
+            return "textureSampleBaseClampToEdge";
+        case BuiltinFn::kTextureStore:
+            return "textureStore";
+        case BuiltinFn::kTextureLoad:
+            return "textureLoad";
+        case BuiltinFn::kAtomicLoad:
+            return "atomicLoad";
+        case BuiltinFn::kAtomicStore:
+            return "atomicStore";
+        case BuiltinFn::kAtomicAdd:
+            return "atomicAdd";
+        case BuiltinFn::kAtomicSub:
+            return "atomicSub";
+        case BuiltinFn::kAtomicMax:
+            return "atomicMax";
+        case BuiltinFn::kAtomicMin:
+            return "atomicMin";
+        case BuiltinFn::kAtomicAnd:
+            return "atomicAnd";
+        case BuiltinFn::kAtomicOr:
+            return "atomicOr";
+        case BuiltinFn::kAtomicXor:
+            return "atomicXor";
+        case BuiltinFn::kAtomicExchange:
+            return "atomicExchange";
+        case BuiltinFn::kAtomicCompareExchangeWeak:
+            return "atomicCompareExchangeWeak";
+        case BuiltinFn::kSubgroupBallot:
+            return "subgroupBallot";
+        case BuiltinFn::kSubgroupBroadcast:
+            return "subgroupBroadcast";
+        case BuiltinFn::kTintMaterialize:
+            return "_tint_materialize";
+    }
+    return "<unknown>";
+}
+
+bool IsCoarseDerivative(BuiltinFn f) {
+    return f == BuiltinFn::kDpdxCoarse || f == BuiltinFn::kDpdyCoarse ||
+           f == BuiltinFn::kFwidthCoarse;
+}
+
+bool IsFineDerivative(BuiltinFn f) {
+    return f == BuiltinFn::kDpdxFine || f == BuiltinFn::kDpdyFine || f == BuiltinFn::kFwidthFine;
+}
+
+bool IsDerivative(BuiltinFn f) {
+    return f == BuiltinFn::kDpdx || f == BuiltinFn::kDpdy || f == BuiltinFn::kFwidth ||
+           IsCoarseDerivative(f) || IsFineDerivative(f);
+}
+
+bool IsTexture(BuiltinFn f) {
+    return IsImageQuery(f) ||                                //
+           f == BuiltinFn::kTextureGather ||                 //
+           f == BuiltinFn::kTextureGatherCompare ||          //
+           f == BuiltinFn::kTextureLoad ||                   //
+           f == BuiltinFn::kTextureSample ||                 //
+           f == BuiltinFn::kTextureSampleBaseClampToEdge ||  //
+           f == BuiltinFn::kTextureSampleBias ||             //
+           f == BuiltinFn::kTextureSampleCompare ||          //
+           f == BuiltinFn::kTextureSampleCompareLevel ||     //
+           f == BuiltinFn::kTextureSampleGrad ||             //
+           f == BuiltinFn::kTextureSampleLevel ||            //
+           f == BuiltinFn::kTextureStore;
+}
+
+bool IsImageQuery(BuiltinFn f) {
+    return f == BuiltinFn::kTextureDimensions || f == BuiltinFn::kTextureNumLayers ||
+           f == BuiltinFn::kTextureNumLevels || f == BuiltinFn::kTextureNumSamples;
+}
+
+bool IsDataPacking(BuiltinFn f) {
+    return f == BuiltinFn::kPack4X8Snorm || f == BuiltinFn::kPack4X8Unorm ||
+           f == BuiltinFn::kPack2X16Snorm || f == BuiltinFn::kPack2X16Unorm ||
+           f == BuiltinFn::kPack2X16Float;
+}
+
+bool IsDataUnpacking(BuiltinFn f) {
+    return f == BuiltinFn::kUnpack4X8Snorm || f == BuiltinFn::kUnpack4X8Unorm ||
+           f == BuiltinFn::kUnpack2X16Snorm || f == BuiltinFn::kUnpack2X16Unorm ||
+           f == BuiltinFn::kUnpack2X16Float;
+}
+
+bool IsBarrier(BuiltinFn f) {
+    return f == BuiltinFn::kWorkgroupBarrier || f == BuiltinFn::kStorageBarrier ||
+           f == BuiltinFn::kTextureBarrier;
+}
+
+bool IsAtomic(BuiltinFn f) {
+    return f == BuiltinFn::kAtomicLoad || f == BuiltinFn::kAtomicStore ||
+           f == BuiltinFn::kAtomicAdd || f == BuiltinFn::kAtomicSub || f == BuiltinFn::kAtomicMax ||
+           f == BuiltinFn::kAtomicMin || f == BuiltinFn::kAtomicAnd || f == BuiltinFn::kAtomicOr ||
+           f == BuiltinFn::kAtomicXor || f == BuiltinFn::kAtomicExchange ||
+           f == BuiltinFn::kAtomicCompareExchangeWeak;
+}
+
+bool IsDP4a(BuiltinFn f) {
+    return f == BuiltinFn::kDot4I8Packed || f == BuiltinFn::kDot4U8Packed;
+}
+
+bool IsSubgroup(BuiltinFn f) {
+    return f == BuiltinFn::kSubgroupBallot || f == BuiltinFn::kSubgroupBroadcast;
+}
+
+bool HasSideEffects(BuiltinFn f) {
+    switch (f) {
+        case BuiltinFn::kAtomicAdd:
+        case BuiltinFn::kAtomicAnd:
+        case BuiltinFn::kAtomicCompareExchangeWeak:
+        case BuiltinFn::kAtomicExchange:
+        case BuiltinFn::kAtomicMax:
+        case BuiltinFn::kAtomicMin:
+        case BuiltinFn::kAtomicOr:
+        case BuiltinFn::kAtomicStore:
+        case BuiltinFn::kAtomicSub:
+        case BuiltinFn::kAtomicXor:
+        case BuiltinFn::kTextureStore:
+        case BuiltinFn::kWorkgroupUniformLoad:
+            return true;
+        default:
+            break;
+    }
+    return false;
+}
+
+}  // namespace tint::core
diff --git a/src/tint/lang/core/builtin_fn.cc.tmpl b/src/tint/lang/core/builtin_fn.cc.tmpl
new file mode 100644
index 0000000..5a0d100
--- /dev/null
+++ b/src/tint/lang/core/builtin_fn.cc.tmpl
@@ -0,0 +1,132 @@
+{{- /*
+--------------------------------------------------------------------------------
+Template file for use with tools/src/cmd/gen to generate builtin_function.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/core/core.def" -}}
+#include "src/tint/lang/core/builtin_fn.h"
+
+namespace tint::core {
+
+BuiltinFn ParseBuiltinFn(std::string_view name) {
+{{- range $I.Sem.Builtins  }}
+    if (name == "{{.Name}}") {
+        return BuiltinFn::k{{PascalCase .Name}};
+    }
+{{- end  }}
+    return BuiltinFn::kNone;
+}
+
+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>";
+}
+
+bool IsCoarseDerivative(BuiltinFn f) {
+    return f == BuiltinFn::kDpdxCoarse || f == BuiltinFn::kDpdyCoarse ||
+           f == BuiltinFn::kFwidthCoarse;
+}
+
+bool IsFineDerivative(BuiltinFn f) {
+    return f == BuiltinFn::kDpdxFine || f == BuiltinFn::kDpdyFine ||
+           f == BuiltinFn::kFwidthFine;
+}
+
+bool IsDerivative(BuiltinFn f) {
+    return f == BuiltinFn::kDpdx || f == BuiltinFn::kDpdy ||
+           f == BuiltinFn::kFwidth || IsCoarseDerivative(f) ||
+           IsFineDerivative(f);
+}
+
+bool IsTexture(BuiltinFn f) {
+    return IsImageQuery(f) ||                                //
+           f == BuiltinFn::kTextureGather ||                 //
+           f == BuiltinFn::kTextureGatherCompare ||          //
+           f == BuiltinFn::kTextureLoad ||                   //
+           f == BuiltinFn::kTextureSample ||                 //
+           f == BuiltinFn::kTextureSampleBaseClampToEdge ||  //
+           f == BuiltinFn::kTextureSampleBias ||             //
+           f == BuiltinFn::kTextureSampleCompare ||          //
+           f == BuiltinFn::kTextureSampleCompareLevel ||     //
+           f == BuiltinFn::kTextureSampleGrad ||             //
+           f == BuiltinFn::kTextureSampleLevel ||            //
+           f == BuiltinFn::kTextureStore;
+}
+
+bool IsImageQuery(BuiltinFn f) {
+    return f == BuiltinFn::kTextureDimensions ||
+           f == BuiltinFn::kTextureNumLayers || f == BuiltinFn::kTextureNumLevels ||
+           f == BuiltinFn::kTextureNumSamples;
+}
+
+bool IsDataPacking(BuiltinFn f) {
+    return f == BuiltinFn::kPack4X8Snorm || f == BuiltinFn::kPack4X8Unorm ||
+           f == BuiltinFn::kPack2X16Snorm || f == BuiltinFn::kPack2X16Unorm ||
+           f == BuiltinFn::kPack2X16Float;
+}
+
+bool IsDataUnpacking(BuiltinFn f) {
+    return f == BuiltinFn::kUnpack4X8Snorm || f == BuiltinFn::kUnpack4X8Unorm ||
+           f == BuiltinFn::kUnpack2X16Snorm || f == BuiltinFn::kUnpack2X16Unorm ||
+           f == BuiltinFn::kUnpack2X16Float;
+}
+
+bool IsBarrier(BuiltinFn f) {
+    return f == BuiltinFn::kWorkgroupBarrier || f == BuiltinFn::kStorageBarrier ||
+           f == BuiltinFn::kTextureBarrier;
+}
+
+bool IsAtomic(BuiltinFn f) {
+    return f == BuiltinFn::kAtomicLoad || f == BuiltinFn::kAtomicStore ||
+           f == BuiltinFn::kAtomicAdd || f == BuiltinFn::kAtomicSub ||
+           f == BuiltinFn::kAtomicMax || f == BuiltinFn::kAtomicMin ||
+           f == BuiltinFn::kAtomicAnd || f == BuiltinFn::kAtomicOr ||
+           f == BuiltinFn::kAtomicXor || f == BuiltinFn::kAtomicExchange ||
+           f == BuiltinFn::kAtomicCompareExchangeWeak;
+}
+
+bool IsDP4a(BuiltinFn f) {
+    return f == BuiltinFn::kDot4I8Packed || f == BuiltinFn::kDot4U8Packed;
+}
+
+bool IsSubgroup(BuiltinFn f) {
+    return f == BuiltinFn::kSubgroupBallot || f == BuiltinFn::kSubgroupBroadcast;
+}
+
+bool HasSideEffects(BuiltinFn f) {
+    switch (f) {
+        case BuiltinFn::kAtomicAdd:
+        case BuiltinFn::kAtomicAnd:
+        case BuiltinFn::kAtomicCompareExchangeWeak:
+        case BuiltinFn::kAtomicExchange:
+        case BuiltinFn::kAtomicMax:
+        case BuiltinFn::kAtomicMin:
+        case BuiltinFn::kAtomicOr:
+        case BuiltinFn::kAtomicStore:
+        case BuiltinFn::kAtomicSub:
+        case BuiltinFn::kAtomicXor:
+        case BuiltinFn::kTextureStore:
+        case BuiltinFn::kWorkgroupUniformLoad:
+            return true;
+        default:
+            break;
+    }
+    return false;
+}
+
+}  // namespace tint::core
diff --git a/src/tint/lang/core/function.h b/src/tint/lang/core/builtin_fn.h
similarity index 64%
rename from src/tint/lang/core/function.h
rename to src/tint/lang/core/builtin_fn.h
index e9b52fa..5e58b5e 100644
--- a/src/tint/lang/core/function.h
+++ b/src/tint/lang/core/builtin_fn.h
@@ -14,15 +14,15 @@
 
 ////////////////////////////////////////////////////////////////////////////////
 // File generated by 'tools/src/cmd/gen' using the template:
-//   src/tint/lang/core/function.h.tmpl
+//   src/tint/lang/core/builtin_fn.h.tmpl
 //
 // To regenerate run: './tools/run gen'
 //
 //                       Do not modify this file directly
 ////////////////////////////////////////////////////////////////////////////////
 
-#ifndef SRC_TINT_LANG_CORE_FUNCTION_H_
-#define SRC_TINT_LANG_CORE_FUNCTION_H_
+#ifndef SRC_TINT_LANG_CORE_BUILTIN_FN_H_
+#define SRC_TINT_LANG_CORE_BUILTIN_FN_H_
 
 #include <cstdint>
 #include <string>
@@ -33,7 +33,7 @@
 namespace tint::core {
 
 /// Enumerator of all builtin functions
-enum class Function : uint8_t {
+enum class BuiltinFn : uint8_t {
     kAbs,
     kAcos,
     kAcosh,
@@ -154,146 +154,146 @@
     kNone,
 };
 
-/// Matches the Function by name
+/// Matches the BuiltinFn by name
 /// @param name the builtin name to parse
-/// @returns the parsed Function, or Function::kNone if `name` did not
+/// @returns the parsed BuiltinFn, or BuiltinFn::kNone if `name` did not
 /// match any builtin function.
-Function ParseFunction(std::string_view name);
+BuiltinFn ParseBuiltinFn(std::string_view name);
 
 /// @returns the name of the builtin function type. The spelling, including
 /// case, matches the name in the WGSL spec.
-const char* str(Function i);
+const char* str(BuiltinFn i);
 
 /// Emits the name of the builtin function type. The spelling, including case,
 /// matches the name in the WGSL spec.
 template <typename STREAM, typename = traits::EnableIfIsOStream<STREAM>>
-auto& operator<<(STREAM& o, Function i) {
+auto& operator<<(STREAM& o, BuiltinFn i) {
     return o << str(i);
 }
 
 /// All builtin functions
-constexpr Function kFunctions[] = {
-    Function::kAbs,
-    Function::kAcos,
-    Function::kAcosh,
-    Function::kAll,
-    Function::kAny,
-    Function::kArrayLength,
-    Function::kAsin,
-    Function::kAsinh,
-    Function::kAtan,
-    Function::kAtan2,
-    Function::kAtanh,
-    Function::kCeil,
-    Function::kClamp,
-    Function::kCos,
-    Function::kCosh,
-    Function::kCountLeadingZeros,
-    Function::kCountOneBits,
-    Function::kCountTrailingZeros,
-    Function::kCross,
-    Function::kDegrees,
-    Function::kDeterminant,
-    Function::kDistance,
-    Function::kDot,
-    Function::kDot4I8Packed,
-    Function::kDot4U8Packed,
-    Function::kDpdx,
-    Function::kDpdxCoarse,
-    Function::kDpdxFine,
-    Function::kDpdy,
-    Function::kDpdyCoarse,
-    Function::kDpdyFine,
-    Function::kExp,
-    Function::kExp2,
-    Function::kExtractBits,
-    Function::kFaceForward,
-    Function::kFirstLeadingBit,
-    Function::kFirstTrailingBit,
-    Function::kFloor,
-    Function::kFma,
-    Function::kFract,
-    Function::kFrexp,
-    Function::kFwidth,
-    Function::kFwidthCoarse,
-    Function::kFwidthFine,
-    Function::kInsertBits,
-    Function::kInverseSqrt,
-    Function::kLdexp,
-    Function::kLength,
-    Function::kLog,
-    Function::kLog2,
-    Function::kMax,
-    Function::kMin,
-    Function::kMix,
-    Function::kModf,
-    Function::kNormalize,
-    Function::kPack2X16Float,
-    Function::kPack2X16Snorm,
-    Function::kPack2X16Unorm,
-    Function::kPack4X8Snorm,
-    Function::kPack4X8Unorm,
-    Function::kPow,
-    Function::kQuantizeToF16,
-    Function::kRadians,
-    Function::kReflect,
-    Function::kRefract,
-    Function::kReverseBits,
-    Function::kRound,
-    Function::kSaturate,
-    Function::kSelect,
-    Function::kSign,
-    Function::kSin,
-    Function::kSinh,
-    Function::kSmoothstep,
-    Function::kSqrt,
-    Function::kStep,
-    Function::kStorageBarrier,
-    Function::kTan,
-    Function::kTanh,
-    Function::kTranspose,
-    Function::kTrunc,
-    Function::kUnpack2X16Float,
-    Function::kUnpack2X16Snorm,
-    Function::kUnpack2X16Unorm,
-    Function::kUnpack4X8Snorm,
-    Function::kUnpack4X8Unorm,
-    Function::kWorkgroupBarrier,
-    Function::kWorkgroupUniformLoad,
-    Function::kTextureBarrier,
-    Function::kTextureDimensions,
-    Function::kTextureGather,
-    Function::kTextureGatherCompare,
-    Function::kTextureNumLayers,
-    Function::kTextureNumLevels,
-    Function::kTextureNumSamples,
-    Function::kTextureSample,
-    Function::kTextureSampleBias,
-    Function::kTextureSampleCompare,
-    Function::kTextureSampleCompareLevel,
-    Function::kTextureSampleGrad,
-    Function::kTextureSampleLevel,
-    Function::kTextureSampleBaseClampToEdge,
-    Function::kTextureStore,
-    Function::kTextureLoad,
-    Function::kAtomicLoad,
-    Function::kAtomicStore,
-    Function::kAtomicAdd,
-    Function::kAtomicSub,
-    Function::kAtomicMax,
-    Function::kAtomicMin,
-    Function::kAtomicAnd,
-    Function::kAtomicOr,
-    Function::kAtomicXor,
-    Function::kAtomicExchange,
-    Function::kAtomicCompareExchangeWeak,
-    Function::kSubgroupBallot,
-    Function::kSubgroupBroadcast,
-    Function::kTintMaterialize,
+constexpr BuiltinFn kBuiltinFns[] = {
+    BuiltinFn::kAbs,
+    BuiltinFn::kAcos,
+    BuiltinFn::kAcosh,
+    BuiltinFn::kAll,
+    BuiltinFn::kAny,
+    BuiltinFn::kArrayLength,
+    BuiltinFn::kAsin,
+    BuiltinFn::kAsinh,
+    BuiltinFn::kAtan,
+    BuiltinFn::kAtan2,
+    BuiltinFn::kAtanh,
+    BuiltinFn::kCeil,
+    BuiltinFn::kClamp,
+    BuiltinFn::kCos,
+    BuiltinFn::kCosh,
+    BuiltinFn::kCountLeadingZeros,
+    BuiltinFn::kCountOneBits,
+    BuiltinFn::kCountTrailingZeros,
+    BuiltinFn::kCross,
+    BuiltinFn::kDegrees,
+    BuiltinFn::kDeterminant,
+    BuiltinFn::kDistance,
+    BuiltinFn::kDot,
+    BuiltinFn::kDot4I8Packed,
+    BuiltinFn::kDot4U8Packed,
+    BuiltinFn::kDpdx,
+    BuiltinFn::kDpdxCoarse,
+    BuiltinFn::kDpdxFine,
+    BuiltinFn::kDpdy,
+    BuiltinFn::kDpdyCoarse,
+    BuiltinFn::kDpdyFine,
+    BuiltinFn::kExp,
+    BuiltinFn::kExp2,
+    BuiltinFn::kExtractBits,
+    BuiltinFn::kFaceForward,
+    BuiltinFn::kFirstLeadingBit,
+    BuiltinFn::kFirstTrailingBit,
+    BuiltinFn::kFloor,
+    BuiltinFn::kFma,
+    BuiltinFn::kFract,
+    BuiltinFn::kFrexp,
+    BuiltinFn::kFwidth,
+    BuiltinFn::kFwidthCoarse,
+    BuiltinFn::kFwidthFine,
+    BuiltinFn::kInsertBits,
+    BuiltinFn::kInverseSqrt,
+    BuiltinFn::kLdexp,
+    BuiltinFn::kLength,
+    BuiltinFn::kLog,
+    BuiltinFn::kLog2,
+    BuiltinFn::kMax,
+    BuiltinFn::kMin,
+    BuiltinFn::kMix,
+    BuiltinFn::kModf,
+    BuiltinFn::kNormalize,
+    BuiltinFn::kPack2X16Float,
+    BuiltinFn::kPack2X16Snorm,
+    BuiltinFn::kPack2X16Unorm,
+    BuiltinFn::kPack4X8Snorm,
+    BuiltinFn::kPack4X8Unorm,
+    BuiltinFn::kPow,
+    BuiltinFn::kQuantizeToF16,
+    BuiltinFn::kRadians,
+    BuiltinFn::kReflect,
+    BuiltinFn::kRefract,
+    BuiltinFn::kReverseBits,
+    BuiltinFn::kRound,
+    BuiltinFn::kSaturate,
+    BuiltinFn::kSelect,
+    BuiltinFn::kSign,
+    BuiltinFn::kSin,
+    BuiltinFn::kSinh,
+    BuiltinFn::kSmoothstep,
+    BuiltinFn::kSqrt,
+    BuiltinFn::kStep,
+    BuiltinFn::kStorageBarrier,
+    BuiltinFn::kTan,
+    BuiltinFn::kTanh,
+    BuiltinFn::kTranspose,
+    BuiltinFn::kTrunc,
+    BuiltinFn::kUnpack2X16Float,
+    BuiltinFn::kUnpack2X16Snorm,
+    BuiltinFn::kUnpack2X16Unorm,
+    BuiltinFn::kUnpack4X8Snorm,
+    BuiltinFn::kUnpack4X8Unorm,
+    BuiltinFn::kWorkgroupBarrier,
+    BuiltinFn::kWorkgroupUniformLoad,
+    BuiltinFn::kTextureBarrier,
+    BuiltinFn::kTextureDimensions,
+    BuiltinFn::kTextureGather,
+    BuiltinFn::kTextureGatherCompare,
+    BuiltinFn::kTextureNumLayers,
+    BuiltinFn::kTextureNumLevels,
+    BuiltinFn::kTextureNumSamples,
+    BuiltinFn::kTextureSample,
+    BuiltinFn::kTextureSampleBias,
+    BuiltinFn::kTextureSampleCompare,
+    BuiltinFn::kTextureSampleCompareLevel,
+    BuiltinFn::kTextureSampleGrad,
+    BuiltinFn::kTextureSampleLevel,
+    BuiltinFn::kTextureSampleBaseClampToEdge,
+    BuiltinFn::kTextureStore,
+    BuiltinFn::kTextureLoad,
+    BuiltinFn::kAtomicLoad,
+    BuiltinFn::kAtomicStore,
+    BuiltinFn::kAtomicAdd,
+    BuiltinFn::kAtomicSub,
+    BuiltinFn::kAtomicMax,
+    BuiltinFn::kAtomicMin,
+    BuiltinFn::kAtomicAnd,
+    BuiltinFn::kAtomicOr,
+    BuiltinFn::kAtomicXor,
+    BuiltinFn::kAtomicExchange,
+    BuiltinFn::kAtomicCompareExchangeWeak,
+    BuiltinFn::kSubgroupBallot,
+    BuiltinFn::kSubgroupBroadcast,
+    BuiltinFn::kTintMaterialize,
 };
 
 /// All builtin function names
-constexpr const char* kFunctionStrings[] = {
+constexpr const char* kBuiltinFnStrings[] = {
     "abs",
     "acos",
     "acosh",
@@ -416,63 +416,63 @@
 /// Determines if the given `f` is a coarse derivative.
 /// @param f the builtin type
 /// @returns true if the given derivative is coarse.
-bool IsCoarseDerivativeBuiltin(Function f);
+bool IsCoarseDerivative(BuiltinFn f);
 
 /// Determines if the given `f` is a fine derivative.
 /// @param f the builtin type
 /// @returns true if the given derivative is fine.
-bool IsFineDerivativeBuiltin(Function f);
+bool IsFineDerivative(BuiltinFn f);
 
 /// Determine if the given `f` is a derivative builtin.
 /// @param f the builtin type
 /// @returns true if the given `f` is a derivative builtin
-bool IsDerivativeBuiltin(Function f);
+bool IsDerivative(BuiltinFn f);
 
 /// Determines if the given `f` is a texture operation builtin.
 /// @param f the builtin type
 /// @returns true if the given `f` is a texture operation builtin
-bool IsTextureBuiltin(Function f);
+bool IsTexture(BuiltinFn f);
 
 /// Determines if the given `f` is an image query builtin.
 /// @param f the builtin type
 /// @returns true if the given `f` is an image query builtin
-bool IsImageQueryBuiltin(Function f);
+bool IsImageQuery(BuiltinFn f);
 
 /// Determines if the given `f` is a data packing builtin.
 /// @param f the builtin type
 /// @returns true if the given `f` is a data packing builtin
-bool IsDataPackingBuiltin(Function f);
+bool IsDataPacking(BuiltinFn f);
 
 /// Determines if the given `f` is a data unpacking builtin.
 /// @param f the builtin type
 /// @returns true if the given `f` is a data unpacking builtin
-bool IsDataUnpackingBuiltin(Function f);
+bool IsDataUnpacking(BuiltinFn f);
 
 /// Determines if the given `f` is a barrier builtin.
 /// @param f the builtin type
 /// @returns true if the given `f` is a barrier builtin
-bool IsBarrierBuiltin(Function f);
+bool IsBarrier(BuiltinFn f);
 
 /// Determines if the given `f` is an atomic builtin.
 /// @param f the builtin type
 /// @returns true if the given `f` is an atomic builtin
-bool IsAtomicBuiltin(Function f);
+bool IsAtomic(BuiltinFn f);
 
 /// Determines if the given `f` is a DP4a builtin.
 /// @param f the builtin type
 /// @returns true if the given `f` is a DP4a builtin
-bool IsDP4aBuiltin(Function f);
+bool IsDP4a(BuiltinFn f);
 
 /// Determines if the given `f` is a subgroup builtin.
 /// @param f the builtin type
 /// @returns true if the given `f` is a subgroup builtin
-bool IsSubgroupBuiltin(Function f);
+bool IsSubgroup(BuiltinFn f);
 
 /// Determines if the given `f` may have side-effects (i.e. writes to at least one of its inputs)
 /// @returns true if intrinsic may have side-effects
-bool HasSideEffects(Function f);
+bool HasSideEffects(BuiltinFn f);
 
 }  // namespace tint::core
 // \endcond
 
-#endif  // SRC_TINT_LANG_CORE_FUNCTION_H_
+#endif  // SRC_TINT_LANG_CORE_BUILTIN_FN_H_
diff --git a/src/tint/lang/core/function.h.tmpl b/src/tint/lang/core/builtin_fn.h.tmpl
similarity index 75%
rename from src/tint/lang/core/function.h.tmpl
rename to src/tint/lang/core/builtin_fn.h.tmpl
index 546b385..3a10674 100644
--- a/src/tint/lang/core/function.h.tmpl
+++ b/src/tint/lang/core/builtin_fn.h.tmpl
@@ -1,6 +1,6 @@
 {{- /*
 --------------------------------------------------------------------------------
-Template file for use with tools/src/cmd/gen to generate function.h
+Template file for use with tools/src/cmd/gen to generate builtin_function.h
 
 To update the generated file, run:
     ./tools/run gen
@@ -13,8 +13,8 @@
 
 {{- $I := LoadIntrinsics "src/tint/lang/core/core.def" -}}
 
-#ifndef SRC_TINT_LANG_CORE_FUNCTION_H_
-#define SRC_TINT_LANG_CORE_FUNCTION_H_
+#ifndef SRC_TINT_LANG_CORE_BUILTIN_FN_H_
+#define SRC_TINT_LANG_CORE_BUILTIN_FN_H_
 
 #include <cstdint>
 #include <string>
@@ -25,39 +25,39 @@
 namespace tint::core {
 
 /// Enumerator of all builtin functions
-enum class Function : uint8_t {
+enum class BuiltinFn : uint8_t {
 {{- range $I.Sem.Builtins }}
     k{{PascalCase .Name}},
 {{- end }}
     kNone,
 };
 
-/// Matches the Function by name
+/// Matches the BuiltinFn by name
 /// @param name the builtin name to parse
-/// @returns the parsed Function, or Function::kNone if `name` did not
+/// @returns the parsed BuiltinFn, or BuiltinFn::kNone if `name` did not
 /// match any builtin function.
-Function ParseFunction(std::string_view name);
+BuiltinFn ParseBuiltinFn(std::string_view name);
 
 /// @returns the name of the builtin function type. The spelling, including
 /// case, matches the name in the WGSL spec.
-const char* str(Function i);
+const char* str(BuiltinFn i);
 
 /// Emits the name of the builtin function type. The spelling, including case,
 /// matches the name in the WGSL spec.
 template <typename STREAM, typename = traits::EnableIfIsOStream<STREAM>>
-auto& operator<<(STREAM& o, Function i) {
+auto& operator<<(STREAM& o, BuiltinFn i) {
   return o << str(i);
 }
 
 /// All builtin functions
-constexpr Function kFunctions[] = {
+constexpr BuiltinFn kBuiltinFns[] = {
 {{- range $I.Sem.Builtins }}
-    Function::k{{PascalCase .Name}},
+    BuiltinFn::k{{PascalCase .Name}},
 {{- end }}
 };
 
 /// All builtin function names
-constexpr const char* kFunctionStrings[] = {
+constexpr const char* kBuiltinFnStrings[] = {
 {{- range $I.Sem.Builtins }}
     "{{.Name}}",
 {{- end }}
@@ -66,63 +66,63 @@
 /// Determines if the given `f` is a coarse derivative.
 /// @param f the builtin type
 /// @returns true if the given derivative is coarse.
-bool IsCoarseDerivativeBuiltin(Function f);
+bool IsCoarseDerivative(BuiltinFn f);
 
 /// Determines if the given `f` is a fine derivative.
 /// @param f the builtin type
 /// @returns true if the given derivative is fine.
-bool IsFineDerivativeBuiltin(Function f);
+bool IsFineDerivative(BuiltinFn f);
 
 /// Determine if the given `f` is a derivative builtin.
 /// @param f the builtin type
 /// @returns true if the given `f` is a derivative builtin
-bool IsDerivativeBuiltin(Function f);
+bool IsDerivative(BuiltinFn f);
 
 /// Determines if the given `f` is a texture operation builtin.
 /// @param f the builtin type
 /// @returns true if the given `f` is a texture operation builtin
-bool IsTextureBuiltin(Function f);
+bool IsTexture(BuiltinFn f);
 
 /// Determines if the given `f` is an image query builtin.
 /// @param f the builtin type
 /// @returns true if the given `f` is an image query builtin
-bool IsImageQueryBuiltin(Function f);
+bool IsImageQuery(BuiltinFn f);
 
 /// Determines if the given `f` is a data packing builtin.
 /// @param f the builtin type
 /// @returns true if the given `f` is a data packing builtin
-bool IsDataPackingBuiltin(Function f);
+bool IsDataPacking(BuiltinFn f);
 
 /// Determines if the given `f` is a data unpacking builtin.
 /// @param f the builtin type
 /// @returns true if the given `f` is a data unpacking builtin
-bool IsDataUnpackingBuiltin(Function f);
+bool IsDataUnpacking(BuiltinFn f);
 
 /// Determines if the given `f` is a barrier builtin.
 /// @param f the builtin type
 /// @returns true if the given `f` is a barrier builtin
-bool IsBarrierBuiltin(Function f);
+bool IsBarrier(BuiltinFn f);
 
 /// Determines if the given `f` is an atomic builtin.
 /// @param f the builtin type
 /// @returns true if the given `f` is an atomic builtin
-bool IsAtomicBuiltin(Function f);
+bool IsAtomic(BuiltinFn f);
 
 /// Determines if the given `f` is a DP4a builtin.
 /// @param f the builtin type
 /// @returns true if the given `f` is a DP4a builtin
-bool IsDP4aBuiltin(Function f);
+bool IsDP4a(BuiltinFn f);
 
 /// Determines if the given `f` is a subgroup builtin.
 /// @param f the builtin type
 /// @returns true if the given `f` is a subgroup builtin
-bool IsSubgroupBuiltin(Function f);
+bool IsSubgroup(BuiltinFn f);
 
 /// Determines if the given `f` may have side-effects (i.e. writes to at least one of its inputs)
 /// @returns true if intrinsic may have side-effects
-bool HasSideEffects(Function f);
+bool HasSideEffects(BuiltinFn f);
 
 }  // namespace tint::core
 // \endcond
 
-#endif  // SRC_TINT_LANG_CORE_FUNCTION_H_
+#endif  // SRC_TINT_LANG_CORE_BUILTIN_FN_H_
diff --git a/src/tint/lang/core/builtin_test.cc b/src/tint/lang/core/builtin_test.cc
deleted file mode 100644
index 61a2187..0000000
--- a/src/tint/lang/core/builtin_test.cc
+++ /dev/null
@@ -1,460 +0,0 @@
-// Copyright 2022 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-////////////////////////////////////////////////////////////////////////////////
-// File generated by 'tools/src/cmd/gen' using the template:
-//   src/tint/lang/core/builtin_test.cc.tmpl
-//
-// To regenerate run: './tools/run gen'
-//
-//                       Do not modify this file directly
-////////////////////////////////////////////////////////////////////////////////
-
-#include "src/tint/lang/core/builtin.h"
-
-#include <string>
-
-#include "gtest/gtest.h"
-
-#include "src/tint/utils/text/string.h"
-
-namespace tint::core {
-namespace {
-
-namespace parse_print_tests {
-
-struct Case {
-    const char* string;
-    Builtin value;
-};
-
-inline std::ostream& operator<<(std::ostream& out, Case c) {
-    return out << "'" << std::string(c.string) << "'";
-}
-
-static constexpr Case kValidCases[] = {
-    {"__atomic_compare_exchange_result_i32", Builtin::kAtomicCompareExchangeResultI32},
-    {"__atomic_compare_exchange_result_u32", Builtin::kAtomicCompareExchangeResultU32},
-    {"__frexp_result_abstract", Builtin::kFrexpResultAbstract},
-    {"__frexp_result_f16", Builtin::kFrexpResultF16},
-    {"__frexp_result_f32", Builtin::kFrexpResultF32},
-    {"__frexp_result_vec2_abstract", Builtin::kFrexpResultVec2Abstract},
-    {"__frexp_result_vec2_f16", Builtin::kFrexpResultVec2F16},
-    {"__frexp_result_vec2_f32", Builtin::kFrexpResultVec2F32},
-    {"__frexp_result_vec3_abstract", Builtin::kFrexpResultVec3Abstract},
-    {"__frexp_result_vec3_f16", Builtin::kFrexpResultVec3F16},
-    {"__frexp_result_vec3_f32", Builtin::kFrexpResultVec3F32},
-    {"__frexp_result_vec4_abstract", Builtin::kFrexpResultVec4Abstract},
-    {"__frexp_result_vec4_f16", Builtin::kFrexpResultVec4F16},
-    {"__frexp_result_vec4_f32", Builtin::kFrexpResultVec4F32},
-    {"__modf_result_abstract", Builtin::kModfResultAbstract},
-    {"__modf_result_f16", Builtin::kModfResultF16},
-    {"__modf_result_f32", Builtin::kModfResultF32},
-    {"__modf_result_vec2_abstract", Builtin::kModfResultVec2Abstract},
-    {"__modf_result_vec2_f16", Builtin::kModfResultVec2F16},
-    {"__modf_result_vec2_f32", Builtin::kModfResultVec2F32},
-    {"__modf_result_vec3_abstract", Builtin::kModfResultVec3Abstract},
-    {"__modf_result_vec3_f16", Builtin::kModfResultVec3F16},
-    {"__modf_result_vec3_f32", Builtin::kModfResultVec3F32},
-    {"__modf_result_vec4_abstract", Builtin::kModfResultVec4Abstract},
-    {"__modf_result_vec4_f16", Builtin::kModfResultVec4F16},
-    {"__modf_result_vec4_f32", Builtin::kModfResultVec4F32},
-    {"__packed_vec3", Builtin::kPackedVec3},
-    {"array", Builtin::kArray},
-    {"atomic", Builtin::kAtomic},
-    {"bool", Builtin::kBool},
-    {"f16", Builtin::kF16},
-    {"f32", Builtin::kF32},
-    {"i32", Builtin::kI32},
-    {"mat2x2", Builtin::kMat2X2},
-    {"mat2x2f", Builtin::kMat2X2F},
-    {"mat2x2h", Builtin::kMat2X2H},
-    {"mat2x3", Builtin::kMat2X3},
-    {"mat2x3f", Builtin::kMat2X3F},
-    {"mat2x3h", Builtin::kMat2X3H},
-    {"mat2x4", Builtin::kMat2X4},
-    {"mat2x4f", Builtin::kMat2X4F},
-    {"mat2x4h", Builtin::kMat2X4H},
-    {"mat3x2", Builtin::kMat3X2},
-    {"mat3x2f", Builtin::kMat3X2F},
-    {"mat3x2h", Builtin::kMat3X2H},
-    {"mat3x3", Builtin::kMat3X3},
-    {"mat3x3f", Builtin::kMat3X3F},
-    {"mat3x3h", Builtin::kMat3X3H},
-    {"mat3x4", Builtin::kMat3X4},
-    {"mat3x4f", Builtin::kMat3X4F},
-    {"mat3x4h", Builtin::kMat3X4H},
-    {"mat4x2", Builtin::kMat4X2},
-    {"mat4x2f", Builtin::kMat4X2F},
-    {"mat4x2h", Builtin::kMat4X2H},
-    {"mat4x3", Builtin::kMat4X3},
-    {"mat4x3f", Builtin::kMat4X3F},
-    {"mat4x3h", Builtin::kMat4X3H},
-    {"mat4x4", Builtin::kMat4X4},
-    {"mat4x4f", Builtin::kMat4X4F},
-    {"mat4x4h", Builtin::kMat4X4H},
-    {"ptr", Builtin::kPtr},
-    {"sampler", Builtin::kSampler},
-    {"sampler_comparison", Builtin::kSamplerComparison},
-    {"texture_1d", Builtin::kTexture1D},
-    {"texture_2d", Builtin::kTexture2D},
-    {"texture_2d_array", Builtin::kTexture2DArray},
-    {"texture_3d", Builtin::kTexture3D},
-    {"texture_cube", Builtin::kTextureCube},
-    {"texture_cube_array", Builtin::kTextureCubeArray},
-    {"texture_depth_2d", Builtin::kTextureDepth2D},
-    {"texture_depth_2d_array", Builtin::kTextureDepth2DArray},
-    {"texture_depth_cube", Builtin::kTextureDepthCube},
-    {"texture_depth_cube_array", Builtin::kTextureDepthCubeArray},
-    {"texture_depth_multisampled_2d", Builtin::kTextureDepthMultisampled2D},
-    {"texture_external", Builtin::kTextureExternal},
-    {"texture_multisampled_2d", Builtin::kTextureMultisampled2D},
-    {"texture_storage_1d", Builtin::kTextureStorage1D},
-    {"texture_storage_2d", Builtin::kTextureStorage2D},
-    {"texture_storage_2d_array", Builtin::kTextureStorage2DArray},
-    {"texture_storage_3d", Builtin::kTextureStorage3D},
-    {"u32", Builtin::kU32},
-    {"vec2", Builtin::kVec2},
-    {"vec2f", Builtin::kVec2F},
-    {"vec2h", Builtin::kVec2H},
-    {"vec2i", Builtin::kVec2I},
-    {"vec2u", Builtin::kVec2U},
-    {"vec3", Builtin::kVec3},
-    {"vec3f", Builtin::kVec3F},
-    {"vec3h", Builtin::kVec3H},
-    {"vec3i", Builtin::kVec3I},
-    {"vec3u", Builtin::kVec3U},
-    {"vec4", Builtin::kVec4},
-    {"vec4f", Builtin::kVec4F},
-    {"vec4h", Builtin::kVec4H},
-    {"vec4i", Builtin::kVec4I},
-    {"vec4u", Builtin::kVec4U},
-};
-
-static constexpr Case kInvalidCases[] = {
-    {"__atomic_compareexchangeccresult_i32", Builtin::kUndefined},
-    {"__atoml3_compare_exchane_resulti2", Builtin::kUndefined},
-    {"__atomic_compare_Vxchange_result_i32", Builtin::kUndefined},
-    {"__atomic_com1are_exchange_result_u32", Builtin::kUndefined},
-    {"__atomic_qqompare_exchage_resulJ_u32", Builtin::kUndefined},
-    {"__atllmic_compare_exchange_result_u377", Builtin::kUndefined},
-    {"qpp_frexp_resultHHbstract", Builtin::kUndefined},
-    {"__fep_esulv_abstract", Builtin::kUndefined},
-    {"__Gbexp_resul_abstract", Builtin::kUndefined},
-    {"_vfrexp_resiilt_f16", Builtin::kUndefined},
-    {"__fr8xp_resultWWf16", Builtin::kUndefined},
-    {"__frxp_result_fMxx", Builtin::kUndefined},
-    {"gg_fXexp_reslt_f32", Builtin::kUndefined},
-    {"__frXxpresul_V32", Builtin::kUndefined},
-    {"__frexp_r3sult_f32", Builtin::kUndefined},
-    {"__frexpEresult_vec2_abstract", Builtin::kUndefined},
-    {"__frex_rPPsult_vTTc2_abstract", Builtin::kUndefined},
-    {"__frexp_resuddt_ec2_xxbstract", Builtin::kUndefined},
-    {"__frexp_result_ve442_f16", Builtin::kUndefined},
-    {"_SSfrexp_resulVV_vec2_f16", Builtin::kUndefined},
-    {"__fRxpRr22sult_vec2_f16", Builtin::kUndefined},
-    {"__frexp_res9lt_vec_fF2", Builtin::kUndefined},
-    {"__frexp_result_ve2_f32", Builtin::kUndefined},
-    {"_OOfrexp_result_VeHRRf32", Builtin::kUndefined},
-    {"__frexp_reyult_vec3_absract", Builtin::kUndefined},
-    {"__frexp_re77ulll_vecG_arrnstract", Builtin::kUndefined},
-    {"__4rexp_result_vec3_00bstract", Builtin::kUndefined},
-    {"__oorxp_result_vec316", Builtin::kUndefined},
-    {"zz_frexp_esult_ec3_f16", Builtin::kUndefined},
-    {"__iirex11_result_vp3_f16", Builtin::kUndefined},
-    {"__frXXxp_result_vec3_f32", Builtin::kUndefined},
-    {"__fnnexp99resIIlt_vec3_f355", Builtin::kUndefined},
-    {"__faSSerrp_result_vHHc3_fY2", Builtin::kUndefined},
-    {"__freHp_resutve4_abstkkact", Builtin::kUndefined},
-    {"jfrexpgresult_veRR4_abstrac", Builtin::kUndefined},
-    {"__frexp_resul_vec4_absbrac", Builtin::kUndefined},
-    {"_jfrexp_result_vec4_f16", Builtin::kUndefined},
-    {"__frexp_resultvec4_f16", Builtin::kUndefined},
-    {"__freqpresultvec4_f16", Builtin::kUndefined},
-    {"__frexNN_result_vec_f32", Builtin::kUndefined},
-    {"__frexp_resvvlt_vc4_f3", Builtin::kUndefined},
-    {"__frexp_esult_vec4_f3QQ", Builtin::kUndefined},
-    {"rmodf_reffultabstract", Builtin::kUndefined},
-    {"__jodf_result_abstract", Builtin::kUndefined},
-    {"_mNNwdf_r2sult8abstract", Builtin::kUndefined},
-    {"__mdf_result_f16", Builtin::kUndefined},
-    {"__modrr_result_f16", Builtin::kUndefined},
-    {"__mGdf_result_f16", Builtin::kUndefined},
-    {"__modf_resulFF_f32", Builtin::kUndefined},
-    {"__modf_eult_E3", Builtin::kUndefined},
-    {"__odf_resurrt_f32", Builtin::kUndefined},
-    {"__modf_reslt_vec_abstract", Builtin::kUndefined},
-    {"__modfJJresuDt_Xc2_abstract", Builtin::kUndefined},
-    {"_modf_reslt_vec28abstrct", Builtin::kUndefined},
-    {"__odf_reult_vkc211f1", Builtin::kUndefined},
-    {"__mdf_result_vec2_f16", Builtin::kUndefined},
-    {"__modf_resuJt_vec2_f6", Builtin::kUndefined},
-    {"__modf_result_vec2cf32", Builtin::kUndefined},
-    {"__modf_result_vec2_fO2", Builtin::kUndefined},
-    {"KK_movvf_result_vec2_ftt__", Builtin::kUndefined},
-    {"xx_modf_r8sult_vec3_abtr5ct", Builtin::kUndefined},
-    {"__modf_resuFt_vec3_aqt__act", Builtin::kUndefined},
-    {"__modf_result_vec3_aqqstrac", Builtin::kUndefined},
-    {"__odf_33esult_vec3_f1O6", Builtin::kUndefined},
-    {"_ttm6df_resQQlt_ooec9_f16", Builtin::kUndefined},
-    {"_modf_resu66t_vec3_f16", Builtin::kUndefined},
-    {"__mdf_resultOvxc3_f36zz", Builtin::kUndefined},
-    {"__modf_resuyyt_vec3_f32", Builtin::kUndefined},
-    {"__mod_resul_vecZHHf32", Builtin::kUndefined},
-    {"__modf_reqult_44ec4WWbstract", Builtin::kUndefined},
-    {"__mof_result_vec4_abstrOOct", Builtin::kUndefined},
-    {"__modYooresult_vh4_bstract", Builtin::kUndefined},
-    {"__modf_relt_ve4_f16", Builtin::kUndefined},
-    {"__modf_result_ve4Ff16", Builtin::kUndefined},
-    {"__modf_result_wec4_f1", Builtin::kUndefined},
-    {"__Kdff_rGsult_vec4_f2", Builtin::kUndefined},
-    {"__modf_reKKulq_vec4_f32", Builtin::kUndefined},
-    {"__modf_resummt3vec4_f3F", Builtin::kUndefined},
-    {"__packed_ec3", Builtin::kUndefined},
-    {"__packed_ecq", Builtin::kUndefined},
-    {"_backed_bbec3", Builtin::kUndefined},
-    {"iira", Builtin::kUndefined},
-    {"aqOOy", Builtin::kUndefined},
-    {"arvvTTy", Builtin::kUndefined},
-    {"atomFFc", Builtin::kUndefined},
-    {"aoQ00P", Builtin::kUndefined},
-    {"atPmic", Builtin::kUndefined},
-    {"bos77", Builtin::kUndefined},
-    {"CoRbbl", Builtin::kUndefined},
-    {"booXX", Builtin::kUndefined},
-    {"qOOO6", Builtin::kUndefined},
-    {"fs", Builtin::kUndefined},
-    {"f1X", Builtin::kUndefined},
-    {"f3", Builtin::kUndefined},
-    {"q", Builtin::kUndefined},
-    {"f322", Builtin::kUndefined},
-    {"0yz2", Builtin::kUndefined},
-    {"iVP", Builtin::kUndefined},
-    {"Cnn", Builtin::kUndefined},
-    {"AtqqHH2", Builtin::kUndefined},
-    {"at2x2", Builtin::kUndefined},
-    {"mafKK", Builtin::kUndefined},
-    {"ltgg2f", Builtin::kUndefined},
-    {"mat2xf", Builtin::kUndefined},
-    {"NTTtcx4f", Builtin::kUndefined},
-    {"ma7ppl2h", Builtin::kUndefined},
-    {"mNNt2xg", Builtin::kUndefined},
-    {"uub2XX2h", Builtin::kUndefined},
-    {"mt2x3", Builtin::kUndefined},
-    {"m88xK", Builtin::kUndefined},
-    {"maqx3", Builtin::kUndefined},
-    {"m11t2x3f", Builtin::kUndefined},
-    {"22at2iif", Builtin::kUndefined},
-    {"at2x377", Builtin::kUndefined},
-    {"m2t2xNh", Builtin::kUndefined},
-    {"mVVt2x3h", Builtin::kUndefined},
-    {"FaWW2w11h", Builtin::kUndefined},
-    {"matww4", Builtin::kUndefined},
-    {"mat2D4", Builtin::kUndefined},
-    {"maKx4", Builtin::kUndefined},
-    {"mat21PPhf", Builtin::kUndefined},
-    {"mat24f", Builtin::kUndefined},
-    {"mYYt2x4f", Builtin::kUndefined},
-    {"mttHH4kk", Builtin::kUndefined},
-    {"mat2rr4h", Builtin::kUndefined},
-    {"WWas2x4h", Builtin::kUndefined},
-    {"maYx2", Builtin::kUndefined},
-    {"mq3f2", Builtin::kUndefined},
-    {"vvafu222", Builtin::kUndefined},
-    {"t3x2f", Builtin::kUndefined},
-    {"YYat3f", Builtin::kUndefined},
-    {"may3x2EYY", Builtin::kUndefined},
-    {"da3xMoh", Builtin::kUndefined},
-    {"matMMx2", Builtin::kUndefined},
-    {"mat3x55h", Builtin::kUndefined},
-    {"maN3", Builtin::kUndefined},
-    {"ma33x3", Builtin::kUndefined},
-    {"mt3x3", Builtin::kUndefined},
-    {"mm66Issf", Builtin::kUndefined},
-    {"mat3x1f", Builtin::kUndefined},
-    {"Xt3x3", Builtin::kUndefined},
-    {"LatIx3h", Builtin::kUndefined},
-    {"at3fh", Builtin::kUndefined},
-    {"mYtURD3", Builtin::kUndefined},
-    {"mah3x4", Builtin::kUndefined},
-    {"muqII4", Builtin::kUndefined},
-    {"mat3xH", Builtin::kUndefined},
-    {"at3QQvv", Builtin::kUndefined},
-    {"at66ef", Builtin::kUndefined},
-    {"ma7O4f", Builtin::kUndefined},
-    {"m55t3x0DD", Builtin::kUndefined},
-    {"maH3x4II", Builtin::kUndefined},
-    {"at3x4", Builtin::kUndefined},
-    {"ma994x2", Builtin::kUndefined},
-    {"mWWt4Gt2", Builtin::kUndefined},
-    {"ay42", Builtin::kUndefined},
-    {"mt4x2f", Builtin::kUndefined},
-    {"IIaBB4x2f", Builtin::kUndefined},
-    {"TTat4x833", Builtin::kUndefined},
-    {"ddUUnntYYx2h", Builtin::kUndefined},
-    {"m5CCxxdZ", Builtin::kUndefined},
-    {"matkkq2h", Builtin::kUndefined},
-    {"5iitp00", Builtin::kUndefined},
-    {"mnntIIx3", Builtin::kUndefined},
-    {"ccaKx", Builtin::kUndefined},
-    {"m43KK", Builtin::kUndefined},
-    {"mat66x3f", Builtin::kUndefined},
-    {"Et4PP3K", Builtin::kUndefined},
-    {"xxatx3h", Builtin::kUndefined},
-    {"qat4x3h", Builtin::kUndefined},
-    {"MMayySrxh", Builtin::kUndefined},
-    {"uat4", Builtin::kUndefined},
-    {"tx4", Builtin::kUndefined},
-    {"ma54FF4", Builtin::kUndefined},
-    {"rra444z4f", Builtin::kUndefined},
-    {"matWW", Builtin::kUndefined},
-    {"CatZJXx4f", Builtin::kUndefined},
-    {"maPPx4h", Builtin::kUndefined},
-    {"mat4c4h", Builtin::kUndefined},
-    {"matPPll6h", Builtin::kUndefined},
-    {"9tyy", Builtin::kUndefined},
-    {"ptKK", Builtin::kUndefined},
-    {"x_", Builtin::kUndefined},
-    {"ayKer", Builtin::kUndefined},
-    {"szmpVek", Builtin::kUndefined},
-    {"sampqeK", Builtin::kUndefined},
-    {"sampler_comparisn", Builtin::kUndefined},
-    {"sapler_comparisVVn", Builtin::kUndefined},
-    {"samplerIcompaAUison", Builtin::kUndefined},
-    {"jexurbRd", Builtin::kUndefined},
-    {"exure_YYd", Builtin::kUndefined},
-    {"exture_1d", Builtin::kUndefined},
-    {"texxxur_1d", Builtin::kUndefined},
-    {"tJxucce_2d", Builtin::kUndefined},
-    {"texure_JJd", Builtin::kUndefined},
-    {"lDexture_fCC_arraU", Builtin::kUndefined},
-    {"tegture_2d_array", Builtin::kUndefined},
-    {"teCCure2d_arra", Builtin::kUndefined},
-    {"textue_3d", Builtin::kUndefined},
-    {"tIx__ure_3d", Builtin::kUndefined},
-    {"texurettPP", Builtin::kUndefined},
-    {"tddx3ure_cube", Builtin::kUndefined},
-    {"teKyyur_cube", Builtin::kUndefined},
-    {"teturecub", Builtin::kUndefined},
-    {"textinne_c03e_array", Builtin::kUndefined},
-    {"nextCCruuvcubK_array", Builtin::kUndefined},
-    {"tXxturellcbe_array", Builtin::kUndefined},
-    {"tppxture_depth_2d", Builtin::kUndefined},
-    {"txture_deptww_2d", Builtin::kUndefined},
-    {"gexturedemmthuu2", Builtin::kUndefined},
-    {"texmmre_depthaa2daray", Builtin::kUndefined},
-    {"texture_RRepth_Td_ccZray", Builtin::kUndefined},
-    {"text88re_depthTOd_array", Builtin::kUndefined},
-    {"texture_depth_cm00e", Builtin::kUndefined},
-    {"texture_Bmepth_cube", Builtin::kUndefined},
-    {"Mextre_ppeph_cube", Builtin::kUndefined},
-    {"texturOO_depth_cub_array", Builtin::kUndefined},
-    {"GeGGture_depthcube_array", Builtin::kUndefined},
-    {"texture11Hdepth_cube_array", Builtin::kUndefined},
-    {"textu6e_FFepth_multiameeled_2d", Builtin::kUndefined},
-    {"texture_epth_mltisampled_2d", Builtin::kUndefined},
-    {"texture_depth_mullKsaiipled_2d", Builtin::kUndefined},
-    {"texture_extenal", Builtin::kUndefined},
-    {"IIext99reexvvernal", Builtin::kUndefined},
-    {"texture_externl", Builtin::kUndefined},
-    {"texture_mhltisampled_2d", Builtin::kUndefined},
-    {"texturemPllltisampzzed_2d", Builtin::kUndefined},
-    {"exture_mltisamed_2d", Builtin::kUndefined},
-    {"texture_qqtoragff_1", Builtin::kUndefined},
-    {"textre_JJddorage_1W", Builtin::kUndefined},
-    {"XXrxture_storae1zz", Builtin::kUndefined},
-    {"texturestorag2_2d", Builtin::kUndefined},
-    {"yyNxture_storage_2d", Builtin::kUndefined},
-    {"etue_storage_2OO", Builtin::kUndefined},
-    {"reutuPe_storZgeE2d_array", Builtin::kUndefined},
-    {"texlure_storddeee_d_22rray", Builtin::kUndefined},
-    {"texture_mtorage_2V_a9ra", Builtin::kUndefined},
-    {"teII1re_storage_3d", Builtin::kUndefined},
-    {"texture_storagb_3d", Builtin::kUndefined},
-    {"texizrestorge73d", Builtin::kUndefined},
-    {"u3oi", Builtin::kUndefined},
-    {"3", Builtin::kUndefined},
-    {"S2", Builtin::kUndefined},
-    {"e22", Builtin::kUndefined},
-    {"1eC2", Builtin::kUndefined},
-    {"vf8c2", Builtin::kUndefined},
-    {"c2f", Builtin::kUndefined},
-    {"JJecSSf", Builtin::kUndefined},
-    {"92f", Builtin::kUndefined},
-    {"vbbJJ2TT", Builtin::kUndefined},
-    {"e66h", Builtin::kUndefined},
-    {"u662h", Builtin::kUndefined},
-    {"vW2i", Builtin::kUndefined},
-    {"v2i", Builtin::kUndefined},
-    {"veci", Builtin::kUndefined},
-    {"rec2u", Builtin::kUndefined},
-    {"2ec2B", Builtin::kUndefined},
-    {"vcBBu", Builtin::kUndefined},
-    {"veRR", Builtin::kUndefined},
-    {"VLL0", Builtin::kUndefined},
-    {"KOe3", Builtin::kUndefined},
-    {"vgwcf", Builtin::kUndefined},
-    {"vLphf", Builtin::kUndefined},
-    {"eiiEf", Builtin::kUndefined},
-    {"ec3h", Builtin::kUndefined},
-    {"UU883", Builtin::kUndefined},
-    {"rrecvvh", Builtin::kUndefined},
-    {"ecmm", Builtin::kUndefined},
-    {"vec4j", Builtin::kUndefined},
-    {"vec3X", Builtin::kUndefined},
-    {"vec38", Builtin::kUndefined},
-    {"vecvEE", Builtin::kUndefined},
-    {"z99ci", Builtin::kUndefined},
-    {"JJGeQQ4", Builtin::kUndefined},
-    {"ssec4", Builtin::kUndefined},
-    {"PecK", Builtin::kUndefined},
-    {"tpc4f", Builtin::kUndefined},
-    {"vec", Builtin::kUndefined},
-    {"MMec4f", Builtin::kUndefined},
-    {"vJJc40", Builtin::kUndefined},
-    {"8c", Builtin::kUndefined},
-    {"vecggKh", Builtin::kUndefined},
-    {"vecfi", Builtin::kUndefined},
-    {"vec47Q", Builtin::kUndefined},
-    {"veY4i", Builtin::kUndefined},
-    {"keSu", Builtin::kUndefined},
-    {"n422", Builtin::kUndefined},
-    {"vFFu", Builtin::kUndefined},
-};
-
-using BuiltinParseTest = testing::TestWithParam<Case>;
-
-TEST_P(BuiltinParseTest, Parse) {
-    const char* string = GetParam().string;
-    Builtin expect = GetParam().value;
-    EXPECT_EQ(expect, ParseBuiltin(string));
-}
-
-INSTANTIATE_TEST_SUITE_P(ValidCases, BuiltinParseTest, testing::ValuesIn(kValidCases));
-INSTANTIATE_TEST_SUITE_P(InvalidCases, BuiltinParseTest, testing::ValuesIn(kInvalidCases));
-
-using BuiltinPrintTest = testing::TestWithParam<Case>;
-
-TEST_P(BuiltinPrintTest, Print) {
-    Builtin value = GetParam().value;
-    const char* expect = GetParam().string;
-    EXPECT_EQ(expect, tint::ToString(value));
-}
-
-INSTANTIATE_TEST_SUITE_P(ValidCases, BuiltinPrintTest, testing::ValuesIn(kValidCases));
-
-}  // namespace parse_print_tests
-
-}  // namespace
-}  // namespace tint::core
diff --git a/src/tint/lang/core/builtin_type.cc b/src/tint/lang/core/builtin_type.cc
new file mode 100644
index 0000000..7fa8c1b
--- /dev/null
+++ b/src/tint/lang/core/builtin_type.cc
@@ -0,0 +1,523 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+////////////////////////////////////////////////////////////////////////////////
+// File generated by 'tools/src/cmd/gen' using the template:
+//   src/tint/lang/core/builtin_type.cc.tmpl
+//
+// To regenerate run: './tools/run gen'
+//
+//                       Do not modify this file directly
+////////////////////////////////////////////////////////////////////////////////
+
+#include "src/tint/lang/core/builtin_type.h"
+
+namespace tint::core {
+
+/// ParseBuiltinType parses a BuiltinType from a string.
+/// @param str the string to parse
+/// @returns the parsed enum, or BuiltinType::kUndefined if the string could not be parsed.
+BuiltinType ParseBuiltinType(std::string_view str) {
+    if (str == "__atomic_compare_exchange_result_i32") {
+        return BuiltinType::kAtomicCompareExchangeResultI32;
+    }
+    if (str == "__atomic_compare_exchange_result_u32") {
+        return BuiltinType::kAtomicCompareExchangeResultU32;
+    }
+    if (str == "__frexp_result_abstract") {
+        return BuiltinType::kFrexpResultAbstract;
+    }
+    if (str == "__frexp_result_f16") {
+        return BuiltinType::kFrexpResultF16;
+    }
+    if (str == "__frexp_result_f32") {
+        return BuiltinType::kFrexpResultF32;
+    }
+    if (str == "__frexp_result_vec2_abstract") {
+        return BuiltinType::kFrexpResultVec2Abstract;
+    }
+    if (str == "__frexp_result_vec2_f16") {
+        return BuiltinType::kFrexpResultVec2F16;
+    }
+    if (str == "__frexp_result_vec2_f32") {
+        return BuiltinType::kFrexpResultVec2F32;
+    }
+    if (str == "__frexp_result_vec3_abstract") {
+        return BuiltinType::kFrexpResultVec3Abstract;
+    }
+    if (str == "__frexp_result_vec3_f16") {
+        return BuiltinType::kFrexpResultVec3F16;
+    }
+    if (str == "__frexp_result_vec3_f32") {
+        return BuiltinType::kFrexpResultVec3F32;
+    }
+    if (str == "__frexp_result_vec4_abstract") {
+        return BuiltinType::kFrexpResultVec4Abstract;
+    }
+    if (str == "__frexp_result_vec4_f16") {
+        return BuiltinType::kFrexpResultVec4F16;
+    }
+    if (str == "__frexp_result_vec4_f32") {
+        return BuiltinType::kFrexpResultVec4F32;
+    }
+    if (str == "__modf_result_abstract") {
+        return BuiltinType::kModfResultAbstract;
+    }
+    if (str == "__modf_result_f16") {
+        return BuiltinType::kModfResultF16;
+    }
+    if (str == "__modf_result_f32") {
+        return BuiltinType::kModfResultF32;
+    }
+    if (str == "__modf_result_vec2_abstract") {
+        return BuiltinType::kModfResultVec2Abstract;
+    }
+    if (str == "__modf_result_vec2_f16") {
+        return BuiltinType::kModfResultVec2F16;
+    }
+    if (str == "__modf_result_vec2_f32") {
+        return BuiltinType::kModfResultVec2F32;
+    }
+    if (str == "__modf_result_vec3_abstract") {
+        return BuiltinType::kModfResultVec3Abstract;
+    }
+    if (str == "__modf_result_vec3_f16") {
+        return BuiltinType::kModfResultVec3F16;
+    }
+    if (str == "__modf_result_vec3_f32") {
+        return BuiltinType::kModfResultVec3F32;
+    }
+    if (str == "__modf_result_vec4_abstract") {
+        return BuiltinType::kModfResultVec4Abstract;
+    }
+    if (str == "__modf_result_vec4_f16") {
+        return BuiltinType::kModfResultVec4F16;
+    }
+    if (str == "__modf_result_vec4_f32") {
+        return BuiltinType::kModfResultVec4F32;
+    }
+    if (str == "__packed_vec3") {
+        return BuiltinType::kPackedVec3;
+    }
+    if (str == "array") {
+        return BuiltinType::kArray;
+    }
+    if (str == "atomic") {
+        return BuiltinType::kAtomic;
+    }
+    if (str == "bool") {
+        return BuiltinType::kBool;
+    }
+    if (str == "f16") {
+        return BuiltinType::kF16;
+    }
+    if (str == "f32") {
+        return BuiltinType::kF32;
+    }
+    if (str == "i32") {
+        return BuiltinType::kI32;
+    }
+    if (str == "mat2x2") {
+        return BuiltinType::kMat2X2;
+    }
+    if (str == "mat2x2f") {
+        return BuiltinType::kMat2X2F;
+    }
+    if (str == "mat2x2h") {
+        return BuiltinType::kMat2X2H;
+    }
+    if (str == "mat2x3") {
+        return BuiltinType::kMat2X3;
+    }
+    if (str == "mat2x3f") {
+        return BuiltinType::kMat2X3F;
+    }
+    if (str == "mat2x3h") {
+        return BuiltinType::kMat2X3H;
+    }
+    if (str == "mat2x4") {
+        return BuiltinType::kMat2X4;
+    }
+    if (str == "mat2x4f") {
+        return BuiltinType::kMat2X4F;
+    }
+    if (str == "mat2x4h") {
+        return BuiltinType::kMat2X4H;
+    }
+    if (str == "mat3x2") {
+        return BuiltinType::kMat3X2;
+    }
+    if (str == "mat3x2f") {
+        return BuiltinType::kMat3X2F;
+    }
+    if (str == "mat3x2h") {
+        return BuiltinType::kMat3X2H;
+    }
+    if (str == "mat3x3") {
+        return BuiltinType::kMat3X3;
+    }
+    if (str == "mat3x3f") {
+        return BuiltinType::kMat3X3F;
+    }
+    if (str == "mat3x3h") {
+        return BuiltinType::kMat3X3H;
+    }
+    if (str == "mat3x4") {
+        return BuiltinType::kMat3X4;
+    }
+    if (str == "mat3x4f") {
+        return BuiltinType::kMat3X4F;
+    }
+    if (str == "mat3x4h") {
+        return BuiltinType::kMat3X4H;
+    }
+    if (str == "mat4x2") {
+        return BuiltinType::kMat4X2;
+    }
+    if (str == "mat4x2f") {
+        return BuiltinType::kMat4X2F;
+    }
+    if (str == "mat4x2h") {
+        return BuiltinType::kMat4X2H;
+    }
+    if (str == "mat4x3") {
+        return BuiltinType::kMat4X3;
+    }
+    if (str == "mat4x3f") {
+        return BuiltinType::kMat4X3F;
+    }
+    if (str == "mat4x3h") {
+        return BuiltinType::kMat4X3H;
+    }
+    if (str == "mat4x4") {
+        return BuiltinType::kMat4X4;
+    }
+    if (str == "mat4x4f") {
+        return BuiltinType::kMat4X4F;
+    }
+    if (str == "mat4x4h") {
+        return BuiltinType::kMat4X4H;
+    }
+    if (str == "ptr") {
+        return BuiltinType::kPtr;
+    }
+    if (str == "sampler") {
+        return BuiltinType::kSampler;
+    }
+    if (str == "sampler_comparison") {
+        return BuiltinType::kSamplerComparison;
+    }
+    if (str == "texture_1d") {
+        return BuiltinType::kTexture1D;
+    }
+    if (str == "texture_2d") {
+        return BuiltinType::kTexture2D;
+    }
+    if (str == "texture_2d_array") {
+        return BuiltinType::kTexture2DArray;
+    }
+    if (str == "texture_3d") {
+        return BuiltinType::kTexture3D;
+    }
+    if (str == "texture_cube") {
+        return BuiltinType::kTextureCube;
+    }
+    if (str == "texture_cube_array") {
+        return BuiltinType::kTextureCubeArray;
+    }
+    if (str == "texture_depth_2d") {
+        return BuiltinType::kTextureDepth2D;
+    }
+    if (str == "texture_depth_2d_array") {
+        return BuiltinType::kTextureDepth2DArray;
+    }
+    if (str == "texture_depth_cube") {
+        return BuiltinType::kTextureDepthCube;
+    }
+    if (str == "texture_depth_cube_array") {
+        return BuiltinType::kTextureDepthCubeArray;
+    }
+    if (str == "texture_depth_multisampled_2d") {
+        return BuiltinType::kTextureDepthMultisampled2D;
+    }
+    if (str == "texture_external") {
+        return BuiltinType::kTextureExternal;
+    }
+    if (str == "texture_multisampled_2d") {
+        return BuiltinType::kTextureMultisampled2D;
+    }
+    if (str == "texture_storage_1d") {
+        return BuiltinType::kTextureStorage1D;
+    }
+    if (str == "texture_storage_2d") {
+        return BuiltinType::kTextureStorage2D;
+    }
+    if (str == "texture_storage_2d_array") {
+        return BuiltinType::kTextureStorage2DArray;
+    }
+    if (str == "texture_storage_3d") {
+        return BuiltinType::kTextureStorage3D;
+    }
+    if (str == "u32") {
+        return BuiltinType::kU32;
+    }
+    if (str == "vec2") {
+        return BuiltinType::kVec2;
+    }
+    if (str == "vec2f") {
+        return BuiltinType::kVec2F;
+    }
+    if (str == "vec2h") {
+        return BuiltinType::kVec2H;
+    }
+    if (str == "vec2i") {
+        return BuiltinType::kVec2I;
+    }
+    if (str == "vec2u") {
+        return BuiltinType::kVec2U;
+    }
+    if (str == "vec3") {
+        return BuiltinType::kVec3;
+    }
+    if (str == "vec3f") {
+        return BuiltinType::kVec3F;
+    }
+    if (str == "vec3h") {
+        return BuiltinType::kVec3H;
+    }
+    if (str == "vec3i") {
+        return BuiltinType::kVec3I;
+    }
+    if (str == "vec3u") {
+        return BuiltinType::kVec3U;
+    }
+    if (str == "vec4") {
+        return BuiltinType::kVec4;
+    }
+    if (str == "vec4f") {
+        return BuiltinType::kVec4F;
+    }
+    if (str == "vec4h") {
+        return BuiltinType::kVec4H;
+    }
+    if (str == "vec4i") {
+        return BuiltinType::kVec4I;
+    }
+    if (str == "vec4u") {
+        return BuiltinType::kVec4U;
+    }
+    return BuiltinType::kUndefined;
+}
+
+std::string_view ToString(BuiltinType value) {
+    switch (value) {
+        case BuiltinType::kUndefined:
+            return "undefined";
+        case BuiltinType::kAtomicCompareExchangeResultI32:
+            return "__atomic_compare_exchange_result_i32";
+        case BuiltinType::kAtomicCompareExchangeResultU32:
+            return "__atomic_compare_exchange_result_u32";
+        case BuiltinType::kFrexpResultAbstract:
+            return "__frexp_result_abstract";
+        case BuiltinType::kFrexpResultF16:
+            return "__frexp_result_f16";
+        case BuiltinType::kFrexpResultF32:
+            return "__frexp_result_f32";
+        case BuiltinType::kFrexpResultVec2Abstract:
+            return "__frexp_result_vec2_abstract";
+        case BuiltinType::kFrexpResultVec2F16:
+            return "__frexp_result_vec2_f16";
+        case BuiltinType::kFrexpResultVec2F32:
+            return "__frexp_result_vec2_f32";
+        case BuiltinType::kFrexpResultVec3Abstract:
+            return "__frexp_result_vec3_abstract";
+        case BuiltinType::kFrexpResultVec3F16:
+            return "__frexp_result_vec3_f16";
+        case BuiltinType::kFrexpResultVec3F32:
+            return "__frexp_result_vec3_f32";
+        case BuiltinType::kFrexpResultVec4Abstract:
+            return "__frexp_result_vec4_abstract";
+        case BuiltinType::kFrexpResultVec4F16:
+            return "__frexp_result_vec4_f16";
+        case BuiltinType::kFrexpResultVec4F32:
+            return "__frexp_result_vec4_f32";
+        case BuiltinType::kModfResultAbstract:
+            return "__modf_result_abstract";
+        case BuiltinType::kModfResultF16:
+            return "__modf_result_f16";
+        case BuiltinType::kModfResultF32:
+            return "__modf_result_f32";
+        case BuiltinType::kModfResultVec2Abstract:
+            return "__modf_result_vec2_abstract";
+        case BuiltinType::kModfResultVec2F16:
+            return "__modf_result_vec2_f16";
+        case BuiltinType::kModfResultVec2F32:
+            return "__modf_result_vec2_f32";
+        case BuiltinType::kModfResultVec3Abstract:
+            return "__modf_result_vec3_abstract";
+        case BuiltinType::kModfResultVec3F16:
+            return "__modf_result_vec3_f16";
+        case BuiltinType::kModfResultVec3F32:
+            return "__modf_result_vec3_f32";
+        case BuiltinType::kModfResultVec4Abstract:
+            return "__modf_result_vec4_abstract";
+        case BuiltinType::kModfResultVec4F16:
+            return "__modf_result_vec4_f16";
+        case BuiltinType::kModfResultVec4F32:
+            return "__modf_result_vec4_f32";
+        case BuiltinType::kPackedVec3:
+            return "__packed_vec3";
+        case BuiltinType::kArray:
+            return "array";
+        case BuiltinType::kAtomic:
+            return "atomic";
+        case BuiltinType::kBool:
+            return "bool";
+        case BuiltinType::kF16:
+            return "f16";
+        case BuiltinType::kF32:
+            return "f32";
+        case BuiltinType::kI32:
+            return "i32";
+        case BuiltinType::kMat2X2:
+            return "mat2x2";
+        case BuiltinType::kMat2X2F:
+            return "mat2x2f";
+        case BuiltinType::kMat2X2H:
+            return "mat2x2h";
+        case BuiltinType::kMat2X3:
+            return "mat2x3";
+        case BuiltinType::kMat2X3F:
+            return "mat2x3f";
+        case BuiltinType::kMat2X3H:
+            return "mat2x3h";
+        case BuiltinType::kMat2X4:
+            return "mat2x4";
+        case BuiltinType::kMat2X4F:
+            return "mat2x4f";
+        case BuiltinType::kMat2X4H:
+            return "mat2x4h";
+        case BuiltinType::kMat3X2:
+            return "mat3x2";
+        case BuiltinType::kMat3X2F:
+            return "mat3x2f";
+        case BuiltinType::kMat3X2H:
+            return "mat3x2h";
+        case BuiltinType::kMat3X3:
+            return "mat3x3";
+        case BuiltinType::kMat3X3F:
+            return "mat3x3f";
+        case BuiltinType::kMat3X3H:
+            return "mat3x3h";
+        case BuiltinType::kMat3X4:
+            return "mat3x4";
+        case BuiltinType::kMat3X4F:
+            return "mat3x4f";
+        case BuiltinType::kMat3X4H:
+            return "mat3x4h";
+        case BuiltinType::kMat4X2:
+            return "mat4x2";
+        case BuiltinType::kMat4X2F:
+            return "mat4x2f";
+        case BuiltinType::kMat4X2H:
+            return "mat4x2h";
+        case BuiltinType::kMat4X3:
+            return "mat4x3";
+        case BuiltinType::kMat4X3F:
+            return "mat4x3f";
+        case BuiltinType::kMat4X3H:
+            return "mat4x3h";
+        case BuiltinType::kMat4X4:
+            return "mat4x4";
+        case BuiltinType::kMat4X4F:
+            return "mat4x4f";
+        case BuiltinType::kMat4X4H:
+            return "mat4x4h";
+        case BuiltinType::kPtr:
+            return "ptr";
+        case BuiltinType::kSampler:
+            return "sampler";
+        case BuiltinType::kSamplerComparison:
+            return "sampler_comparison";
+        case BuiltinType::kTexture1D:
+            return "texture_1d";
+        case BuiltinType::kTexture2D:
+            return "texture_2d";
+        case BuiltinType::kTexture2DArray:
+            return "texture_2d_array";
+        case BuiltinType::kTexture3D:
+            return "texture_3d";
+        case BuiltinType::kTextureCube:
+            return "texture_cube";
+        case BuiltinType::kTextureCubeArray:
+            return "texture_cube_array";
+        case BuiltinType::kTextureDepth2D:
+            return "texture_depth_2d";
+        case BuiltinType::kTextureDepth2DArray:
+            return "texture_depth_2d_array";
+        case BuiltinType::kTextureDepthCube:
+            return "texture_depth_cube";
+        case BuiltinType::kTextureDepthCubeArray:
+            return "texture_depth_cube_array";
+        case BuiltinType::kTextureDepthMultisampled2D:
+            return "texture_depth_multisampled_2d";
+        case BuiltinType::kTextureExternal:
+            return "texture_external";
+        case BuiltinType::kTextureMultisampled2D:
+            return "texture_multisampled_2d";
+        case BuiltinType::kTextureStorage1D:
+            return "texture_storage_1d";
+        case BuiltinType::kTextureStorage2D:
+            return "texture_storage_2d";
+        case BuiltinType::kTextureStorage2DArray:
+            return "texture_storage_2d_array";
+        case BuiltinType::kTextureStorage3D:
+            return "texture_storage_3d";
+        case BuiltinType::kU32:
+            return "u32";
+        case BuiltinType::kVec2:
+            return "vec2";
+        case BuiltinType::kVec2F:
+            return "vec2f";
+        case BuiltinType::kVec2H:
+            return "vec2h";
+        case BuiltinType::kVec2I:
+            return "vec2i";
+        case BuiltinType::kVec2U:
+            return "vec2u";
+        case BuiltinType::kVec3:
+            return "vec3";
+        case BuiltinType::kVec3F:
+            return "vec3f";
+        case BuiltinType::kVec3H:
+            return "vec3h";
+        case BuiltinType::kVec3I:
+            return "vec3i";
+        case BuiltinType::kVec3U:
+            return "vec3u";
+        case BuiltinType::kVec4:
+            return "vec4";
+        case BuiltinType::kVec4F:
+            return "vec4f";
+        case BuiltinType::kVec4H:
+            return "vec4h";
+        case BuiltinType::kVec4I:
+            return "vec4i";
+        case BuiltinType::kVec4U:
+            return "vec4u";
+    }
+    return "<unknown>";
+}
+
+}  // namespace tint::core
diff --git a/src/tint/lang/core/builtin.cc.tmpl b/src/tint/lang/core/builtin_type.cc.tmpl
similarity index 85%
rename from src/tint/lang/core/builtin.cc.tmpl
rename to src/tint/lang/core/builtin_type.cc.tmpl
index ca622fe..6d8b0e2 100644
--- a/src/tint/lang/core/builtin.cc.tmpl
+++ b/src/tint/lang/core/builtin_type.cc.tmpl
@@ -1,6 +1,6 @@
 {{- /*
 --------------------------------------------------------------------------------
-Template file for use with tools/src/cmd/gen to generate builtin.cc
+Template file for use with tools/src/cmd/gen to generate builtin_type.cc
 
 To update the generated file, run:
     ./tools/run gen
@@ -14,9 +14,9 @@
 {{- $I := LoadIntrinsics "src/tint/lang/core/core.def" -}}
 {{- Import "src/tint/utils/templates/enums.tmpl.inc" -}}
 {{- $enum := ($I.Sem.Enum "builtin_type") -}}
-{{- Eval "OverrideEnumName" "Enum" $enum "Name" "Builtin" -}}
+{{- Eval "OverrideEnumName" "Enum" $enum "Name" "BuiltinType" -}}
 
-#include "src/tint/lang/core/builtin.h"
+#include "src/tint/lang/core/builtin_type.h"
 
 namespace tint::core {
 
diff --git a/src/tint/lang/core/builtin.h b/src/tint/lang/core/builtin_type.h
similarity index 88%
rename from src/tint/lang/core/builtin.h
rename to src/tint/lang/core/builtin_type.h
index eed8ff7..d3f1e0f 100644
--- a/src/tint/lang/core/builtin.h
+++ b/src/tint/lang/core/builtin_type.h
@@ -14,15 +14,15 @@
 
 ////////////////////////////////////////////////////////////////////////////////
 // File generated by 'tools/src/cmd/gen' using the template:
-//   src/tint/lang/core/builtin.h.tmpl
+//   src/tint/lang/core/builtin_type.h.tmpl
 //
 // To regenerate run: './tools/run gen'
 //
 //                       Do not modify this file directly
 ////////////////////////////////////////////////////////////////////////////////
 
-#ifndef SRC_TINT_LANG_CORE_BUILTIN_H_
-#define SRC_TINT_LANG_CORE_BUILTIN_H_
+#ifndef SRC_TINT_LANG_CORE_BUILTIN_TYPE_H_
+#define SRC_TINT_LANG_CORE_BUILTIN_TYPE_H_
 
 #include <cstdint>
 
@@ -30,8 +30,8 @@
 
 namespace tint::core {
 
-/// An enumerator of builtin builtin.
-enum class Builtin : uint8_t {
+/// An enumerator of builtin types.
+enum class BuiltinType : uint8_t {
     kUndefined,
     kAtomicCompareExchangeResultI32,
     kAtomicCompareExchangeResultU32,
@@ -133,22 +133,22 @@
 
 /// @param value the enum value
 /// @returns the string for the given enum value
-std::string_view ToString(Builtin value);
+std::string_view ToString(BuiltinType value);
 
 /// @param out the stream to write to
-/// @param value the Builtin
+/// @param value the BuiltinType
 /// @returns @p out so calls can be chained
 template <typename STREAM, typename = traits::EnableIfIsOStream<STREAM>>
-auto& operator<<(STREAM& out, Builtin value) {
+auto& operator<<(STREAM& out, BuiltinType value) {
     return out << ToString(value);
 }
 
-/// ParseBuiltin parses a Builtin from a string.
+/// ParseBuiltinType parses a BuiltinType from a string.
 /// @param str the string to parse
-/// @returns the parsed enum, or Builtin::kUndefined if the string could not be parsed.
-Builtin ParseBuiltin(std::string_view str);
+/// @returns the parsed enum, or BuiltinType::kUndefined if the string could not be parsed.
+BuiltinType ParseBuiltinType(std::string_view str);
 
-constexpr const char* kBuiltinStrings[] = {
+constexpr const char* kBuiltinTypeStrings[] = {
     "__atomic_compare_exchange_result_i32",
     "__atomic_compare_exchange_result_u32",
     "__frexp_result_abstract",
@@ -249,4 +249,4 @@
 
 }  // namespace tint::core
 
-#endif  // SRC_TINT_LANG_CORE_BUILTIN_H_
+#endif  // SRC_TINT_LANG_CORE_BUILTIN_TYPE_H_
diff --git a/src/tint/lang/core/builtin.h.tmpl b/src/tint/lang/core/builtin_type.h.tmpl
similarity index 75%
rename from src/tint/lang/core/builtin.h.tmpl
rename to src/tint/lang/core/builtin_type.h.tmpl
index 8864a4d..e9b514c 100644
--- a/src/tint/lang/core/builtin.h.tmpl
+++ b/src/tint/lang/core/builtin_type.h.tmpl
@@ -1,6 +1,6 @@
 {{- /*
 --------------------------------------------------------------------------------
-Template file for use with tools/src/cmd/gen to generate builtin.h
+Template file for use with tools/src/cmd/gen to generate builtin_type.h
 
 To update the generated file, run:
     ./tools/run gen
@@ -14,10 +14,10 @@
 {{- $I := LoadIntrinsics "src/tint/lang/core/core.def" -}}
 {{- Import "src/tint/utils/templates/enums.tmpl.inc" -}}
 {{- $enum := ($I.Sem.Enum "builtin_type") -}}
-{{- Eval "OverrideEnumName" "Enum" $enum "Name" "Builtin" -}}
+{{- Eval "OverrideEnumName" "Enum" $enum "Name" "BuiltinType" -}}
 
-#ifndef SRC_TINT_LANG_CORE_BUILTIN_H_
-#define SRC_TINT_LANG_CORE_BUILTIN_H_
+#ifndef SRC_TINT_LANG_CORE_BUILTIN_TYPE_H_
+#define SRC_TINT_LANG_CORE_BUILTIN_TYPE_H_
 
 #include <cstdint>
 
@@ -25,9 +25,9 @@
 
 namespace tint::core {
 
-/// An enumerator of builtin builtin.
+/// An enumerator of builtin types.
 {{ Eval "DeclareEnum" $enum}}
 
 }  // namespace tint::core
 
-#endif  // SRC_TINT_LANG_CORE_BUILTIN_H_
+#endif  // SRC_TINT_LANG_CORE_BUILTIN_TYPE_H_
diff --git a/src/tint/lang/core/builtin_bench.cc b/src/tint/lang/core/builtin_type_bench.cc
similarity index 98%
rename from src/tint/lang/core/builtin_bench.cc
rename to src/tint/lang/core/builtin_type_bench.cc
index d9eaccc..20d438f 100644
--- a/src/tint/lang/core/builtin_bench.cc
+++ b/src/tint/lang/core/builtin_type_bench.cc
@@ -14,14 +14,14 @@
 
 ////////////////////////////////////////////////////////////////////////////////
 // File generated by 'tools/src/cmd/gen' using the template:
-//   src/tint/lang/core/builtin_bench.cc.tmpl
+//   src/tint/lang/core/builtin_type_bench.cc.tmpl
 //
 // To regenerate run: './tools/run gen'
 //
 //                       Do not modify this file directly
 ////////////////////////////////////////////////////////////////////////////////
 
-#include "src/tint/lang/core/builtin.h"
+#include "src/tint/lang/core/builtin_type.h"
 
 #include <array>
 
@@ -30,7 +30,7 @@
 namespace tint::core {
 namespace {
 
-void BuiltinParser(::benchmark::State& state) {
+void BuiltinTypeParser(::benchmark::State& state) {
     const char* kStrings[] = {
         "__atomic_compareexchangeccresult_i32",
         "__atoml3_compare_exchane_resulti2",
@@ -707,13 +707,13 @@
     };
     for (auto _ : state) {
         for (auto* str : kStrings) {
-            auto result = ParseBuiltin(str);
+            auto result = ParseBuiltinType(str);
             benchmark::DoNotOptimize(result);
         }
     }
 }  // NOLINT(readability/fn_size)
 
-BENCHMARK(BuiltinParser);
+BENCHMARK(BuiltinTypeParser);
 
 }  // namespace
 }  // namespace tint::core
diff --git a/src/tint/lang/core/builtin_bench.cc.tmpl b/src/tint/lang/core/builtin_type_bench.cc.tmpl
similarity index 85%
rename from src/tint/lang/core/builtin_bench.cc.tmpl
rename to src/tint/lang/core/builtin_type_bench.cc.tmpl
index 004f675..ab5af9d 100644
--- a/src/tint/lang/core/builtin_bench.cc.tmpl
+++ b/src/tint/lang/core/builtin_type_bench.cc.tmpl
@@ -1,6 +1,6 @@
 {{- /*
 --------------------------------------------------------------------------------
-Template file for use with tools/src/cmd/gen to generate builtin_bench.cc
+Template file for use with tools/src/cmd/gen to generate builtin_type_bench.cc
 
 To update the generated file, run:
     ./tools/run gen
@@ -14,9 +14,9 @@
 {{- $I := LoadIntrinsics "src/tint/lang/core/core.def" -}}
 {{- Import "src/tint/utils/templates/enums.tmpl.inc" -}}
 {{- $enum := ($I.Sem.Enum "builtin_type") -}}
-{{- Eval "OverrideEnumName" "Enum" $enum "Name" "Builtin" -}}
+{{- Eval "OverrideEnumName" "Enum" $enum "Name" "BuiltinType" -}}
 
-#include "src/tint/lang/core/builtin.h"
+#include "src/tint/lang/core/builtin_type.h"
 
 #include <array>
 
diff --git a/src/tint/lang/core/builtin_type_test.cc b/src/tint/lang/core/builtin_type_test.cc
new file mode 100644
index 0000000..d69f938
--- /dev/null
+++ b/src/tint/lang/core/builtin_type_test.cc
@@ -0,0 +1,460 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+////////////////////////////////////////////////////////////////////////////////
+// File generated by 'tools/src/cmd/gen' using the template:
+//   src/tint/lang/core/builtin_type_test.cc.tmpl
+//
+// To regenerate run: './tools/run gen'
+//
+//                       Do not modify this file directly
+////////////////////////////////////////////////////////////////////////////////
+
+#include "src/tint/lang/core/builtin_type.h"
+
+#include <string>
+
+#include "gtest/gtest.h"
+
+#include "src/tint/utils/text/string.h"
+
+namespace tint::core {
+namespace {
+
+namespace parse_print_tests {
+
+struct Case {
+    const char* string;
+    BuiltinType value;
+};
+
+inline std::ostream& operator<<(std::ostream& out, Case c) {
+    return out << "'" << std::string(c.string) << "'";
+}
+
+static constexpr Case kValidCases[] = {
+    {"__atomic_compare_exchange_result_i32", BuiltinType::kAtomicCompareExchangeResultI32},
+    {"__atomic_compare_exchange_result_u32", BuiltinType::kAtomicCompareExchangeResultU32},
+    {"__frexp_result_abstract", BuiltinType::kFrexpResultAbstract},
+    {"__frexp_result_f16", BuiltinType::kFrexpResultF16},
+    {"__frexp_result_f32", BuiltinType::kFrexpResultF32},
+    {"__frexp_result_vec2_abstract", BuiltinType::kFrexpResultVec2Abstract},
+    {"__frexp_result_vec2_f16", BuiltinType::kFrexpResultVec2F16},
+    {"__frexp_result_vec2_f32", BuiltinType::kFrexpResultVec2F32},
+    {"__frexp_result_vec3_abstract", BuiltinType::kFrexpResultVec3Abstract},
+    {"__frexp_result_vec3_f16", BuiltinType::kFrexpResultVec3F16},
+    {"__frexp_result_vec3_f32", BuiltinType::kFrexpResultVec3F32},
+    {"__frexp_result_vec4_abstract", BuiltinType::kFrexpResultVec4Abstract},
+    {"__frexp_result_vec4_f16", BuiltinType::kFrexpResultVec4F16},
+    {"__frexp_result_vec4_f32", BuiltinType::kFrexpResultVec4F32},
+    {"__modf_result_abstract", BuiltinType::kModfResultAbstract},
+    {"__modf_result_f16", BuiltinType::kModfResultF16},
+    {"__modf_result_f32", BuiltinType::kModfResultF32},
+    {"__modf_result_vec2_abstract", BuiltinType::kModfResultVec2Abstract},
+    {"__modf_result_vec2_f16", BuiltinType::kModfResultVec2F16},
+    {"__modf_result_vec2_f32", BuiltinType::kModfResultVec2F32},
+    {"__modf_result_vec3_abstract", BuiltinType::kModfResultVec3Abstract},
+    {"__modf_result_vec3_f16", BuiltinType::kModfResultVec3F16},
+    {"__modf_result_vec3_f32", BuiltinType::kModfResultVec3F32},
+    {"__modf_result_vec4_abstract", BuiltinType::kModfResultVec4Abstract},
+    {"__modf_result_vec4_f16", BuiltinType::kModfResultVec4F16},
+    {"__modf_result_vec4_f32", BuiltinType::kModfResultVec4F32},
+    {"__packed_vec3", BuiltinType::kPackedVec3},
+    {"array", BuiltinType::kArray},
+    {"atomic", BuiltinType::kAtomic},
+    {"bool", BuiltinType::kBool},
+    {"f16", BuiltinType::kF16},
+    {"f32", BuiltinType::kF32},
+    {"i32", BuiltinType::kI32},
+    {"mat2x2", BuiltinType::kMat2X2},
+    {"mat2x2f", BuiltinType::kMat2X2F},
+    {"mat2x2h", BuiltinType::kMat2X2H},
+    {"mat2x3", BuiltinType::kMat2X3},
+    {"mat2x3f", BuiltinType::kMat2X3F},
+    {"mat2x3h", BuiltinType::kMat2X3H},
+    {"mat2x4", BuiltinType::kMat2X4},
+    {"mat2x4f", BuiltinType::kMat2X4F},
+    {"mat2x4h", BuiltinType::kMat2X4H},
+    {"mat3x2", BuiltinType::kMat3X2},
+    {"mat3x2f", BuiltinType::kMat3X2F},
+    {"mat3x2h", BuiltinType::kMat3X2H},
+    {"mat3x3", BuiltinType::kMat3X3},
+    {"mat3x3f", BuiltinType::kMat3X3F},
+    {"mat3x3h", BuiltinType::kMat3X3H},
+    {"mat3x4", BuiltinType::kMat3X4},
+    {"mat3x4f", BuiltinType::kMat3X4F},
+    {"mat3x4h", BuiltinType::kMat3X4H},
+    {"mat4x2", BuiltinType::kMat4X2},
+    {"mat4x2f", BuiltinType::kMat4X2F},
+    {"mat4x2h", BuiltinType::kMat4X2H},
+    {"mat4x3", BuiltinType::kMat4X3},
+    {"mat4x3f", BuiltinType::kMat4X3F},
+    {"mat4x3h", BuiltinType::kMat4X3H},
+    {"mat4x4", BuiltinType::kMat4X4},
+    {"mat4x4f", BuiltinType::kMat4X4F},
+    {"mat4x4h", BuiltinType::kMat4X4H},
+    {"ptr", BuiltinType::kPtr},
+    {"sampler", BuiltinType::kSampler},
+    {"sampler_comparison", BuiltinType::kSamplerComparison},
+    {"texture_1d", BuiltinType::kTexture1D},
+    {"texture_2d", BuiltinType::kTexture2D},
+    {"texture_2d_array", BuiltinType::kTexture2DArray},
+    {"texture_3d", BuiltinType::kTexture3D},
+    {"texture_cube", BuiltinType::kTextureCube},
+    {"texture_cube_array", BuiltinType::kTextureCubeArray},
+    {"texture_depth_2d", BuiltinType::kTextureDepth2D},
+    {"texture_depth_2d_array", BuiltinType::kTextureDepth2DArray},
+    {"texture_depth_cube", BuiltinType::kTextureDepthCube},
+    {"texture_depth_cube_array", BuiltinType::kTextureDepthCubeArray},
+    {"texture_depth_multisampled_2d", BuiltinType::kTextureDepthMultisampled2D},
+    {"texture_external", BuiltinType::kTextureExternal},
+    {"texture_multisampled_2d", BuiltinType::kTextureMultisampled2D},
+    {"texture_storage_1d", BuiltinType::kTextureStorage1D},
+    {"texture_storage_2d", BuiltinType::kTextureStorage2D},
+    {"texture_storage_2d_array", BuiltinType::kTextureStorage2DArray},
+    {"texture_storage_3d", BuiltinType::kTextureStorage3D},
+    {"u32", BuiltinType::kU32},
+    {"vec2", BuiltinType::kVec2},
+    {"vec2f", BuiltinType::kVec2F},
+    {"vec2h", BuiltinType::kVec2H},
+    {"vec2i", BuiltinType::kVec2I},
+    {"vec2u", BuiltinType::kVec2U},
+    {"vec3", BuiltinType::kVec3},
+    {"vec3f", BuiltinType::kVec3F},
+    {"vec3h", BuiltinType::kVec3H},
+    {"vec3i", BuiltinType::kVec3I},
+    {"vec3u", BuiltinType::kVec3U},
+    {"vec4", BuiltinType::kVec4},
+    {"vec4f", BuiltinType::kVec4F},
+    {"vec4h", BuiltinType::kVec4H},
+    {"vec4i", BuiltinType::kVec4I},
+    {"vec4u", BuiltinType::kVec4U},
+};
+
+static constexpr Case kInvalidCases[] = {
+    {"__atomic_compareexchangeccresult_i32", BuiltinType::kUndefined},
+    {"__atoml3_compare_exchane_resulti2", BuiltinType::kUndefined},
+    {"__atomic_compare_Vxchange_result_i32", BuiltinType::kUndefined},
+    {"__atomic_com1are_exchange_result_u32", BuiltinType::kUndefined},
+    {"__atomic_qqompare_exchage_resulJ_u32", BuiltinType::kUndefined},
+    {"__atllmic_compare_exchange_result_u377", BuiltinType::kUndefined},
+    {"qpp_frexp_resultHHbstract", BuiltinType::kUndefined},
+    {"__fep_esulv_abstract", BuiltinType::kUndefined},
+    {"__Gbexp_resul_abstract", BuiltinType::kUndefined},
+    {"_vfrexp_resiilt_f16", BuiltinType::kUndefined},
+    {"__fr8xp_resultWWf16", BuiltinType::kUndefined},
+    {"__frxp_result_fMxx", BuiltinType::kUndefined},
+    {"gg_fXexp_reslt_f32", BuiltinType::kUndefined},
+    {"__frXxpresul_V32", BuiltinType::kUndefined},
+    {"__frexp_r3sult_f32", BuiltinType::kUndefined},
+    {"__frexpEresult_vec2_abstract", BuiltinType::kUndefined},
+    {"__frex_rPPsult_vTTc2_abstract", BuiltinType::kUndefined},
+    {"__frexp_resuddt_ec2_xxbstract", BuiltinType::kUndefined},
+    {"__frexp_result_ve442_f16", BuiltinType::kUndefined},
+    {"_SSfrexp_resulVV_vec2_f16", BuiltinType::kUndefined},
+    {"__fRxpRr22sult_vec2_f16", BuiltinType::kUndefined},
+    {"__frexp_res9lt_vec_fF2", BuiltinType::kUndefined},
+    {"__frexp_result_ve2_f32", BuiltinType::kUndefined},
+    {"_OOfrexp_result_VeHRRf32", BuiltinType::kUndefined},
+    {"__frexp_reyult_vec3_absract", BuiltinType::kUndefined},
+    {"__frexp_re77ulll_vecG_arrnstract", BuiltinType::kUndefined},
+    {"__4rexp_result_vec3_00bstract", BuiltinType::kUndefined},
+    {"__oorxp_result_vec316", BuiltinType::kUndefined},
+    {"zz_frexp_esult_ec3_f16", BuiltinType::kUndefined},
+    {"__iirex11_result_vp3_f16", BuiltinType::kUndefined},
+    {"__frXXxp_result_vec3_f32", BuiltinType::kUndefined},
+    {"__fnnexp99resIIlt_vec3_f355", BuiltinType::kUndefined},
+    {"__faSSerrp_result_vHHc3_fY2", BuiltinType::kUndefined},
+    {"__freHp_resutve4_abstkkact", BuiltinType::kUndefined},
+    {"jfrexpgresult_veRR4_abstrac", BuiltinType::kUndefined},
+    {"__frexp_resul_vec4_absbrac", BuiltinType::kUndefined},
+    {"_jfrexp_result_vec4_f16", BuiltinType::kUndefined},
+    {"__frexp_resultvec4_f16", BuiltinType::kUndefined},
+    {"__freqpresultvec4_f16", BuiltinType::kUndefined},
+    {"__frexNN_result_vec_f32", BuiltinType::kUndefined},
+    {"__frexp_resvvlt_vc4_f3", BuiltinType::kUndefined},
+    {"__frexp_esult_vec4_f3QQ", BuiltinType::kUndefined},
+    {"rmodf_reffultabstract", BuiltinType::kUndefined},
+    {"__jodf_result_abstract", BuiltinType::kUndefined},
+    {"_mNNwdf_r2sult8abstract", BuiltinType::kUndefined},
+    {"__mdf_result_f16", BuiltinType::kUndefined},
+    {"__modrr_result_f16", BuiltinType::kUndefined},
+    {"__mGdf_result_f16", BuiltinType::kUndefined},
+    {"__modf_resulFF_f32", BuiltinType::kUndefined},
+    {"__modf_eult_E3", BuiltinType::kUndefined},
+    {"__odf_resurrt_f32", BuiltinType::kUndefined},
+    {"__modf_reslt_vec_abstract", BuiltinType::kUndefined},
+    {"__modfJJresuDt_Xc2_abstract", BuiltinType::kUndefined},
+    {"_modf_reslt_vec28abstrct", BuiltinType::kUndefined},
+    {"__odf_reult_vkc211f1", BuiltinType::kUndefined},
+    {"__mdf_result_vec2_f16", BuiltinType::kUndefined},
+    {"__modf_resuJt_vec2_f6", BuiltinType::kUndefined},
+    {"__modf_result_vec2cf32", BuiltinType::kUndefined},
+    {"__modf_result_vec2_fO2", BuiltinType::kUndefined},
+    {"KK_movvf_result_vec2_ftt__", BuiltinType::kUndefined},
+    {"xx_modf_r8sult_vec3_abtr5ct", BuiltinType::kUndefined},
+    {"__modf_resuFt_vec3_aqt__act", BuiltinType::kUndefined},
+    {"__modf_result_vec3_aqqstrac", BuiltinType::kUndefined},
+    {"__odf_33esult_vec3_f1O6", BuiltinType::kUndefined},
+    {"_ttm6df_resQQlt_ooec9_f16", BuiltinType::kUndefined},
+    {"_modf_resu66t_vec3_f16", BuiltinType::kUndefined},
+    {"__mdf_resultOvxc3_f36zz", BuiltinType::kUndefined},
+    {"__modf_resuyyt_vec3_f32", BuiltinType::kUndefined},
+    {"__mod_resul_vecZHHf32", BuiltinType::kUndefined},
+    {"__modf_reqult_44ec4WWbstract", BuiltinType::kUndefined},
+    {"__mof_result_vec4_abstrOOct", BuiltinType::kUndefined},
+    {"__modYooresult_vh4_bstract", BuiltinType::kUndefined},
+    {"__modf_relt_ve4_f16", BuiltinType::kUndefined},
+    {"__modf_result_ve4Ff16", BuiltinType::kUndefined},
+    {"__modf_result_wec4_f1", BuiltinType::kUndefined},
+    {"__Kdff_rGsult_vec4_f2", BuiltinType::kUndefined},
+    {"__modf_reKKulq_vec4_f32", BuiltinType::kUndefined},
+    {"__modf_resummt3vec4_f3F", BuiltinType::kUndefined},
+    {"__packed_ec3", BuiltinType::kUndefined},
+    {"__packed_ecq", BuiltinType::kUndefined},
+    {"_backed_bbec3", BuiltinType::kUndefined},
+    {"iira", BuiltinType::kUndefined},
+    {"aqOOy", BuiltinType::kUndefined},
+    {"arvvTTy", BuiltinType::kUndefined},
+    {"atomFFc", BuiltinType::kUndefined},
+    {"aoQ00P", BuiltinType::kUndefined},
+    {"atPmic", BuiltinType::kUndefined},
+    {"bos77", BuiltinType::kUndefined},
+    {"CoRbbl", BuiltinType::kUndefined},
+    {"booXX", BuiltinType::kUndefined},
+    {"qOOO6", BuiltinType::kUndefined},
+    {"fs", BuiltinType::kUndefined},
+    {"f1X", BuiltinType::kUndefined},
+    {"f3", BuiltinType::kUndefined},
+    {"q", BuiltinType::kUndefined},
+    {"f322", BuiltinType::kUndefined},
+    {"0yz2", BuiltinType::kUndefined},
+    {"iVP", BuiltinType::kUndefined},
+    {"Cnn", BuiltinType::kUndefined},
+    {"AtqqHH2", BuiltinType::kUndefined},
+    {"at2x2", BuiltinType::kUndefined},
+    {"mafKK", BuiltinType::kUndefined},
+    {"ltgg2f", BuiltinType::kUndefined},
+    {"mat2xf", BuiltinType::kUndefined},
+    {"NTTtcx4f", BuiltinType::kUndefined},
+    {"ma7ppl2h", BuiltinType::kUndefined},
+    {"mNNt2xg", BuiltinType::kUndefined},
+    {"uub2XX2h", BuiltinType::kUndefined},
+    {"mt2x3", BuiltinType::kUndefined},
+    {"m88xK", BuiltinType::kUndefined},
+    {"maqx3", BuiltinType::kUndefined},
+    {"m11t2x3f", BuiltinType::kUndefined},
+    {"22at2iif", BuiltinType::kUndefined},
+    {"at2x377", BuiltinType::kUndefined},
+    {"m2t2xNh", BuiltinType::kUndefined},
+    {"mVVt2x3h", BuiltinType::kUndefined},
+    {"FaWW2w11h", BuiltinType::kUndefined},
+    {"matww4", BuiltinType::kUndefined},
+    {"mat2D4", BuiltinType::kUndefined},
+    {"maKx4", BuiltinType::kUndefined},
+    {"mat21PPhf", BuiltinType::kUndefined},
+    {"mat24f", BuiltinType::kUndefined},
+    {"mYYt2x4f", BuiltinType::kUndefined},
+    {"mttHH4kk", BuiltinType::kUndefined},
+    {"mat2rr4h", BuiltinType::kUndefined},
+    {"WWas2x4h", BuiltinType::kUndefined},
+    {"maYx2", BuiltinType::kUndefined},
+    {"mq3f2", BuiltinType::kUndefined},
+    {"vvafu222", BuiltinType::kUndefined},
+    {"t3x2f", BuiltinType::kUndefined},
+    {"YYat3f", BuiltinType::kUndefined},
+    {"may3x2EYY", BuiltinType::kUndefined},
+    {"da3xMoh", BuiltinType::kUndefined},
+    {"matMMx2", BuiltinType::kUndefined},
+    {"mat3x55h", BuiltinType::kUndefined},
+    {"maN3", BuiltinType::kUndefined},
+    {"ma33x3", BuiltinType::kUndefined},
+    {"mt3x3", BuiltinType::kUndefined},
+    {"mm66Issf", BuiltinType::kUndefined},
+    {"mat3x1f", BuiltinType::kUndefined},
+    {"Xt3x3", BuiltinType::kUndefined},
+    {"LatIx3h", BuiltinType::kUndefined},
+    {"at3fh", BuiltinType::kUndefined},
+    {"mYtURD3", BuiltinType::kUndefined},
+    {"mah3x4", BuiltinType::kUndefined},
+    {"muqII4", BuiltinType::kUndefined},
+    {"mat3xH", BuiltinType::kUndefined},
+    {"at3QQvv", BuiltinType::kUndefined},
+    {"at66ef", BuiltinType::kUndefined},
+    {"ma7O4f", BuiltinType::kUndefined},
+    {"m55t3x0DD", BuiltinType::kUndefined},
+    {"maH3x4II", BuiltinType::kUndefined},
+    {"at3x4", BuiltinType::kUndefined},
+    {"ma994x2", BuiltinType::kUndefined},
+    {"mWWt4Gt2", BuiltinType::kUndefined},
+    {"ay42", BuiltinType::kUndefined},
+    {"mt4x2f", BuiltinType::kUndefined},
+    {"IIaBB4x2f", BuiltinType::kUndefined},
+    {"TTat4x833", BuiltinType::kUndefined},
+    {"ddUUnntYYx2h", BuiltinType::kUndefined},
+    {"m5CCxxdZ", BuiltinType::kUndefined},
+    {"matkkq2h", BuiltinType::kUndefined},
+    {"5iitp00", BuiltinType::kUndefined},
+    {"mnntIIx3", BuiltinType::kUndefined},
+    {"ccaKx", BuiltinType::kUndefined},
+    {"m43KK", BuiltinType::kUndefined},
+    {"mat66x3f", BuiltinType::kUndefined},
+    {"Et4PP3K", BuiltinType::kUndefined},
+    {"xxatx3h", BuiltinType::kUndefined},
+    {"qat4x3h", BuiltinType::kUndefined},
+    {"MMayySrxh", BuiltinType::kUndefined},
+    {"uat4", BuiltinType::kUndefined},
+    {"tx4", BuiltinType::kUndefined},
+    {"ma54FF4", BuiltinType::kUndefined},
+    {"rra444z4f", BuiltinType::kUndefined},
+    {"matWW", BuiltinType::kUndefined},
+    {"CatZJXx4f", BuiltinType::kUndefined},
+    {"maPPx4h", BuiltinType::kUndefined},
+    {"mat4c4h", BuiltinType::kUndefined},
+    {"matPPll6h", BuiltinType::kUndefined},
+    {"9tyy", BuiltinType::kUndefined},
+    {"ptKK", BuiltinType::kUndefined},
+    {"x_", BuiltinType::kUndefined},
+    {"ayKer", BuiltinType::kUndefined},
+    {"szmpVek", BuiltinType::kUndefined},
+    {"sampqeK", BuiltinType::kUndefined},
+    {"sampler_comparisn", BuiltinType::kUndefined},
+    {"sapler_comparisVVn", BuiltinType::kUndefined},
+    {"samplerIcompaAUison", BuiltinType::kUndefined},
+    {"jexurbRd", BuiltinType::kUndefined},
+    {"exure_YYd", BuiltinType::kUndefined},
+    {"exture_1d", BuiltinType::kUndefined},
+    {"texxxur_1d", BuiltinType::kUndefined},
+    {"tJxucce_2d", BuiltinType::kUndefined},
+    {"texure_JJd", BuiltinType::kUndefined},
+    {"lDexture_fCC_arraU", BuiltinType::kUndefined},
+    {"tegture_2d_array", BuiltinType::kUndefined},
+    {"teCCure2d_arra", BuiltinType::kUndefined},
+    {"textue_3d", BuiltinType::kUndefined},
+    {"tIx__ure_3d", BuiltinType::kUndefined},
+    {"texurettPP", BuiltinType::kUndefined},
+    {"tddx3ure_cube", BuiltinType::kUndefined},
+    {"teKyyur_cube", BuiltinType::kUndefined},
+    {"teturecub", BuiltinType::kUndefined},
+    {"textinne_c03e_array", BuiltinType::kUndefined},
+    {"nextCCruuvcubK_array", BuiltinType::kUndefined},
+    {"tXxturellcbe_array", BuiltinType::kUndefined},
+    {"tppxture_depth_2d", BuiltinType::kUndefined},
+    {"txture_deptww_2d", BuiltinType::kUndefined},
+    {"gexturedemmthuu2", BuiltinType::kUndefined},
+    {"texmmre_depthaa2daray", BuiltinType::kUndefined},
+    {"texture_RRepth_Td_ccZray", BuiltinType::kUndefined},
+    {"text88re_depthTOd_array", BuiltinType::kUndefined},
+    {"texture_depth_cm00e", BuiltinType::kUndefined},
+    {"texture_Bmepth_cube", BuiltinType::kUndefined},
+    {"Mextre_ppeph_cube", BuiltinType::kUndefined},
+    {"texturOO_depth_cub_array", BuiltinType::kUndefined},
+    {"GeGGture_depthcube_array", BuiltinType::kUndefined},
+    {"texture11Hdepth_cube_array", BuiltinType::kUndefined},
+    {"textu6e_FFepth_multiameeled_2d", BuiltinType::kUndefined},
+    {"texture_epth_mltisampled_2d", BuiltinType::kUndefined},
+    {"texture_depth_mullKsaiipled_2d", BuiltinType::kUndefined},
+    {"texture_extenal", BuiltinType::kUndefined},
+    {"IIext99reexvvernal", BuiltinType::kUndefined},
+    {"texture_externl", BuiltinType::kUndefined},
+    {"texture_mhltisampled_2d", BuiltinType::kUndefined},
+    {"texturemPllltisampzzed_2d", BuiltinType::kUndefined},
+    {"exture_mltisamed_2d", BuiltinType::kUndefined},
+    {"texture_qqtoragff_1", BuiltinType::kUndefined},
+    {"textre_JJddorage_1W", BuiltinType::kUndefined},
+    {"XXrxture_storae1zz", BuiltinType::kUndefined},
+    {"texturestorag2_2d", BuiltinType::kUndefined},
+    {"yyNxture_storage_2d", BuiltinType::kUndefined},
+    {"etue_storage_2OO", BuiltinType::kUndefined},
+    {"reutuPe_storZgeE2d_array", BuiltinType::kUndefined},
+    {"texlure_storddeee_d_22rray", BuiltinType::kUndefined},
+    {"texture_mtorage_2V_a9ra", BuiltinType::kUndefined},
+    {"teII1re_storage_3d", BuiltinType::kUndefined},
+    {"texture_storagb_3d", BuiltinType::kUndefined},
+    {"texizrestorge73d", BuiltinType::kUndefined},
+    {"u3oi", BuiltinType::kUndefined},
+    {"3", BuiltinType::kUndefined},
+    {"S2", BuiltinType::kUndefined},
+    {"e22", BuiltinType::kUndefined},
+    {"1eC2", BuiltinType::kUndefined},
+    {"vf8c2", BuiltinType::kUndefined},
+    {"c2f", BuiltinType::kUndefined},
+    {"JJecSSf", BuiltinType::kUndefined},
+    {"92f", BuiltinType::kUndefined},
+    {"vbbJJ2TT", BuiltinType::kUndefined},
+    {"e66h", BuiltinType::kUndefined},
+    {"u662h", BuiltinType::kUndefined},
+    {"vW2i", BuiltinType::kUndefined},
+    {"v2i", BuiltinType::kUndefined},
+    {"veci", BuiltinType::kUndefined},
+    {"rec2u", BuiltinType::kUndefined},
+    {"2ec2B", BuiltinType::kUndefined},
+    {"vcBBu", BuiltinType::kUndefined},
+    {"veRR", BuiltinType::kUndefined},
+    {"VLL0", BuiltinType::kUndefined},
+    {"KOe3", BuiltinType::kUndefined},
+    {"vgwcf", BuiltinType::kUndefined},
+    {"vLphf", BuiltinType::kUndefined},
+    {"eiiEf", BuiltinType::kUndefined},
+    {"ec3h", BuiltinType::kUndefined},
+    {"UU883", BuiltinType::kUndefined},
+    {"rrecvvh", BuiltinType::kUndefined},
+    {"ecmm", BuiltinType::kUndefined},
+    {"vec4j", BuiltinType::kUndefined},
+    {"vec3X", BuiltinType::kUndefined},
+    {"vec38", BuiltinType::kUndefined},
+    {"vecvEE", BuiltinType::kUndefined},
+    {"z99ci", BuiltinType::kUndefined},
+    {"JJGeQQ4", BuiltinType::kUndefined},
+    {"ssec4", BuiltinType::kUndefined},
+    {"PecK", BuiltinType::kUndefined},
+    {"tpc4f", BuiltinType::kUndefined},
+    {"vec", BuiltinType::kUndefined},
+    {"MMec4f", BuiltinType::kUndefined},
+    {"vJJc40", BuiltinType::kUndefined},
+    {"8c", BuiltinType::kUndefined},
+    {"vecggKh", BuiltinType::kUndefined},
+    {"vecfi", BuiltinType::kUndefined},
+    {"vec47Q", BuiltinType::kUndefined},
+    {"veY4i", BuiltinType::kUndefined},
+    {"keSu", BuiltinType::kUndefined},
+    {"n422", BuiltinType::kUndefined},
+    {"vFFu", BuiltinType::kUndefined},
+};
+
+using BuiltinTypeParseTest = testing::TestWithParam<Case>;
+
+TEST_P(BuiltinTypeParseTest, Parse) {
+    const char* string = GetParam().string;
+    BuiltinType expect = GetParam().value;
+    EXPECT_EQ(expect, ParseBuiltinType(string));
+}
+
+INSTANTIATE_TEST_SUITE_P(ValidCases, BuiltinTypeParseTest, testing::ValuesIn(kValidCases));
+INSTANTIATE_TEST_SUITE_P(InvalidCases, BuiltinTypeParseTest, testing::ValuesIn(kInvalidCases));
+
+using BuiltinTypePrintTest = testing::TestWithParam<Case>;
+
+TEST_P(BuiltinTypePrintTest, Print) {
+    BuiltinType value = GetParam().value;
+    const char* expect = GetParam().string;
+    EXPECT_EQ(expect, tint::ToString(value));
+}
+
+INSTANTIATE_TEST_SUITE_P(ValidCases, BuiltinTypePrintTest, testing::ValuesIn(kValidCases));
+
+}  // namespace parse_print_tests
+
+}  // namespace
+}  // namespace tint::core
diff --git a/src/tint/lang/core/builtin_test.cc.tmpl b/src/tint/lang/core/builtin_type_test.cc.tmpl
similarity index 86%
rename from src/tint/lang/core/builtin_test.cc.tmpl
rename to src/tint/lang/core/builtin_type_test.cc.tmpl
index c12911e..b88a32f 100644
--- a/src/tint/lang/core/builtin_test.cc.tmpl
+++ b/src/tint/lang/core/builtin_type_test.cc.tmpl
@@ -1,6 +1,6 @@
 {{- /*
 --------------------------------------------------------------------------------
-Template file for use with tools/src/cmd/gen to generate builtin_test.cc
+Template file for use with tools/src/cmd/gen to generate builtin_type_test.cc
 
 To update the generated file, run:
     ./tools/run gen
@@ -14,9 +14,9 @@
 {{- $I := LoadIntrinsics "src/tint/lang/core/core.def" -}}
 {{- Import "src/tint/utils/templates/enums.tmpl.inc" -}}
 {{- $enum := ($I.Sem.Enum "builtin_type") -}}
-{{- Eval "OverrideEnumName" "Enum" $enum "Name" "Builtin" -}}
+{{- Eval "OverrideEnumName" "Enum" $enum "Name" "BuiltinType" -}}
 
-#include "src/tint/lang/core/builtin.h"
+#include "src/tint/lang/core/builtin_type.h"
 
 #include <string>
 
diff --git a/src/tint/lang/core/constant/eval_builtin_test.cc b/src/tint/lang/core/constant/eval_builtin_test.cc
index 6a31120..cca292d 100644
--- a/src/tint/lang/core/constant/eval_builtin_test.cc
+++ b/src/tint/lang/core/constant/eval_builtin_test.cc
@@ -139,7 +139,7 @@
     return Case{std::move(args), std::move(err)};
 }
 
-using ConstEvalBuiltinTest = ConstEvalTestWithParam<std::tuple<core::Function, Case>>;
+using ConstEvalBuiltinTest = ConstEvalTestWithParam<std::tuple<core::BuiltinFn, Case>>;
 
 TEST_P(ConstEvalBuiltinTest, Test) {
     Enable(wgsl::Extension::kF16);
@@ -186,7 +186,7 @@
 
 INSTANTIATE_TEST_SUITE_P(MixedAbstractArgs_Atan2,
                          ConstEvalBuiltinTest,
-                         testing::Combine(testing::Values(core::Function::kAtan2),
+                         testing::Combine(testing::Values(core::BuiltinFn::kAtan2),
                                           testing::ValuesIn(std::vector{
                                               C({0.0_a, 1_a}, AFloat{0}),
                                               C({0_a, 1.0_a}, AFloat{0}),
@@ -196,7 +196,7 @@
 
 INSTANTIATE_TEST_SUITE_P(MixedAbstractArgs_Max,
                          ConstEvalBuiltinTest,
-                         testing::Combine(testing::Values(core::Function::kMax),
+                         testing::Combine(testing::Values(core::BuiltinFn::kMax),
                                           testing::ValuesIn(std::vector{
                                               // AbstractInt first, AbstractFloat second
                                               C({1_a, 2.0_a}, AFloat{2}),
@@ -247,7 +247,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Abs,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kAbs),
+    testing::Combine(testing::Values(core::BuiltinFn::kAbs),
                      testing::ValuesIn(Concat(AbsCases<AInt>(),  //
                                               AbsCases<i32>(),
                                               AbsCases<u32>(),
@@ -282,7 +282,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     All,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kAll), testing::ValuesIn(AllCases())));
+    testing::Combine(testing::Values(core::BuiltinFn::kAll), testing::ValuesIn(AllCases())));
 
 static std::vector<Case> AnyCases() {
     return {
@@ -311,7 +311,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Any,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kAny), testing::ValuesIn(AnyCases())));
+    testing::Combine(testing::Values(core::BuiltinFn::kAny), testing::ValuesIn(AnyCases())));
 
 template <typename T>
 std::vector<Case> Atan2Cases() {
@@ -346,7 +346,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Atan2,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kAtan2),
+    testing::Combine(testing::Values(core::BuiltinFn::kAtan2),
                      testing::ValuesIn(Concat(Atan2Cases<AFloat>(),  //
                                               Atan2Cases<f32>(),
                                               Atan2Cases<f16>()))));
@@ -367,7 +367,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Atan,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kAtan),
+    testing::Combine(testing::Values(core::BuiltinFn::kAtan),
                      testing::ValuesIn(Concat(AtanCases<AFloat>(),  //
                                               AtanCases<f32>(),
                                               AtanCases<f16>()))));
@@ -392,7 +392,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Atanh,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kAtanh),
+    testing::Combine(testing::Values(core::BuiltinFn::kAtanh),
                      testing::ValuesIn(Concat(AtanhCases<AFloat>(),  //
                                               AtanhCases<f32>(),
                                               AtanhCases<f16>()))));
@@ -418,7 +418,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Acos,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kAcos),
+    testing::Combine(testing::Values(core::BuiltinFn::kAcos),
                      testing::ValuesIn(Concat(AcosCases<AFloat>(),  //
                                               AcosCases<f32>(),
                                               AcosCases<f16>()))));
@@ -440,7 +440,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Acosh,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kAcosh),
+    testing::Combine(testing::Values(core::BuiltinFn::kAcosh),
                      testing::ValuesIn(Concat(AcoshCases<AFloat>(),  //
                                               AcoshCases<f32>(),
                                               AcoshCases<f16>()))));
@@ -467,7 +467,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Asin,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kAsin),
+    testing::Combine(testing::Values(core::BuiltinFn::kAsin),
                      testing::ValuesIn(Concat(AsinCases<AFloat>(),  //
                                               AsinCases<f32>(),
                                               AsinCases<f16>()))));
@@ -491,7 +491,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Asinh,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kAsinh),
+    testing::Combine(testing::Values(core::BuiltinFn::kAsinh),
                      testing::ValuesIn(Concat(AsinhCases<AFloat>(),  //
                                               AsinhCases<f32>(),
                                               AsinhCases<f16>()))));
@@ -513,7 +513,7 @@
     Ceil,
     ConstEvalBuiltinTest,
     testing::Combine(
-        testing::Values(core::Function::kCeil),
+        testing::Values(core::BuiltinFn::kCeil),
         testing::ValuesIn(Concat(CeilCases<AFloat>(), CeilCases<f32>(), CeilCases<f16>()))));
 
 template <typename T>
@@ -558,7 +558,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Clamp,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kClamp),
+    testing::Combine(testing::Values(core::BuiltinFn::kClamp),
                      testing::ValuesIn(Concat(ClampCases<AInt>(),  //
                                               ClampCases<i32>(),
                                               ClampCases<u32>(),
@@ -581,7 +581,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Cos,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kCos),
+    testing::Combine(testing::Values(core::BuiltinFn::kCos),
                      testing::ValuesIn(Concat(CosCases<AFloat>(),  //
                                               CosCases<f32>(),
                                               CosCases<f16>()))));
@@ -607,7 +607,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Cosh,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kCosh),
+    testing::Combine(testing::Values(core::BuiltinFn::kCosh),
                      testing::ValuesIn(Concat(CoshCases<AFloat>(),  //
                                               CoshCases<f32>(),
                                               CoshCases<f16>()))));
@@ -654,7 +654,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     CountLeadingZeros,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kCountLeadingZeros),
+    testing::Combine(testing::Values(core::BuiltinFn::kCountLeadingZeros),
                      testing::ValuesIn(Concat(CountLeadingZerosCases<i32>(),  //
                                               CountLeadingZerosCases<u32>()))));
 
@@ -700,7 +700,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     CountTrailingZeros,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kCountTrailingZeros),
+    testing::Combine(testing::Values(core::BuiltinFn::kCountTrailingZeros),
                      testing::ValuesIn(Concat(CountTrailingZerosCases<i32>(),  //
                                               CountTrailingZerosCases<u32>()))));
 
@@ -737,7 +737,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     CountOneBits,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kCountOneBits),
+    testing::Combine(testing::Values(core::BuiltinFn::kCountOneBits),
                      testing::ValuesIn(Concat(CountOneBitsCases<i32>(),  //
                                               CountOneBitsCases<u32>()))));
 
@@ -836,7 +836,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Cross,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kCross),
+    testing::Combine(testing::Values(core::BuiltinFn::kCross),
                      testing::ValuesIn(Concat(CrossCases<AFloat>(),  //
                                               CrossCases<f32>(),     //
                                               CrossCases<f16>()))));
@@ -863,7 +863,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Distance,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kDistance),
+    testing::Combine(testing::Values(core::BuiltinFn::kDistance),
                      testing::ValuesIn(Concat(DistanceCases<AFloat>(),  //
                                               DistanceCases<f32>(),     //
                                               DistanceCases<f16>()))));
@@ -911,7 +911,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Dot,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kDot),
+    testing::Combine(testing::Values(core::BuiltinFn::kDot),
                      testing::ValuesIn(Concat(DotCases<AInt>(),    //
                                               DotCases<i32>(),     //
                                               DotCases<u32>(),     //
@@ -991,7 +991,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Determinant,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kDeterminant),
+    testing::Combine(testing::Values(core::BuiltinFn::kDeterminant),
                      testing::ValuesIn(Concat(DeterminantCases<AFloat>(),  //
                                               DeterminantCases<f32>(),     //
                                               DeterminantCases<f16>()))));
@@ -1073,7 +1073,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     FaceForward,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kFaceForward),
+    testing::Combine(testing::Values(core::BuiltinFn::kFaceForward),
                      testing::ValuesIn(Concat(FaceForwardCases<AFloat>(),  //
                                               FaceForwardCases<f32>(),     //
                                               FaceForwardCases<f16>()))));
@@ -1136,7 +1136,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     FirstLeadingBit,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kFirstLeadingBit),
+    testing::Combine(testing::Values(core::BuiltinFn::kFirstLeadingBit),
                      testing::ValuesIn(Concat(FirstLeadingBitCases<i32>(),  //
                                               FirstLeadingBitCases<u32>()))));
 
@@ -1169,7 +1169,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     FirstTrailingBit,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kFirstTrailingBit),
+    testing::Combine(testing::Values(core::BuiltinFn::kFirstTrailingBit),
                      testing::ValuesIn(Concat(FirstTrailingBitCases<i32>(),  //
                                               FirstTrailingBitCases<u32>()))));
 
@@ -1189,7 +1189,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Floor,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kFloor),
+    testing::Combine(testing::Values(core::BuiltinFn::kFloor),
                      testing::ValuesIn(Concat(FloorCases<AFloat>(),  //
                                               FloorCases<f32>(),
                                               FloorCases<f16>()))));
@@ -1213,7 +1213,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Fma,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kFma),
+    testing::Combine(testing::Values(core::BuiltinFn::kFma),
                      testing::ValuesIn(Concat(FmaCases<AFloat>(),  //
                                               FmaCases<f32>(),
                                               FmaCases<f16>()))));
@@ -1245,7 +1245,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Fract,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kFract),
+    testing::Combine(testing::Values(core::BuiltinFn::kFract),
                      testing::ValuesIn(Concat(FractCases<AFloat>(),  //
                                               FractCases<f32>(),
                                               FractCases<f16>()))));
@@ -1302,7 +1302,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Frexp,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kFrexp),
+    testing::Combine(testing::Values(core::BuiltinFn::kFrexp),
                      testing::ValuesIn(Concat(FrexpCases<AFloat>(),  //
                                               FrexpCases<f32>(),     //
                                               FrexpCases<f16>()))));
@@ -1384,7 +1384,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     InsertBits,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kInsertBits),
+    testing::Combine(testing::Values(core::BuiltinFn::kInsertBits),
                      testing::ValuesIn(Concat(InsertBitsCases<i32>(),  //
                                               InsertBitsCases<u32>()))));
 
@@ -1404,7 +1404,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     InverseSqrt,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kInverseSqrt),
+    testing::Combine(testing::Values(core::BuiltinFn::kInverseSqrt),
                      testing::ValuesIn(Concat(InverseSqrtCases<AFloat>(),  //
                                               InverseSqrtCases<f32>(),
                                               InverseSqrtCases<f16>()))));
@@ -1423,7 +1423,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     DegreesAFloat,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kDegrees),
+    testing::Combine(testing::Values(core::BuiltinFn::kDegrees),
                      testing::ValuesIn(DegreesAFloatCases<AFloat>())));
 
 template <typename T>
@@ -1440,7 +1440,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     DegreesF32,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kDegrees),
+    testing::Combine(testing::Values(core::BuiltinFn::kDegrees),
                      testing::ValuesIn(DegreesF32Cases<f32>())));
 
 template <typename T>
@@ -1457,7 +1457,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     DegreesF16,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kDegrees),
+    testing::Combine(testing::Values(core::BuiltinFn::kDegrees),
                      testing::ValuesIn(DegreesF16Cases<f16>())));
 
 template <typename T>
@@ -1474,7 +1474,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Exp,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kExp),
+    testing::Combine(testing::Values(core::BuiltinFn::kExp),
                      testing::ValuesIn(Concat(ExpCases<AFloat>(),  //
                                               ExpCases<f32>(),
                                               ExpCases<f16>()))));
@@ -1495,7 +1495,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Exp2,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kExp2),
+    testing::Combine(testing::Values(core::BuiltinFn::kExp2),
                      testing::ValuesIn(Concat(Exp2Cases<AFloat>(),  //
                                               Exp2Cases<f32>(),
                                               Exp2Cases<f16>()))));
@@ -1592,7 +1592,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     ExtractBits,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kExtractBits),
+    testing::Combine(testing::Values(core::BuiltinFn::kExtractBits),
                      testing::ValuesIn(Concat(ExtractBitsCases<i32>(),  //
                                               ExtractBitsCases<u32>()))));
 
@@ -1656,7 +1656,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Ldexp,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kLdexp),
+    testing::Combine(testing::Values(core::BuiltinFn::kLdexp),
                      testing::ValuesIn(Concat(LdexpCases<AFloat>(),  //
                                               LdexpCases<f32>(),
                                               LdexpCases<f16>()))));
@@ -1715,7 +1715,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Length,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kLength),
+    testing::Combine(testing::Values(core::BuiltinFn::kLength),
                      testing::ValuesIn(Concat(LengthCases<AFloat>(),  //
                                               LengthCases<f32>(),
                                               LengthCases<f16>()))));
@@ -1731,7 +1731,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Log,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kLog),
+    testing::Combine(testing::Values(core::BuiltinFn::kLog),
                      testing::ValuesIn(Concat(LogCases<AFloat>(),  //
                                               LogCases<f32>(),
                                               LogCases<f16>()))));
@@ -1744,7 +1744,8 @@
 INSTANTIATE_TEST_SUITE_P(  //
     LogF16,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kLog), testing::ValuesIn(LogF16Cases<f16>())));
+    testing::Combine(testing::Values(core::BuiltinFn::kLog),
+                     testing::ValuesIn(LogF16Cases<f16>())));
 template <typename T>
 std::vector<Case> LogF32Cases() {
     return {
@@ -1754,7 +1755,8 @@
 INSTANTIATE_TEST_SUITE_P(  //
     LogF32,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kLog), testing::ValuesIn(LogF32Cases<f32>())));
+    testing::Combine(testing::Values(core::BuiltinFn::kLog),
+                     testing::ValuesIn(LogF32Cases<f32>())));
 
 template <typename T>
 std::vector<Case> LogAbstractCases() {
@@ -1765,7 +1767,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     LogAbstract,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kLog),
+    testing::Combine(testing::Values(core::BuiltinFn::kLog),
                      testing::ValuesIn(LogAbstractCases<AFloat>())));
 
 template <typename T>
@@ -1783,7 +1785,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Log2,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kLog2),
+    testing::Combine(testing::Values(core::BuiltinFn::kLog2),
                      testing::ValuesIn(Concat(Log2Cases<AFloat>(),  //
                                               Log2Cases<f32>(),
                                               Log2Cases<f16>()))));
@@ -1796,7 +1798,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Log2F16,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kLog2),
+    testing::Combine(testing::Values(core::BuiltinFn::kLog2),
                      testing::ValuesIn(Log2F16Cases<f16>())));
 template <typename T>
 std::vector<Case> Log2F32Cases() {
@@ -1807,7 +1809,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Log2F32,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kLog2),
+    testing::Combine(testing::Values(core::BuiltinFn::kLog2),
                      testing::ValuesIn(Log2F32Cases<f32>())));
 template <typename T>
 std::vector<Case> Log2AbstractCases() {
@@ -1818,7 +1820,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Log2Abstract,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kLog2),
+    testing::Combine(testing::Values(core::BuiltinFn::kLog2),
                      testing::ValuesIn(Log2AbstractCases<AFloat>())));
 
 template <typename T>
@@ -1841,7 +1843,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Max,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kMax),
+    testing::Combine(testing::Values(core::BuiltinFn::kMax),
                      testing::ValuesIn(Concat(MaxCases<AInt>(),  //
                                               MaxCases<i32>(),
                                               MaxCases<u32>(),
@@ -1867,7 +1869,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Min,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kMin),
+    testing::Combine(testing::Values(core::BuiltinFn::kMin),
                      testing::ValuesIn(Concat(MinCases<AInt>(),  //
                                               MinCases<i32>(),
                                               MinCases<u32>(),
@@ -1957,7 +1959,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Mix,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kMix),
+    testing::Combine(testing::Values(core::BuiltinFn::kMix),
                      testing::ValuesIn(Concat(MixCases<AFloat>(),  //
                                               MixCases<f32>(),     //
                                               MixCases<f16>()))));
@@ -1991,7 +1993,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Modf,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kModf),
+    testing::Combine(testing::Values(core::BuiltinFn::kModf),
                      testing::ValuesIn(Concat(ModfCases<AFloat>(),  //
                                               ModfCases<f32>(),     //
                                               ModfCases<f16>()))));
@@ -2021,7 +2023,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Normalize,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kNormalize),
+    testing::Combine(testing::Values(core::BuiltinFn::kNormalize),
                      testing::ValuesIn(Concat(NormalizeCases<AFloat>(),  //
                                               NormalizeCases<f32>(),     //
                                               NormalizeCases<f16>()))));
@@ -2041,7 +2043,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Pack4x8snorm,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kPack4X8Snorm),
+    testing::Combine(testing::Values(core::BuiltinFn::kPack4X8Snorm),
                      testing::ValuesIn(Pack4x8snormCases())));
 
 std::vector<Case> Pack4x8unormCases() {
@@ -2058,7 +2060,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Pack4x8unorm,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kPack4X8Unorm),
+    testing::Combine(testing::Values(core::BuiltinFn::kPack4X8Unorm),
                      testing::ValuesIn(Pack4x8unormCases())));
 
 std::vector<Case> Pack2x16floatCases() {
@@ -2079,7 +2081,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Pack2x16float,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kPack2X16Float),
+    testing::Combine(testing::Values(core::BuiltinFn::kPack2X16Float),
                      testing::ValuesIn(Pack2x16floatCases())));
 
 std::vector<Case> Pack2x16snormCases() {
@@ -2097,7 +2099,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Pack2x16snorm,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kPack2X16Snorm),
+    testing::Combine(testing::Values(core::BuiltinFn::kPack2X16Snorm),
                      testing::ValuesIn(Pack2x16snormCases())));
 
 std::vector<Case> Pack2x16unormCases() {
@@ -2111,7 +2113,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Pack2x16unorm,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kPack2X16Unorm),
+    testing::Combine(testing::Values(core::BuiltinFn::kPack2X16Unorm),
                      testing::ValuesIn(Pack2x16unormCases())));
 
 template <typename T>
@@ -2157,7 +2159,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Pow,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kPow),
+    testing::Combine(testing::Values(core::BuiltinFn::kPow),
                      testing::ValuesIn(Concat(PowCases<AFloat>(),  //
                                               PowCases<f32>(),     //
                                               PowCases<f16>()))));
@@ -2204,7 +2206,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     ReverseBits,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kReverseBits),
+    testing::Combine(testing::Values(core::BuiltinFn::kReverseBits),
                      testing::ValuesIn(Concat(ReverseBitsCases<i32>(),  //
                                               ReverseBitsCases<u32>()))));
 
@@ -2256,7 +2258,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Reflect,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kReflect),
+    testing::Combine(testing::Values(core::BuiltinFn::kReflect),
                      testing::ValuesIn(Concat(ReflectCases<AFloat>(),  //
                                               ReflectCases<f32>(),     //
                                               ReflectCases<f16>()))));
@@ -2346,7 +2348,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Refract,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kRefract),
+    testing::Combine(testing::Values(core::BuiltinFn::kRefract),
                      testing::ValuesIn(Concat(RefractCases<AFloat>(),  //
                                               RefractCases<f32>(),     //
                                               RefractCases<f16>()))));
@@ -2365,7 +2367,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Radians,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kRadians),
+    testing::Combine(testing::Values(core::BuiltinFn::kRadians),
                      testing::ValuesIn(Concat(RadiansCases<AFloat>(),  //
                                               RadiansCases<f32>()))));
 
@@ -2383,7 +2385,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     RadiansF16,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kRadians),
+    testing::Combine(testing::Values(core::BuiltinFn::kRadians),
                      testing::ValuesIn(RadiansF16Cases<f16>())));
 
 template <typename T>
@@ -2409,7 +2411,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Round,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kRound),
+    testing::Combine(testing::Values(core::BuiltinFn::kRound),
                      testing::ValuesIn(Concat(RoundCases<AFloat>(),  //
                                               RoundCases<f32>(),
                                               RoundCases<f16>()))));
@@ -2434,7 +2436,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Saturate,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kSaturate),
+    testing::Combine(testing::Values(core::BuiltinFn::kSaturate),
                      testing::ValuesIn(Concat(SaturateCases<AFloat>(),  //
                                               SaturateCases<f32>(),
                                               SaturateCases<f16>()))));
@@ -2476,7 +2478,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Select,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kSelect),
+    testing::Combine(testing::Values(core::BuiltinFn::kSelect),
                      testing::ValuesIn(Concat(SelectCases<AInt>(),  //
                                               SelectCases<i32>(),
                                               SelectCases<u32>(),
@@ -2517,7 +2519,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Sign,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kSign),
+    testing::Combine(testing::Values(core::BuiltinFn::kSign),
                      testing::ValuesIn(Concat(SignCases<AInt>(),  //
                                               SignCases<i32>(),
                                               SignCases<AFloat>(),
@@ -2539,7 +2541,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Sin,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kSin),
+    testing::Combine(testing::Values(core::BuiltinFn::kSin),
                      testing::ValuesIn(Concat(SinCases<AFloat>(),  //
                                               SinCases<f32>(),
                                               SinCases<f16>()))));
@@ -2564,7 +2566,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Sinh,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kSinh),
+    testing::Combine(testing::Values(core::BuiltinFn::kSinh),
                      testing::ValuesIn(Concat(SinhCases<AFloat>(),  //
                                               SinhCases<f32>(),
                                               SinhCases<f16>()))));
@@ -2597,7 +2599,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Smoothstep,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kSmoothstep),
+    testing::Combine(testing::Values(core::BuiltinFn::kSmoothstep),
                      testing::ValuesIn(Concat(SmoothstepCases<AFloat>(),  //
                                               SmoothstepCases<f32>(),
                                               SmoothstepCases<f16>()))));
@@ -2629,7 +2631,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Step,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kStep),
+    testing::Combine(testing::Values(core::BuiltinFn::kStep),
                      testing::ValuesIn(Concat(StepCases<AFloat>(),  //
                                               StepCases<f32>(),
                                               StepCases<f16>()))));
@@ -2650,7 +2652,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Sqrt,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kSqrt),
+    testing::Combine(testing::Values(core::BuiltinFn::kSqrt),
                      testing::ValuesIn(Concat(SqrtCases<AFloat>(),  //
                                               SqrtCases<f32>(),
                                               SqrtCases<f16>()))));
@@ -2669,7 +2671,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Tan,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kTan),
+    testing::Combine(testing::Values(core::BuiltinFn::kTan),
                      testing::ValuesIn(Concat(TanCases<AFloat>(),  //
                                               TanCases<f32>(),
                                               TanCases<f16>()))));
@@ -2689,7 +2691,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Tanh,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kTanh),
+    testing::Combine(testing::Values(core::BuiltinFn::kTanh),
                      testing::ValuesIn(Concat(TanhCases<AFloat>(),  //
                                               TanhCases<f32>(),
                                               TanhCases<f16>()))));
@@ -2755,7 +2757,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Transpose,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kTranspose),
+    testing::Combine(testing::Values(core::BuiltinFn::kTranspose),
                      testing::ValuesIn(Concat(TransposeCases<AFloat>(),  //
                                               TransposeCases<f32>(),
                                               TransposeCases<f16>()))));
@@ -2773,7 +2775,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Trunc,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kTrunc),
+    testing::Combine(testing::Values(core::BuiltinFn::kTrunc),
                      testing::ValuesIn(Concat(TruncCases<AFloat>(),  //
                                               TruncCases<f32>(),
                                               TruncCases<f16>()))));
@@ -2794,7 +2796,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Unpack4x8snorm,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kUnpack4X8Snorm),
+    testing::Combine(testing::Values(core::BuiltinFn::kUnpack4X8Snorm),
                      testing::ValuesIn(Unpack4x8snormCases())));
 
 std::vector<Case> Unpack4x8unormCases() {
@@ -2811,7 +2813,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Unpack4x8unorm,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kUnpack4X8Unorm),
+    testing::Combine(testing::Values(core::BuiltinFn::kUnpack4X8Unorm),
                      testing::ValuesIn(Unpack4x8unormCases())));
 
 std::vector<Case> Unpack2x16floatCases() {
@@ -2825,7 +2827,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Unpack2x16float,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kUnpack2X16Float),
+    testing::Combine(testing::Values(core::BuiltinFn::kUnpack2X16Float),
                      testing::ValuesIn(Unpack2x16floatCases())));
 
 std::vector<Case> Unpack2x16snormCases() {
@@ -2843,7 +2845,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Unpack2x16snorm,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kUnpack2X16Snorm),
+    testing::Combine(testing::Values(core::BuiltinFn::kUnpack2X16Snorm),
                      testing::ValuesIn(Unpack2x16snormCases())));
 
 std::vector<Case> Unpack2x16unormCases() {
@@ -2857,7 +2859,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Unpack2x16unorm,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kUnpack2X16Unorm),
+    testing::Combine(testing::Values(core::BuiltinFn::kUnpack2X16Unorm),
                      testing::ValuesIn(Unpack2x16unormCases())));
 
 std::vector<Case> QuantizeToF16Cases() {
@@ -2916,7 +2918,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     QuantizeToF16,
     ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kQuantizeToF16),
+    testing::Combine(testing::Values(core::BuiltinFn::kQuantizeToF16),
                      testing::ValuesIn(QuantizeToF16Cases())));
 
 }  // namespace
diff --git a/src/tint/lang/core/constant/eval_conversion_test.cc b/src/tint/lang/core/constant/eval_conversion_test.cc
index 2ff6ce6..200666e 100644
--- a/src/tint/lang/core/constant/eval_conversion_test.cc
+++ b/src/tint/lang/core/constant/eval_conversion_test.cc
@@ -453,7 +453,7 @@
     //   const c = modf(4.0);
     //   var v = c;
     // }
-    auto* expr_c = Call(core::Function::kModf, 0_a);
+    auto* expr_c = Call(core::BuiltinFn::kModf, 0_a);
     auto* materialized = Expr("c");
     WrapInFunction(Decl(Const("c", expr_c)), Decl(Var("v", materialized)));
 
diff --git a/src/tint/lang/core/core.def b/src/tint/lang/core/core.def
index 77b445a..3ca17b9 100644
--- a/src/tint/lang/core/core.def
+++ b/src/tint/lang/core/core.def
@@ -13,7 +13,7 @@
 // limitations under the License.
 
 ////////////////////////////////////////////////////////////////////////////////
-// WGSL builtin definition file                                               //
+// core builtin definition file                                               //
 //                                                                            //
 // This file is used to generate parts of the Tint BuiltinTable, various      //
 // enum definition files, as well as test .wgsl files.                        //
@@ -23,6 +23,10 @@
 // from the Dawn source directory.                                            //
 ////////////////////////////////////////////////////////////////////////////////
 
+import "src/tint/lang/core/address_space.def"
+import "src/tint/lang/core/access.def"
+import "src/tint/lang/core/texel_format.def"
+
 ////////////////////////////////////////////////////////////////////////////////
 // Enumerators                                                                //
 ////////////////////////////////////////////////////////////////////////////////
@@ -46,48 +50,6 @@
   __point_size
 }
 
-// https://gpuweb.github.io/gpuweb/wgsl/#storage-class
-enum address_space {
-  function
-  private
-  workgroup
-  uniform
-  storage
-  push_constant
-  pixel_local
-  __in
-  __out
-  @internal handle
-}
-
-// https://gpuweb.github.io/gpuweb/wgsl/#memory-access-mode
-enum access {
-  read
-  write
-  read_write
-}
-
-// https://gpuweb.github.io/gpuweb/wgsl/#texel-formats
-enum texel_format {
-  bgra8unorm
-  rgba8unorm
-  rgba8snorm
-  rgba8uint
-  rgba8sint
-  rgba16uint
-  rgba16sint
-  rgba16float
-  r32uint
-  r32sint
-  r32float
-  rg32uint
-  rg32sint
-  rg32float
-  rgba32uint
-  rgba32sint
-  rgba32float
-}
-
 // https://www.w3.org/TR/WGSL/#interpolation
 enum interpolation_type {
   perspective
diff --git a/src/tint/lang/core/function.cc b/src/tint/lang/core/function.cc
deleted file mode 100644
index afa1150..0000000
--- a/src/tint/lang/core/function.cc
+++ /dev/null
@@ -1,711 +0,0 @@
-// Copyright 2023 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-////////////////////////////////////////////////////////////////////////////////
-// File generated by 'tools/src/cmd/gen' using the template:
-//   src/tint/lang/core/function.cc.tmpl
-//
-// To regenerate run: './tools/run gen'
-//
-//                       Do not modify this file directly
-////////////////////////////////////////////////////////////////////////////////
-
-#include "src/tint/lang/core/function.h"
-
-namespace tint::core {
-
-Function ParseFunction(std::string_view name) {
-    if (name == "abs") {
-        return Function::kAbs;
-    }
-    if (name == "acos") {
-        return Function::kAcos;
-    }
-    if (name == "acosh") {
-        return Function::kAcosh;
-    }
-    if (name == "all") {
-        return Function::kAll;
-    }
-    if (name == "any") {
-        return Function::kAny;
-    }
-    if (name == "arrayLength") {
-        return Function::kArrayLength;
-    }
-    if (name == "asin") {
-        return Function::kAsin;
-    }
-    if (name == "asinh") {
-        return Function::kAsinh;
-    }
-    if (name == "atan") {
-        return Function::kAtan;
-    }
-    if (name == "atan2") {
-        return Function::kAtan2;
-    }
-    if (name == "atanh") {
-        return Function::kAtanh;
-    }
-    if (name == "ceil") {
-        return Function::kCeil;
-    }
-    if (name == "clamp") {
-        return Function::kClamp;
-    }
-    if (name == "cos") {
-        return Function::kCos;
-    }
-    if (name == "cosh") {
-        return Function::kCosh;
-    }
-    if (name == "countLeadingZeros") {
-        return Function::kCountLeadingZeros;
-    }
-    if (name == "countOneBits") {
-        return Function::kCountOneBits;
-    }
-    if (name == "countTrailingZeros") {
-        return Function::kCountTrailingZeros;
-    }
-    if (name == "cross") {
-        return Function::kCross;
-    }
-    if (name == "degrees") {
-        return Function::kDegrees;
-    }
-    if (name == "determinant") {
-        return Function::kDeterminant;
-    }
-    if (name == "distance") {
-        return Function::kDistance;
-    }
-    if (name == "dot") {
-        return Function::kDot;
-    }
-    if (name == "dot4I8Packed") {
-        return Function::kDot4I8Packed;
-    }
-    if (name == "dot4U8Packed") {
-        return Function::kDot4U8Packed;
-    }
-    if (name == "dpdx") {
-        return Function::kDpdx;
-    }
-    if (name == "dpdxCoarse") {
-        return Function::kDpdxCoarse;
-    }
-    if (name == "dpdxFine") {
-        return Function::kDpdxFine;
-    }
-    if (name == "dpdy") {
-        return Function::kDpdy;
-    }
-    if (name == "dpdyCoarse") {
-        return Function::kDpdyCoarse;
-    }
-    if (name == "dpdyFine") {
-        return Function::kDpdyFine;
-    }
-    if (name == "exp") {
-        return Function::kExp;
-    }
-    if (name == "exp2") {
-        return Function::kExp2;
-    }
-    if (name == "extractBits") {
-        return Function::kExtractBits;
-    }
-    if (name == "faceForward") {
-        return Function::kFaceForward;
-    }
-    if (name == "firstLeadingBit") {
-        return Function::kFirstLeadingBit;
-    }
-    if (name == "firstTrailingBit") {
-        return Function::kFirstTrailingBit;
-    }
-    if (name == "floor") {
-        return Function::kFloor;
-    }
-    if (name == "fma") {
-        return Function::kFma;
-    }
-    if (name == "fract") {
-        return Function::kFract;
-    }
-    if (name == "frexp") {
-        return Function::kFrexp;
-    }
-    if (name == "fwidth") {
-        return Function::kFwidth;
-    }
-    if (name == "fwidthCoarse") {
-        return Function::kFwidthCoarse;
-    }
-    if (name == "fwidthFine") {
-        return Function::kFwidthFine;
-    }
-    if (name == "insertBits") {
-        return Function::kInsertBits;
-    }
-    if (name == "inverseSqrt") {
-        return Function::kInverseSqrt;
-    }
-    if (name == "ldexp") {
-        return Function::kLdexp;
-    }
-    if (name == "length") {
-        return Function::kLength;
-    }
-    if (name == "log") {
-        return Function::kLog;
-    }
-    if (name == "log2") {
-        return Function::kLog2;
-    }
-    if (name == "max") {
-        return Function::kMax;
-    }
-    if (name == "min") {
-        return Function::kMin;
-    }
-    if (name == "mix") {
-        return Function::kMix;
-    }
-    if (name == "modf") {
-        return Function::kModf;
-    }
-    if (name == "normalize") {
-        return Function::kNormalize;
-    }
-    if (name == "pack2x16float") {
-        return Function::kPack2X16Float;
-    }
-    if (name == "pack2x16snorm") {
-        return Function::kPack2X16Snorm;
-    }
-    if (name == "pack2x16unorm") {
-        return Function::kPack2X16Unorm;
-    }
-    if (name == "pack4x8snorm") {
-        return Function::kPack4X8Snorm;
-    }
-    if (name == "pack4x8unorm") {
-        return Function::kPack4X8Unorm;
-    }
-    if (name == "pow") {
-        return Function::kPow;
-    }
-    if (name == "quantizeToF16") {
-        return Function::kQuantizeToF16;
-    }
-    if (name == "radians") {
-        return Function::kRadians;
-    }
-    if (name == "reflect") {
-        return Function::kReflect;
-    }
-    if (name == "refract") {
-        return Function::kRefract;
-    }
-    if (name == "reverseBits") {
-        return Function::kReverseBits;
-    }
-    if (name == "round") {
-        return Function::kRound;
-    }
-    if (name == "saturate") {
-        return Function::kSaturate;
-    }
-    if (name == "select") {
-        return Function::kSelect;
-    }
-    if (name == "sign") {
-        return Function::kSign;
-    }
-    if (name == "sin") {
-        return Function::kSin;
-    }
-    if (name == "sinh") {
-        return Function::kSinh;
-    }
-    if (name == "smoothstep") {
-        return Function::kSmoothstep;
-    }
-    if (name == "sqrt") {
-        return Function::kSqrt;
-    }
-    if (name == "step") {
-        return Function::kStep;
-    }
-    if (name == "storageBarrier") {
-        return Function::kStorageBarrier;
-    }
-    if (name == "tan") {
-        return Function::kTan;
-    }
-    if (name == "tanh") {
-        return Function::kTanh;
-    }
-    if (name == "transpose") {
-        return Function::kTranspose;
-    }
-    if (name == "trunc") {
-        return Function::kTrunc;
-    }
-    if (name == "unpack2x16float") {
-        return Function::kUnpack2X16Float;
-    }
-    if (name == "unpack2x16snorm") {
-        return Function::kUnpack2X16Snorm;
-    }
-    if (name == "unpack2x16unorm") {
-        return Function::kUnpack2X16Unorm;
-    }
-    if (name == "unpack4x8snorm") {
-        return Function::kUnpack4X8Snorm;
-    }
-    if (name == "unpack4x8unorm") {
-        return Function::kUnpack4X8Unorm;
-    }
-    if (name == "workgroupBarrier") {
-        return Function::kWorkgroupBarrier;
-    }
-    if (name == "workgroupUniformLoad") {
-        return Function::kWorkgroupUniformLoad;
-    }
-    if (name == "textureBarrier") {
-        return Function::kTextureBarrier;
-    }
-    if (name == "textureDimensions") {
-        return Function::kTextureDimensions;
-    }
-    if (name == "textureGather") {
-        return Function::kTextureGather;
-    }
-    if (name == "textureGatherCompare") {
-        return Function::kTextureGatherCompare;
-    }
-    if (name == "textureNumLayers") {
-        return Function::kTextureNumLayers;
-    }
-    if (name == "textureNumLevels") {
-        return Function::kTextureNumLevels;
-    }
-    if (name == "textureNumSamples") {
-        return Function::kTextureNumSamples;
-    }
-    if (name == "textureSample") {
-        return Function::kTextureSample;
-    }
-    if (name == "textureSampleBias") {
-        return Function::kTextureSampleBias;
-    }
-    if (name == "textureSampleCompare") {
-        return Function::kTextureSampleCompare;
-    }
-    if (name == "textureSampleCompareLevel") {
-        return Function::kTextureSampleCompareLevel;
-    }
-    if (name == "textureSampleGrad") {
-        return Function::kTextureSampleGrad;
-    }
-    if (name == "textureSampleLevel") {
-        return Function::kTextureSampleLevel;
-    }
-    if (name == "textureSampleBaseClampToEdge") {
-        return Function::kTextureSampleBaseClampToEdge;
-    }
-    if (name == "textureStore") {
-        return Function::kTextureStore;
-    }
-    if (name == "textureLoad") {
-        return Function::kTextureLoad;
-    }
-    if (name == "atomicLoad") {
-        return Function::kAtomicLoad;
-    }
-    if (name == "atomicStore") {
-        return Function::kAtomicStore;
-    }
-    if (name == "atomicAdd") {
-        return Function::kAtomicAdd;
-    }
-    if (name == "atomicSub") {
-        return Function::kAtomicSub;
-    }
-    if (name == "atomicMax") {
-        return Function::kAtomicMax;
-    }
-    if (name == "atomicMin") {
-        return Function::kAtomicMin;
-    }
-    if (name == "atomicAnd") {
-        return Function::kAtomicAnd;
-    }
-    if (name == "atomicOr") {
-        return Function::kAtomicOr;
-    }
-    if (name == "atomicXor") {
-        return Function::kAtomicXor;
-    }
-    if (name == "atomicExchange") {
-        return Function::kAtomicExchange;
-    }
-    if (name == "atomicCompareExchangeWeak") {
-        return Function::kAtomicCompareExchangeWeak;
-    }
-    if (name == "subgroupBallot") {
-        return Function::kSubgroupBallot;
-    }
-    if (name == "subgroupBroadcast") {
-        return Function::kSubgroupBroadcast;
-    }
-    if (name == "_tint_materialize") {
-        return Function::kTintMaterialize;
-    }
-    return Function::kNone;
-}
-
-const char* str(Function i) {
-    switch (i) {
-        case Function::kNone:
-            return "<none>";
-        case Function::kAbs:
-            return "abs";
-        case Function::kAcos:
-            return "acos";
-        case Function::kAcosh:
-            return "acosh";
-        case Function::kAll:
-            return "all";
-        case Function::kAny:
-            return "any";
-        case Function::kArrayLength:
-            return "arrayLength";
-        case Function::kAsin:
-            return "asin";
-        case Function::kAsinh:
-            return "asinh";
-        case Function::kAtan:
-            return "atan";
-        case Function::kAtan2:
-            return "atan2";
-        case Function::kAtanh:
-            return "atanh";
-        case Function::kCeil:
-            return "ceil";
-        case Function::kClamp:
-            return "clamp";
-        case Function::kCos:
-            return "cos";
-        case Function::kCosh:
-            return "cosh";
-        case Function::kCountLeadingZeros:
-            return "countLeadingZeros";
-        case Function::kCountOneBits:
-            return "countOneBits";
-        case Function::kCountTrailingZeros:
-            return "countTrailingZeros";
-        case Function::kCross:
-            return "cross";
-        case Function::kDegrees:
-            return "degrees";
-        case Function::kDeterminant:
-            return "determinant";
-        case Function::kDistance:
-            return "distance";
-        case Function::kDot:
-            return "dot";
-        case Function::kDot4I8Packed:
-            return "dot4I8Packed";
-        case Function::kDot4U8Packed:
-            return "dot4U8Packed";
-        case Function::kDpdx:
-            return "dpdx";
-        case Function::kDpdxCoarse:
-            return "dpdxCoarse";
-        case Function::kDpdxFine:
-            return "dpdxFine";
-        case Function::kDpdy:
-            return "dpdy";
-        case Function::kDpdyCoarse:
-            return "dpdyCoarse";
-        case Function::kDpdyFine:
-            return "dpdyFine";
-        case Function::kExp:
-            return "exp";
-        case Function::kExp2:
-            return "exp2";
-        case Function::kExtractBits:
-            return "extractBits";
-        case Function::kFaceForward:
-            return "faceForward";
-        case Function::kFirstLeadingBit:
-            return "firstLeadingBit";
-        case Function::kFirstTrailingBit:
-            return "firstTrailingBit";
-        case Function::kFloor:
-            return "floor";
-        case Function::kFma:
-            return "fma";
-        case Function::kFract:
-            return "fract";
-        case Function::kFrexp:
-            return "frexp";
-        case Function::kFwidth:
-            return "fwidth";
-        case Function::kFwidthCoarse:
-            return "fwidthCoarse";
-        case Function::kFwidthFine:
-            return "fwidthFine";
-        case Function::kInsertBits:
-            return "insertBits";
-        case Function::kInverseSqrt:
-            return "inverseSqrt";
-        case Function::kLdexp:
-            return "ldexp";
-        case Function::kLength:
-            return "length";
-        case Function::kLog:
-            return "log";
-        case Function::kLog2:
-            return "log2";
-        case Function::kMax:
-            return "max";
-        case Function::kMin:
-            return "min";
-        case Function::kMix:
-            return "mix";
-        case Function::kModf:
-            return "modf";
-        case Function::kNormalize:
-            return "normalize";
-        case Function::kPack2X16Float:
-            return "pack2x16float";
-        case Function::kPack2X16Snorm:
-            return "pack2x16snorm";
-        case Function::kPack2X16Unorm:
-            return "pack2x16unorm";
-        case Function::kPack4X8Snorm:
-            return "pack4x8snorm";
-        case Function::kPack4X8Unorm:
-            return "pack4x8unorm";
-        case Function::kPow:
-            return "pow";
-        case Function::kQuantizeToF16:
-            return "quantizeToF16";
-        case Function::kRadians:
-            return "radians";
-        case Function::kReflect:
-            return "reflect";
-        case Function::kRefract:
-            return "refract";
-        case Function::kReverseBits:
-            return "reverseBits";
-        case Function::kRound:
-            return "round";
-        case Function::kSaturate:
-            return "saturate";
-        case Function::kSelect:
-            return "select";
-        case Function::kSign:
-            return "sign";
-        case Function::kSin:
-            return "sin";
-        case Function::kSinh:
-            return "sinh";
-        case Function::kSmoothstep:
-            return "smoothstep";
-        case Function::kSqrt:
-            return "sqrt";
-        case Function::kStep:
-            return "step";
-        case Function::kStorageBarrier:
-            return "storageBarrier";
-        case Function::kTan:
-            return "tan";
-        case Function::kTanh:
-            return "tanh";
-        case Function::kTranspose:
-            return "transpose";
-        case Function::kTrunc:
-            return "trunc";
-        case Function::kUnpack2X16Float:
-            return "unpack2x16float";
-        case Function::kUnpack2X16Snorm:
-            return "unpack2x16snorm";
-        case Function::kUnpack2X16Unorm:
-            return "unpack2x16unorm";
-        case Function::kUnpack4X8Snorm:
-            return "unpack4x8snorm";
-        case Function::kUnpack4X8Unorm:
-            return "unpack4x8unorm";
-        case Function::kWorkgroupBarrier:
-            return "workgroupBarrier";
-        case Function::kWorkgroupUniformLoad:
-            return "workgroupUniformLoad";
-        case Function::kTextureBarrier:
-            return "textureBarrier";
-        case Function::kTextureDimensions:
-            return "textureDimensions";
-        case Function::kTextureGather:
-            return "textureGather";
-        case Function::kTextureGatherCompare:
-            return "textureGatherCompare";
-        case Function::kTextureNumLayers:
-            return "textureNumLayers";
-        case Function::kTextureNumLevels:
-            return "textureNumLevels";
-        case Function::kTextureNumSamples:
-            return "textureNumSamples";
-        case Function::kTextureSample:
-            return "textureSample";
-        case Function::kTextureSampleBias:
-            return "textureSampleBias";
-        case Function::kTextureSampleCompare:
-            return "textureSampleCompare";
-        case Function::kTextureSampleCompareLevel:
-            return "textureSampleCompareLevel";
-        case Function::kTextureSampleGrad:
-            return "textureSampleGrad";
-        case Function::kTextureSampleLevel:
-            return "textureSampleLevel";
-        case Function::kTextureSampleBaseClampToEdge:
-            return "textureSampleBaseClampToEdge";
-        case Function::kTextureStore:
-            return "textureStore";
-        case Function::kTextureLoad:
-            return "textureLoad";
-        case Function::kAtomicLoad:
-            return "atomicLoad";
-        case Function::kAtomicStore:
-            return "atomicStore";
-        case Function::kAtomicAdd:
-            return "atomicAdd";
-        case Function::kAtomicSub:
-            return "atomicSub";
-        case Function::kAtomicMax:
-            return "atomicMax";
-        case Function::kAtomicMin:
-            return "atomicMin";
-        case Function::kAtomicAnd:
-            return "atomicAnd";
-        case Function::kAtomicOr:
-            return "atomicOr";
-        case Function::kAtomicXor:
-            return "atomicXor";
-        case Function::kAtomicExchange:
-            return "atomicExchange";
-        case Function::kAtomicCompareExchangeWeak:
-            return "atomicCompareExchangeWeak";
-        case Function::kSubgroupBallot:
-            return "subgroupBallot";
-        case Function::kSubgroupBroadcast:
-            return "subgroupBroadcast";
-        case Function::kTintMaterialize:
-            return "_tint_materialize";
-    }
-    return "<unknown>";
-}
-
-bool IsCoarseDerivativeBuiltin(Function f) {
-    return f == Function::kDpdxCoarse || f == Function::kDpdyCoarse || f == Function::kFwidthCoarse;
-}
-
-bool IsFineDerivativeBuiltin(Function f) {
-    return f == Function::kDpdxFine || f == Function::kDpdyFine || f == Function::kFwidthFine;
-}
-
-bool IsDerivativeBuiltin(Function f) {
-    return f == Function::kDpdx || f == Function::kDpdy || f == Function::kFwidth ||
-           IsCoarseDerivativeBuiltin(f) || IsFineDerivativeBuiltin(f);
-}
-
-bool IsTextureBuiltin(Function f) {
-    return IsImageQueryBuiltin(f) ||                        //
-           f == Function::kTextureGather ||                 //
-           f == Function::kTextureGatherCompare ||          //
-           f == Function::kTextureLoad ||                   //
-           f == Function::kTextureSample ||                 //
-           f == Function::kTextureSampleBaseClampToEdge ||  //
-           f == Function::kTextureSampleBias ||             //
-           f == Function::kTextureSampleCompare ||          //
-           f == Function::kTextureSampleCompareLevel ||     //
-           f == Function::kTextureSampleGrad ||             //
-           f == Function::kTextureSampleLevel ||            //
-           f == Function::kTextureStore;
-}
-
-bool IsImageQueryBuiltin(Function f) {
-    return f == Function::kTextureDimensions || f == Function::kTextureNumLayers ||
-           f == Function::kTextureNumLevels || f == Function::kTextureNumSamples;
-}
-
-bool IsDataPackingBuiltin(Function f) {
-    return f == Function::kPack4X8Snorm || f == Function::kPack4X8Unorm ||
-           f == Function::kPack2X16Snorm || f == Function::kPack2X16Unorm ||
-           f == Function::kPack2X16Float;
-}
-
-bool IsDataUnpackingBuiltin(Function f) {
-    return f == Function::kUnpack4X8Snorm || f == Function::kUnpack4X8Unorm ||
-           f == Function::kUnpack2X16Snorm || f == Function::kUnpack2X16Unorm ||
-           f == Function::kUnpack2X16Float;
-}
-
-bool IsBarrierBuiltin(Function f) {
-    return f == Function::kWorkgroupBarrier || f == Function::kStorageBarrier ||
-           f == Function::kTextureBarrier;
-}
-
-bool IsAtomicBuiltin(Function f) {
-    return f == Function::kAtomicLoad || f == Function::kAtomicStore || f == Function::kAtomicAdd ||
-           f == Function::kAtomicSub || f == Function::kAtomicMax || f == Function::kAtomicMin ||
-           f == Function::kAtomicAnd || f == Function::kAtomicOr || f == Function::kAtomicXor ||
-           f == Function::kAtomicExchange || f == Function::kAtomicCompareExchangeWeak;
-}
-
-bool IsDP4aBuiltin(Function f) {
-    return f == Function::kDot4I8Packed || f == Function::kDot4U8Packed;
-}
-
-bool IsSubgroupBuiltin(Function f) {
-    return f == Function::kSubgroupBallot || f == Function::kSubgroupBroadcast;
-}
-
-bool HasSideEffects(Function f) {
-    switch (f) {
-        case Function::kAtomicAdd:
-        case Function::kAtomicAnd:
-        case Function::kAtomicCompareExchangeWeak:
-        case Function::kAtomicExchange:
-        case Function::kAtomicMax:
-        case Function::kAtomicMin:
-        case Function::kAtomicOr:
-        case Function::kAtomicStore:
-        case Function::kAtomicSub:
-        case Function::kAtomicXor:
-        case Function::kTextureStore:
-        case Function::kWorkgroupUniformLoad:
-            return true;
-        default:
-            break;
-    }
-    return false;
-}
-
-}  // namespace tint::core
diff --git a/src/tint/lang/core/function.cc.tmpl b/src/tint/lang/core/function.cc.tmpl
deleted file mode 100644
index e594eb6..0000000
--- a/src/tint/lang/core/function.cc.tmpl
+++ /dev/null
@@ -1,132 +0,0 @@
-{{- /*
---------------------------------------------------------------------------------
-Template file for use with tools/src/cmd/gen to generate function.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/core/core.def" -}}
-#include "src/tint/lang/core/function.h"
-
-namespace tint::core {
-
-Function ParseFunction(std::string_view name) {
-{{- range $I.Sem.Builtins  }}
-    if (name == "{{.Name}}") {
-        return Function::k{{PascalCase .Name}};
-    }
-{{- end  }}
-    return Function::kNone;
-}
-
-const char* str(Function i) {
-    switch (i) {
-        case Function::kNone:
-            return "<none>";
-{{- range $I.Sem.Builtins  }}
-        case Function::k{{PascalCase .Name}}:
-            return "{{.Name}}";
-{{- end  }}
-    }
-    return "<unknown>";
-}
-
-bool IsCoarseDerivativeBuiltin(Function f) {
-    return f == Function::kDpdxCoarse || f == Function::kDpdyCoarse ||
-           f == Function::kFwidthCoarse;
-}
-
-bool IsFineDerivativeBuiltin(Function f) {
-    return f == Function::kDpdxFine || f == Function::kDpdyFine ||
-           f == Function::kFwidthFine;
-}
-
-bool IsDerivativeBuiltin(Function f) {
-    return f == Function::kDpdx || f == Function::kDpdy ||
-           f == Function::kFwidth || IsCoarseDerivativeBuiltin(f) ||
-           IsFineDerivativeBuiltin(f);
-}
-
-bool IsTextureBuiltin(Function f) {
-    return IsImageQueryBuiltin(f) ||                                 //
-           f == Function::kTextureGather ||                 //
-           f == Function::kTextureGatherCompare ||          //
-           f == Function::kTextureLoad ||                   //
-           f == Function::kTextureSample ||                 //
-           f == Function::kTextureSampleBaseClampToEdge ||  //
-           f == Function::kTextureSampleBias ||             //
-           f == Function::kTextureSampleCompare ||          //
-           f == Function::kTextureSampleCompareLevel ||     //
-           f == Function::kTextureSampleGrad ||             //
-           f == Function::kTextureSampleLevel ||            //
-           f == Function::kTextureStore;
-}
-
-bool IsImageQueryBuiltin(Function f) {
-    return f == Function::kTextureDimensions ||
-           f == Function::kTextureNumLayers || f == Function::kTextureNumLevels ||
-           f == Function::kTextureNumSamples;
-}
-
-bool IsDataPackingBuiltin(Function f) {
-    return f == Function::kPack4X8Snorm || f == Function::kPack4X8Unorm ||
-           f == Function::kPack2X16Snorm || f == Function::kPack2X16Unorm ||
-           f == Function::kPack2X16Float;
-}
-
-bool IsDataUnpackingBuiltin(Function f) {
-    return f == Function::kUnpack4X8Snorm || f == Function::kUnpack4X8Unorm ||
-           f == Function::kUnpack2X16Snorm || f == Function::kUnpack2X16Unorm ||
-           f == Function::kUnpack2X16Float;
-}
-
-bool IsBarrierBuiltin(Function f) {
-    return f == Function::kWorkgroupBarrier || f == Function::kStorageBarrier ||
-           f == Function::kTextureBarrier;
-}
-
-bool IsAtomicBuiltin(Function f) {
-    return f == Function::kAtomicLoad || f == Function::kAtomicStore ||
-           f == Function::kAtomicAdd || f == Function::kAtomicSub ||
-           f == Function::kAtomicMax || f == Function::kAtomicMin ||
-           f == Function::kAtomicAnd || f == Function::kAtomicOr ||
-           f == Function::kAtomicXor || f == Function::kAtomicExchange ||
-           f == Function::kAtomicCompareExchangeWeak;
-}
-
-bool IsDP4aBuiltin(Function f) {
-    return f == Function::kDot4I8Packed || f == Function::kDot4U8Packed;
-}
-
-bool IsSubgroupBuiltin(Function f) {
-    return f == Function::kSubgroupBallot || f == Function::kSubgroupBroadcast;
-}
-
-bool HasSideEffects(Function f) {
-    switch (f) {
-        case Function::kAtomicAdd:
-        case Function::kAtomicAnd:
-        case Function::kAtomicCompareExchangeWeak:
-        case Function::kAtomicExchange:
-        case Function::kAtomicMax:
-        case Function::kAtomicMin:
-        case Function::kAtomicOr:
-        case Function::kAtomicStore:
-        case Function::kAtomicSub:
-        case Function::kAtomicXor:
-        case Function::kTextureStore:
-        case Function::kWorkgroupUniformLoad:
-            return true;
-        default:
-            break;
-    }
-    return false;
-}
-
-}  // namespace tint::core
diff --git a/src/tint/lang/core/intrinsic/table.cc b/src/tint/lang/core/intrinsic/table.cc
index 221f43b..ce4c23e 100644
--- a/src/tint/lang/core/intrinsic/table.cc
+++ b/src/tint/lang/core/intrinsic/table.cc
@@ -584,7 +584,7 @@
 }  // namespace
 
 Result<Overload> Lookup(Context& context,
-                        core::Function builtin_type,
+                        core::BuiltinFn builtin_type,
                         VectorRef<const core::type::Type*> args,
                         EvaluationStage earliest_eval_stage,
                         const Source& source) {
diff --git a/src/tint/lang/core/intrinsic/table.h b/src/tint/lang/core/intrinsic/table.h
index edae840..1f38b3d 100644
--- a/src/tint/lang/core/intrinsic/table.h
+++ b/src/tint/lang/core/intrinsic/table.h
@@ -19,7 +19,7 @@
 #include <string>
 
 #include "src/tint/lang/core/binary_op.h"
-#include "src/tint/lang/core/function.h"
+#include "src/tint/lang/core/builtin_fn.h"
 #include "src/tint/lang/core/intrinsic/ctor_conv.h"
 #include "src/tint/lang/core/intrinsic/table_data.h"
 #include "src/tint/lang/core/parameter_usage.h"
@@ -110,7 +110,7 @@
 /// @param source the source of the builtin call
 /// @return the resolved builtin function overload
 Result<Overload> Lookup(Context& context,
-                        core::Function builtin_type,
+                        core::BuiltinFn builtin_type,
                         VectorRef<const core::type::Type*> args,
                         EvaluationStage earliest_eval_stage,
                         const Source& source);
diff --git a/src/tint/lang/core/intrinsic/table_test.cc b/src/tint/lang/core/intrinsic/table_test.cc
index c0a3387..88d36f3 100644
--- a/src/tint/lang/core/intrinsic/table_test.cc
+++ b/src/tint/lang/core/intrinsic/table_test.cc
@@ -56,8 +56,8 @@
 TEST_F(IntrinsicTableTest, MatchF32) {
     auto* f32 = create<core::type::F32>();
     auto result =
-        Lookup(context, core::Function::kCos, Vector{f32}, EvaluationStage::kConstant, Source{});
-    ASSERT_TRUE(result) << Diagnostics().str();
+        Lookup(context, core::BuiltinFn::kCos, Vector{f32}, EvaluationStage::kConstant, Source{});
+    ASSERT_TRUE(result) << Diagnostics();
     ASSERT_EQ(Diagnostics().str(), "");
     EXPECT_EQ(result->return_type, f32);
     ASSERT_EQ(result->parameters.Length(), 1u);
@@ -67,7 +67,7 @@
 TEST_F(IntrinsicTableTest, MismatchF32) {
     auto* i32 = create<core::type::I32>();
     auto result =
-        Lookup(context, core::Function::kCos, Vector{i32}, EvaluationStage::kConstant, Source{});
+        Lookup(context, core::BuiltinFn::kCos, Vector{i32}, EvaluationStage::kConstant, Source{});
     ASSERT_FALSE(result);
     ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
 }
@@ -76,9 +76,9 @@
     auto* f32 = create<core::type::F32>();
     auto* u32 = create<core::type::U32>();
     auto* vec2_f32 = create<core::type::Vector>(f32, 2u);
-    auto result = Lookup(context, core::Function::kUnpack2X16Float, Vector{u32},
+    auto result = Lookup(context, core::BuiltinFn::kUnpack2X16Float, Vector{u32},
                          EvaluationStage::kConstant, Source{});
-    ASSERT_TRUE(result) << Diagnostics().str();
+    ASSERT_TRUE(result) << Diagnostics();
     ASSERT_EQ(Diagnostics().str(), "");
     EXPECT_EQ(result->return_type, vec2_f32);
     ASSERT_EQ(result->parameters.Length(), 1u);
@@ -87,7 +87,7 @@
 
 TEST_F(IntrinsicTableTest, MismatchU32) {
     auto* f32 = create<core::type::F32>();
-    auto result = Lookup(context, core::Function::kUnpack2X16Float, Vector{f32},
+    auto result = Lookup(context, core::BuiltinFn::kUnpack2X16Float, Vector{f32},
                          EvaluationStage::kConstant, Source{});
     ASSERT_FALSE(result);
     ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
@@ -98,9 +98,9 @@
     auto* i32 = create<core::type::I32>();
     auto* vec4_f32 = create<core::type::Vector>(f32, 4u);
     auto* tex = create<core::type::SampledTexture>(core::type::TextureDimension::k1d, f32);
-    auto result = Lookup(context, core::Function::kTextureLoad, Vector{tex, i32, i32},
+    auto result = Lookup(context, core::BuiltinFn::kTextureLoad, Vector{tex, i32, i32},
                          EvaluationStage::kConstant, Source{});
-    ASSERT_TRUE(result) << Diagnostics().str();
+    ASSERT_TRUE(result) << Diagnostics();
     ASSERT_EQ(Diagnostics().str(), "");
     EXPECT_EQ(result->return_type, vec4_f32);
     ASSERT_EQ(result->parameters.Length(), 3u);
@@ -115,7 +115,7 @@
 TEST_F(IntrinsicTableTest, MismatchI32) {
     auto* f32 = create<core::type::F32>();
     auto* tex = create<core::type::SampledTexture>(core::type::TextureDimension::k1d, f32);
-    auto result = Lookup(context, core::Function::kTextureLoad, Vector{tex, f32},
+    auto result = Lookup(context, core::BuiltinFn::kTextureLoad, Vector{tex, f32},
                          EvaluationStage::kConstant, Source{});
     ASSERT_FALSE(result);
     ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
@@ -123,9 +123,9 @@
 
 TEST_F(IntrinsicTableTest, MatchIU32AsI32) {
     auto* i32 = create<core::type::I32>();
-    auto result = Lookup(context, core::Function::kCountOneBits, Vector{i32},
+    auto result = Lookup(context, core::BuiltinFn::kCountOneBits, Vector{i32},
                          EvaluationStage::kConstant, Source{});
-    ASSERT_TRUE(result) << Diagnostics().str();
+    ASSERT_TRUE(result) << Diagnostics();
     ASSERT_EQ(Diagnostics().str(), "");
     EXPECT_EQ(result->return_type, i32);
     ASSERT_EQ(result->parameters.Length(), 1u);
@@ -134,9 +134,9 @@
 
 TEST_F(IntrinsicTableTest, MatchIU32AsU32) {
     auto* u32 = create<core::type::U32>();
-    auto result = Lookup(context, core::Function::kCountOneBits, Vector{u32},
+    auto result = Lookup(context, core::BuiltinFn::kCountOneBits, Vector{u32},
                          EvaluationStage::kConstant, Source{});
-    ASSERT_TRUE(result) << Diagnostics().str();
+    ASSERT_TRUE(result) << Diagnostics();
     ASSERT_EQ(Diagnostics().str(), "");
     EXPECT_EQ(result->return_type, u32);
     ASSERT_EQ(result->parameters.Length(), 1u);
@@ -145,7 +145,7 @@
 
 TEST_F(IntrinsicTableTest, MismatchIU32) {
     auto* f32 = create<core::type::F32>();
-    auto result = Lookup(context, core::Function::kCountOneBits, Vector{f32},
+    auto result = Lookup(context, core::BuiltinFn::kCountOneBits, Vector{f32},
                          EvaluationStage::kConstant, Source{});
     ASSERT_FALSE(result);
     ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
@@ -153,9 +153,9 @@
 
 TEST_F(IntrinsicTableTest, MatchFIU32AsI32) {
     auto* i32 = create<core::type::I32>();
-    auto result = Lookup(context, core::Function::kClamp, Vector{i32, i32, i32},
+    auto result = Lookup(context, core::BuiltinFn::kClamp, Vector{i32, i32, i32},
                          EvaluationStage::kConstant, Source{});
-    ASSERT_TRUE(result) << Diagnostics().str();
+    ASSERT_TRUE(result) << Diagnostics();
     ASSERT_EQ(Diagnostics().str(), "");
     EXPECT_EQ(result->return_type, i32);
     ASSERT_EQ(result->parameters.Length(), 3u);
@@ -166,9 +166,9 @@
 
 TEST_F(IntrinsicTableTest, MatchFIU32AsU32) {
     auto* u32 = create<core::type::U32>();
-    auto result = Lookup(context, core::Function::kClamp, Vector{u32, u32, u32},
+    auto result = Lookup(context, core::BuiltinFn::kClamp, Vector{u32, u32, u32},
                          EvaluationStage::kConstant, Source{});
-    ASSERT_TRUE(result) << Diagnostics().str();
+    ASSERT_TRUE(result) << Diagnostics();
     ASSERT_EQ(Diagnostics().str(), "");
     EXPECT_EQ(result->return_type, u32);
     ASSERT_EQ(result->parameters.Length(), 3u);
@@ -179,9 +179,9 @@
 
 TEST_F(IntrinsicTableTest, MatchFIU32AsF32) {
     auto* f32 = create<core::type::F32>();
-    auto result = Lookup(context, core::Function::kClamp, Vector{f32, f32, f32},
+    auto result = Lookup(context, core::BuiltinFn::kClamp, Vector{f32, f32, f32},
                          EvaluationStage::kConstant, Source{});
-    ASSERT_TRUE(result) << Diagnostics().str();
+    ASSERT_TRUE(result) << Diagnostics();
     ASSERT_EQ(Diagnostics().str(), "");
     EXPECT_EQ(result->return_type, f32);
     ASSERT_EQ(result->parameters.Length(), 3u);
@@ -192,7 +192,7 @@
 
 TEST_F(IntrinsicTableTest, MismatchFIU32) {
     auto* bool_ = create<core::type::Bool>();
-    auto result = Lookup(context, core::Function::kClamp, Vector{bool_, bool_, bool_},
+    auto result = Lookup(context, core::BuiltinFn::kClamp, Vector{bool_, bool_, bool_},
                          EvaluationStage::kConstant, Source{});
     ASSERT_FALSE(result);
     ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
@@ -201,9 +201,9 @@
 TEST_F(IntrinsicTableTest, MatchBool) {
     auto* f32 = create<core::type::F32>();
     auto* bool_ = create<core::type::Bool>();
-    auto result = Lookup(context, core::Function::kSelect, Vector{f32, f32, bool_},
+    auto result = Lookup(context, core::BuiltinFn::kSelect, Vector{f32, f32, bool_},
                          EvaluationStage::kConstant, Source{});
-    ASSERT_TRUE(result) << Diagnostics().str();
+    ASSERT_TRUE(result) << Diagnostics();
     ASSERT_EQ(Diagnostics().str(), "");
     EXPECT_EQ(result->return_type, f32);
     ASSERT_EQ(result->parameters.Length(), 3u);
@@ -214,7 +214,7 @@
 
 TEST_F(IntrinsicTableTest, MismatchBool) {
     auto* f32 = create<core::type::F32>();
-    auto result = Lookup(context, core::Function::kSelect, Vector{f32, f32, f32},
+    auto result = Lookup(context, core::BuiltinFn::kSelect, Vector{f32, f32, f32},
                          EvaluationStage::kConstant, Source{});
     ASSERT_FALSE(result);
     ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
@@ -225,9 +225,9 @@
     auto* atomicI32 = create<core::type::Atomic>(i32);
     auto* ptr = create<core::type::Pointer>(core::AddressSpace::kWorkgroup, atomicI32,
                                             core::Access::kReadWrite);
-    auto result = Lookup(context, core::Function::kAtomicLoad, Vector{ptr},
+    auto result = Lookup(context, core::BuiltinFn::kAtomicLoad, Vector{ptr},
                          EvaluationStage::kConstant, Source{});
-    ASSERT_TRUE(result) << Diagnostics().str();
+    ASSERT_TRUE(result) << Diagnostics();
     ASSERT_EQ(Diagnostics().str(), "");
     EXPECT_EQ(result->return_type, i32);
     ASSERT_EQ(result->parameters.Length(), 1u);
@@ -237,7 +237,7 @@
 TEST_F(IntrinsicTableTest, MismatchPointer) {
     auto* i32 = create<core::type::I32>();
     auto* atomicI32 = create<core::type::Atomic>(i32);
-    auto result = Lookup(context, core::Function::kAtomicLoad, Vector{atomicI32},
+    auto result = Lookup(context, core::BuiltinFn::kAtomicLoad, Vector{atomicI32},
                          EvaluationStage::kConstant, Source{});
     ASSERT_FALSE(result);
     ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
@@ -248,9 +248,9 @@
                                           create<core::type::RuntimeArrayCount>(), 4u, 4u, 4u, 4u);
     auto* arr_ptr =
         create<core::type::Pointer>(core::AddressSpace::kStorage, arr, core::Access::kReadWrite);
-    auto result = Lookup(context, core::Function::kArrayLength, Vector{arr_ptr},
+    auto result = Lookup(context, core::BuiltinFn::kArrayLength, Vector{arr_ptr},
                          EvaluationStage::kConstant, Source{});
-    ASSERT_TRUE(result) << Diagnostics().str();
+    ASSERT_TRUE(result) << Diagnostics();
     ASSERT_EQ(Diagnostics().str(), "");
     EXPECT_TRUE(result->return_type->Is<core::type::U32>());
     ASSERT_EQ(result->parameters.Length(), 1u);
@@ -261,7 +261,7 @@
 
 TEST_F(IntrinsicTableTest, MismatchArray) {
     auto* f32 = create<core::type::F32>();
-    auto result = Lookup(context, core::Function::kArrayLength, Vector{f32},
+    auto result = Lookup(context, core::BuiltinFn::kArrayLength, Vector{f32},
                          EvaluationStage::kConstant, Source{});
     ASSERT_FALSE(result);
     ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
@@ -273,9 +273,9 @@
     auto* vec4_f32 = create<core::type::Vector>(f32, 4u);
     auto* tex = create<core::type::SampledTexture>(core::type::TextureDimension::k2d, f32);
     auto* sampler = create<core::type::Sampler>(core::type::SamplerKind::kSampler);
-    auto result = Lookup(context, core::Function::kTextureSample, Vector{tex, sampler, vec2_f32},
+    auto result = Lookup(context, core::BuiltinFn::kTextureSample, Vector{tex, sampler, vec2_f32},
                          EvaluationStage::kConstant, Source{});
-    ASSERT_TRUE(result) << Diagnostics().str();
+    ASSERT_TRUE(result) << Diagnostics();
     ASSERT_EQ(Diagnostics().str(), "");
     EXPECT_EQ(result->return_type, vec4_f32);
     ASSERT_EQ(result->parameters.Length(), 3u);
@@ -291,7 +291,7 @@
     auto* f32 = create<core::type::F32>();
     auto* vec2_f32 = create<core::type::Vector>(f32, 2u);
     auto* tex = create<core::type::SampledTexture>(core::type::TextureDimension::k2d, f32);
-    auto result = Lookup(context, core::Function::kTextureSample, Vector{tex, f32, vec2_f32},
+    auto result = Lookup(context, core::BuiltinFn::kTextureSample, Vector{tex, f32, vec2_f32},
                          EvaluationStage::kConstant, Source{});
     ASSERT_FALSE(result);
     ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
@@ -303,9 +303,9 @@
     auto* vec2_i32 = create<core::type::Vector>(i32, 2u);
     auto* vec4_f32 = create<core::type::Vector>(f32, 4u);
     auto* tex = create<core::type::SampledTexture>(core::type::TextureDimension::k2d, f32);
-    auto result = Lookup(context, core::Function::kTextureLoad, Vector{tex, vec2_i32, i32},
+    auto result = Lookup(context, core::BuiltinFn::kTextureLoad, Vector{tex, vec2_i32, i32},
                          EvaluationStage::kConstant, Source{});
-    ASSERT_TRUE(result) << Diagnostics().str();
+    ASSERT_TRUE(result) << Diagnostics();
     ASSERT_EQ(Diagnostics().str(), "");
     EXPECT_EQ(result->return_type, vec4_f32);
     ASSERT_EQ(result->parameters.Length(), 3u);
@@ -323,9 +323,9 @@
     auto* vec2_i32 = create<core::type::Vector>(i32, 2u);
     auto* vec4_f32 = create<core::type::Vector>(f32, 4u);
     auto* tex = create<core::type::MultisampledTexture>(core::type::TextureDimension::k2d, f32);
-    auto result = Lookup(context, core::Function::kTextureLoad, Vector{tex, vec2_i32, i32},
+    auto result = Lookup(context, core::BuiltinFn::kTextureLoad, Vector{tex, vec2_i32, i32},
                          EvaluationStage::kConstant, Source{});
-    ASSERT_TRUE(result) << Diagnostics().str();
+    ASSERT_TRUE(result) << Diagnostics();
     ASSERT_EQ(Diagnostics().str(), "");
     EXPECT_EQ(result->return_type, vec4_f32);
     ASSERT_EQ(result->parameters.Length(), 3u);
@@ -342,9 +342,9 @@
     auto* i32 = create<core::type::I32>();
     auto* vec2_i32 = create<core::type::Vector>(i32, 2u);
     auto* tex = create<core::type::DepthTexture>(core::type::TextureDimension::k2d);
-    auto result = Lookup(context, core::Function::kTextureLoad, Vector{tex, vec2_i32, i32},
+    auto result = Lookup(context, core::BuiltinFn::kTextureLoad, Vector{tex, vec2_i32, i32},
                          EvaluationStage::kConstant, Source{});
-    ASSERT_TRUE(result) << Diagnostics().str();
+    ASSERT_TRUE(result) << Diagnostics();
     ASSERT_EQ(Diagnostics().str(), "");
     EXPECT_EQ(result->return_type, f32);
     ASSERT_EQ(result->parameters.Length(), 3u);
@@ -361,9 +361,9 @@
     auto* i32 = create<core::type::I32>();
     auto* vec2_i32 = create<core::type::Vector>(i32, 2u);
     auto* tex = create<core::type::DepthMultisampledTexture>(core::type::TextureDimension::k2d);
-    auto result = Lookup(context, core::Function::kTextureLoad, Vector{tex, vec2_i32, i32},
+    auto result = Lookup(context, core::BuiltinFn::kTextureLoad, Vector{tex, vec2_i32, i32},
                          EvaluationStage::kConstant, Source{});
-    ASSERT_TRUE(result) << Diagnostics().str();
+    ASSERT_TRUE(result) << Diagnostics();
     ASSERT_EQ(Diagnostics().str(), "");
     EXPECT_EQ(result->return_type, f32);
     ASSERT_EQ(result->parameters.Length(), 3u);
@@ -381,9 +381,9 @@
     auto* vec2_i32 = create<core::type::Vector>(i32, 2u);
     auto* vec4_f32 = create<core::type::Vector>(f32, 4u);
     auto* tex = create<core::type::ExternalTexture>();
-    auto result = Lookup(context, core::Function::kTextureLoad, Vector{tex, vec2_i32},
+    auto result = Lookup(context, core::BuiltinFn::kTextureLoad, Vector{tex, vec2_i32},
                          EvaluationStage::kConstant, Source{});
-    ASSERT_TRUE(result) << Diagnostics().str();
+    ASSERT_TRUE(result) << Diagnostics();
     ASSERT_EQ(Diagnostics().str(), "");
     EXPECT_EQ(result->return_type, vec4_f32);
     ASSERT_EQ(result->parameters.Length(), 2u);
@@ -403,9 +403,9 @@
                                                    core::TexelFormat::kR32Float,
                                                    core::Access::kWrite, subtype);
 
-    auto result = Lookup(context, core::Function::kTextureStore, Vector{tex, vec2_i32, vec4_f32},
+    auto result = Lookup(context, core::BuiltinFn::kTextureStore, Vector{tex, vec2_i32, vec4_f32},
                          EvaluationStage::kConstant, Source{});
-    ASSERT_TRUE(result) << Diagnostics().str();
+    ASSERT_TRUE(result) << Diagnostics();
     ASSERT_EQ(Diagnostics().str(), "");
     EXPECT_TRUE(result->return_type->Is<type::Void>());
     ASSERT_EQ(result->parameters.Length(), 3u);
@@ -421,7 +421,7 @@
     auto* f32 = create<core::type::F32>();
     auto* i32 = create<core::type::I32>();
     auto* vec2_i32 = create<core::type::Vector>(i32, 2u);
-    auto result = Lookup(context, core::Function::kTextureLoad, Vector{f32, vec2_i32},
+    auto result = Lookup(context, core::BuiltinFn::kTextureLoad, Vector{f32, vec2_i32},
                          EvaluationStage::kConstant, Source{});
     ASSERT_FALSE(result);
     ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
@@ -429,13 +429,13 @@
 
 TEST_F(IntrinsicTableTest, ImplicitLoadOnReference) {
     auto* f32 = create<core::type::F32>();
-    auto result = Lookup(context, core::Function::kCos,
+    auto result = Lookup(context, core::BuiltinFn::kCos,
                          Vector{
                              create<core::type::Reference>(core::AddressSpace::kFunction, f32,
                                                            core::Access::kReadWrite),
                          },
                          EvaluationStage::kConstant, Source{});
-    ASSERT_TRUE(result) << Diagnostics().str();
+    ASSERT_TRUE(result) << Diagnostics();
     ASSERT_EQ(Diagnostics().str(), "");
     EXPECT_EQ(result->return_type, f32);
     ASSERT_EQ(result->parameters.Length(), 1u);
@@ -444,9 +444,9 @@
 
 TEST_F(IntrinsicTableTest, MatchTemplateType) {
     auto* f32 = create<core::type::F32>();
-    auto result = Lookup(context, core::Function::kClamp, Vector{f32, f32, f32},
+    auto result = Lookup(context, core::BuiltinFn::kClamp, Vector{f32, f32, f32},
                          EvaluationStage::kConstant, Source{});
-    ASSERT_TRUE(result) << Diagnostics().str();
+    ASSERT_TRUE(result) << Diagnostics();
     ASSERT_EQ(Diagnostics().str(), "");
     EXPECT_EQ(result->return_type, f32);
     EXPECT_EQ(result->parameters[0].type, f32);
@@ -457,7 +457,7 @@
 TEST_F(IntrinsicTableTest, MismatchTemplateType) {
     auto* f32 = create<core::type::F32>();
     auto* u32 = create<core::type::U32>();
-    auto result = Lookup(context, core::Function::kClamp, Vector{f32, u32, f32},
+    auto result = Lookup(context, core::BuiltinFn::kClamp, Vector{f32, u32, f32},
                          EvaluationStage::kConstant, Source{});
     ASSERT_FALSE(result);
     ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
@@ -466,9 +466,9 @@
 TEST_F(IntrinsicTableTest, MatchOpenSizeVector) {
     auto* f32 = create<core::type::F32>();
     auto* vec2_f32 = create<core::type::Vector>(f32, 2u);
-    auto result = Lookup(context, core::Function::kClamp, Vector{vec2_f32, vec2_f32, vec2_f32},
+    auto result = Lookup(context, core::BuiltinFn::kClamp, Vector{vec2_f32, vec2_f32, vec2_f32},
                          EvaluationStage::kConstant, Source{});
-    ASSERT_TRUE(result) << Diagnostics().str();
+    ASSERT_TRUE(result) << Diagnostics();
     ASSERT_EQ(Diagnostics().str(), "");
     EXPECT_EQ(result->return_type, vec2_f32);
     ASSERT_EQ(result->parameters.Length(), 3u);
@@ -481,7 +481,7 @@
     auto* f32 = create<core::type::F32>();
     auto* u32 = create<core::type::U32>();
     auto* vec2_f32 = create<core::type::Vector>(f32, 2u);
-    auto result = Lookup(context, core::Function::kClamp, Vector{vec2_f32, u32, vec2_f32},
+    auto result = Lookup(context, core::BuiltinFn::kClamp, Vector{vec2_f32, u32, vec2_f32},
                          EvaluationStage::kConstant, Source{});
     ASSERT_FALSE(result);
     ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
@@ -491,9 +491,9 @@
     auto* f32 = create<core::type::F32>();
     auto* vec3_f32 = create<core::type::Vector>(f32, 3u);
     auto* mat3_f32 = create<core::type::Matrix>(vec3_f32, 3u);
-    auto result = Lookup(context, core::Function::kDeterminant, Vector{mat3_f32},
+    auto result = Lookup(context, core::BuiltinFn::kDeterminant, Vector{mat3_f32},
                          EvaluationStage::kConstant, Source{});
-    ASSERT_TRUE(result) << Diagnostics().str();
+    ASSERT_TRUE(result) << Diagnostics();
     ASSERT_EQ(Diagnostics().str(), "");
     EXPECT_EQ(result->return_type, f32);
     ASSERT_EQ(result->parameters.Length(), 1u);
@@ -504,7 +504,7 @@
     auto* f32 = create<core::type::F32>();
     auto* vec2_f32 = create<core::type::Vector>(f32, 2u);
     auto* mat3x2_f32 = create<core::type::Matrix>(vec2_f32, 3u);
-    auto result = Lookup(context, core::Function::kDeterminant, Vector{mat3x2_f32},
+    auto result = Lookup(context, core::BuiltinFn::kDeterminant, Vector{mat3x2_f32},
                          EvaluationStage::kConstant, Source{});
     ASSERT_FALSE(result);
     ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
@@ -513,9 +513,9 @@
 TEST_F(IntrinsicTableTest, MatchDifferentArgsElementType_Builtin_ConstantEval) {
     auto* af = create<core::type::AbstractFloat>();
     auto* bool_ = create<core::type::Bool>();
-    auto result = Lookup(context, core::Function::kSelect, Vector{af, af, bool_},
+    auto result = Lookup(context, core::BuiltinFn::kSelect, Vector{af, af, bool_},
                          EvaluationStage::kConstant, Source{});
-    ASSERT_TRUE(result) << Diagnostics().str();
+    ASSERT_TRUE(result) << Diagnostics();
     ASSERT_EQ(Diagnostics().str(), "");
     EXPECT_NE(result->const_eval_fn, nullptr);
     EXPECT_EQ(result->return_type, af);
@@ -529,9 +529,9 @@
     auto* af = create<core::type::AbstractFloat>();
     auto* bool_ref = create<core::type::Reference>(
         core::AddressSpace::kFunction, create<core::type::Bool>(), core::Access::kReadWrite);
-    auto result = Lookup(context, core::Function::kSelect, Vector{af, af, bool_ref},
+    auto result = Lookup(context, core::BuiltinFn::kSelect, Vector{af, af, bool_ref},
                          EvaluationStage::kRuntime, Source{});
-    ASSERT_TRUE(result) << Diagnostics().str();
+    ASSERT_TRUE(result) << Diagnostics();
     ASSERT_EQ(Diagnostics().str(), "");
     EXPECT_NE(result->const_eval_fn, nullptr);
     EXPECT_TRUE(result->return_type->Is<core::type::F32>());
@@ -546,8 +546,8 @@
     auto* u32 = create<core::type::U32>();
     auto result = Lookup(context, core::BinaryOp::kShiftLeft, ai, u32, EvaluationStage::kConstant,
                          Source{}, false);
-    ASSERT_TRUE(result) << Diagnostics().str();
-    ASSERT_NE(result->const_eval_fn, nullptr) << Diagnostics().str();
+    ASSERT_TRUE(result) << Diagnostics();
+    ASSERT_NE(result->const_eval_fn, nullptr) << Diagnostics();
     ASSERT_EQ(Diagnostics().str(), "");
     EXPECT_EQ(result->return_type, ai);
     EXPECT_EQ(result->parameters[0].type, ai);
@@ -559,8 +559,8 @@
     auto* u32 = create<core::type::U32>();
     auto result = Lookup(context, core::BinaryOp::kShiftLeft, ai, u32, EvaluationStage::kRuntime,
                          Source{}, false);
-    ASSERT_TRUE(result) << Diagnostics().str();
-    ASSERT_NE(result->const_eval_fn, nullptr) << Diagnostics().str();
+    ASSERT_TRUE(result) << Diagnostics();
+    ASSERT_NE(result->const_eval_fn, nullptr) << Diagnostics();
     ASSERT_EQ(Diagnostics().str(), "");
     EXPECT_TRUE(result->return_type->Is<core::type::I32>());
     EXPECT_TRUE(result->parameters[0].type->Is<core::type::I32>());
@@ -571,7 +571,7 @@
     // None of the arguments match, so expect the overloads with 2 parameters to
     // come first
     auto* bool_ = create<core::type::Bool>();
-    auto result = Lookup(context, core::Function::kTextureDimensions, Vector{bool_, bool_},
+    auto result = Lookup(context, core::BuiltinFn::kTextureDimensions, Vector{bool_, bool_},
                          EvaluationStage::kConstant, Source{});
     EXPECT_FALSE(result);
     ASSERT_EQ(Diagnostics().str(),
@@ -611,7 +611,7 @@
 TEST_F(IntrinsicTableTest, OverloadOrderByMatchingParameter) {
     auto* tex = create<core::type::DepthTexture>(core::type::TextureDimension::k2d);
     auto* bool_ = create<core::type::Bool>();
-    auto result = Lookup(context, core::Function::kTextureDimensions, Vector{tex, bool_},
+    auto result = Lookup(context, core::BuiltinFn::kTextureDimensions, Vector{tex, bool_},
                          EvaluationStage::kConstant, Source{});
     EXPECT_FALSE(result);
     ASSERT_EQ(Diagnostics().str(),
@@ -760,7 +760,7 @@
     auto* vec3_i32 = create<core::type::Vector>(i32, 3u);
     auto result = Lookup(context, CtorConv::kVec3, nullptr, Vector{i32, i32, i32},
                          EvaluationStage::kConstant, Source{{12, 34}});
-    ASSERT_TRUE(result) << Diagnostics().str();
+    ASSERT_TRUE(result) << Diagnostics();
     EXPECT_EQ(result->return_type, vec3_i32);
     EXPECT_TRUE(result->info->flags.Contains(OverloadFlag::kIsConstructor));
     ASSERT_EQ(result->parameters.Length(), 3u);
@@ -775,7 +775,7 @@
     auto* vec3_i32 = create<core::type::Vector>(i32, 3u);
     auto result = Lookup(context, CtorConv::kVec3, i32, Vector{i32, i32, i32},
                          EvaluationStage::kConstant, Source{{12, 34}});
-    ASSERT_TRUE(result) << Diagnostics().str();
+    ASSERT_TRUE(result) << Diagnostics();
     EXPECT_EQ(result->return_type, vec3_i32);
     EXPECT_TRUE(result->info->flags.Contains(OverloadFlag::kIsConstructor));
     ASSERT_EQ(result->parameters.Length(), 3u);
@@ -844,7 +844,7 @@
     auto* vec3_ai = create<core::type::Vector>(ai, 3u);
     auto result = Lookup(context, CtorConv::kVec3, nullptr, Vector{vec3_ai},
                          EvaluationStage::kConstant, Source{{12, 34}});
-    ASSERT_TRUE(result) << Diagnostics().str();
+    ASSERT_TRUE(result) << Diagnostics();
     EXPECT_EQ(result->return_type, vec3_ai);
     EXPECT_TRUE(result->info->flags.Contains(OverloadFlag::kIsConstructor));
     ASSERT_EQ(result->parameters.Length(), 1u);
@@ -859,7 +859,7 @@
     auto* mat2x2_af = create<core::type::Matrix>(vec2_af, 2u);
     auto result = Lookup(context, CtorConv::kMat2x2, nullptr, Vector{vec2_ai, vec2_ai},
                          EvaluationStage::kConstant, Source{{12, 34}});
-    ASSERT_TRUE(result) << Diagnostics().str();
+    ASSERT_TRUE(result) << Diagnostics();
     EXPECT_TYPE(result->return_type, mat2x2_af);
     EXPECT_TRUE(result->info->flags.Contains(OverloadFlag::kIsConstructor));
     ASSERT_EQ(result->parameters.Length(), 2u);
@@ -873,7 +873,7 @@
     auto* vec3_ai = create<core::type::Vector>(ai, 3u);
     auto result = Lookup(context, CtorConv::kVec3, nullptr, Vector{ai, ai, ai},
                          EvaluationStage::kConstant, Source{{12, 34}});
-    ASSERT_TRUE(result) << Diagnostics().str();
+    ASSERT_TRUE(result) << Diagnostics();
     EXPECT_NE(result->const_eval_fn, nullptr);
     EXPECT_EQ(result->return_type, vec3_ai);
     EXPECT_TRUE(result->info->flags.Contains(OverloadFlag::kIsConstructor));
@@ -890,7 +890,7 @@
                          EvaluationStage::kRuntime, Source{{12, 34}});
     auto* i32 = create<type::I32>();
     auto* vec3_i32 = create<type::Vector>(i32, 3u);
-    ASSERT_TRUE(result) << Diagnostics().str();
+    ASSERT_TRUE(result) << Diagnostics();
     EXPECT_NE(result->const_eval_fn, nullptr);
     EXPECT_EQ(result->return_type, vec3_i32);
     EXPECT_TRUE(result->info->flags.Contains(OverloadFlag::kIsConstructor));
@@ -908,7 +908,7 @@
     auto* vec3_f32 = create<core::type::Vector>(f32, 3u);
     auto result = Lookup(context, CtorConv::kVec3, i32, Vector{vec3_f32},
                          EvaluationStage::kConstant, Source{{12, 34}});
-    ASSERT_TRUE(result) << Diagnostics().str();
+    ASSERT_TRUE(result) << Diagnostics();
     EXPECT_EQ(result->return_type, vec3_i32);
     EXPECT_FALSE(result->info->flags.Contains(OverloadFlag::kIsConstructor));
     ASSERT_EQ(result->parameters.Length(), 1u);
@@ -951,7 +951,7 @@
     auto* vec3_f32 = create<core::type::Vector>(f32, 3u);
     auto result = Lookup(context, CtorConv::kVec3, af, Vector{vec3_ai}, EvaluationStage::kConstant,
                          Source{{12, 34}});
-    ASSERT_TRUE(result) << Diagnostics().str();
+    ASSERT_TRUE(result) << Diagnostics();
     EXPECT_NE(result->const_eval_fn, nullptr);
     // NOTE: Conversions are explicit, so there's no way to have it return abstracts
     EXPECT_EQ(result->return_type, vec3_f32);
@@ -968,7 +968,7 @@
     auto* vec3_i32 = create<core::type::Vector>(create<core::type::I32>(), 3u);
     auto result = Lookup(context, CtorConv::kVec3, af, Vector{vec3_ai}, EvaluationStage::kRuntime,
                          Source{{12, 34}});
-    ASSERT_TRUE(result) << Diagnostics().str();
+    ASSERT_TRUE(result) << Diagnostics();
     EXPECT_NE(result->const_eval_fn, nullptr);
     EXPECT_EQ(result->return_type, vec3_f32);
     EXPECT_FALSE(result->info->flags.Contains(OverloadFlag::kIsConstructor));
@@ -980,7 +980,7 @@
     auto* f32 = create<core::type::F32>();
     Vector<const core::type::Type*, 0> arg_tys;
     arg_tys.Resize(257, f32);
-    auto result = Lookup(context, core::Function::kAbs, std::move(arg_tys),
+    auto result = Lookup(context, core::BuiltinFn::kAbs, std::move(arg_tys),
                          EvaluationStage::kConstant, Source{});
     ASSERT_FALSE(result);
     ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
@@ -995,7 +995,7 @@
     auto* i32 = create<core::type::I32>();
     auto result =
         Lookup(context, CtorConv::kI32, nullptr, Vector{ai}, EvaluationStage::kConstant, Source{});
-    ASSERT_TRUE(result) << Diagnostics().str();
+    ASSERT_TRUE(result) << Diagnostics();
     EXPECT_EQ(result->return_type, i32);
     EXPECT_EQ(result->parameters.Length(), 1u);
     EXPECT_EQ(result->parameters[0].type, ai);
@@ -1042,7 +1042,7 @@
 
     bool matched = result;
     bool expected_match = GetParam().expected_match;
-    EXPECT_EQ(matched, expected_match) << Diagnostics().str();
+    EXPECT_EQ(matched, expected_match) << Diagnostics();
 
     if (matched) {
         auto* expected_result = GetParam().expected_result(*this);
@@ -1221,11 +1221,11 @@
     auto* arg_a = GetParam().arg_a(*this);
     auto* arg_b = GetParam().arg_b(*this);
     auto* arg_c = GetParam().arg_c(*this);
-    auto builtin = Lookup(context, core::Function::kClamp, Vector{arg_a, arg_b, arg_c},
+    auto builtin = Lookup(context, core::BuiltinFn::kClamp, Vector{arg_a, arg_b, arg_c},
                           EvaluationStage::kConstant, Source{{12, 34}});
 
     bool expected_match = GetParam().expected_match;
-    EXPECT_EQ(builtin == true, expected_match) << Diagnostics().str();
+    EXPECT_EQ(builtin == true, expected_match) << Diagnostics();
 
     auto* result = builtin ? builtin->return_type : nullptr;
     auto* expected_result = GetParam().expected_result(*this);
diff --git a/src/tint/lang/core/ir/BUILD.bazel b/src/tint/lang/core/ir/BUILD.bazel
index 7d7188cd..2b94ce3 100644
--- a/src/tint/lang/core/ir/BUILD.bazel
+++ b/src/tint/lang/core/ir/BUILD.bazel
@@ -35,6 +35,7 @@
     "builder.cc",
     "builtin_call.cc",
     "call.cc",
+    "clone_context.cc",
     "constant.cc",
     "construct.cc",
     "continue.cc",
@@ -52,7 +53,6 @@
     "if.cc",
     "instruction.cc",
     "instruction_result.cc",
-    "intrinsic_call.cc",
     "let.cc",
     "load.cc",
     "load_vector_element.cc",
@@ -85,6 +85,7 @@
     "builder.h",
     "builtin_call.h",
     "call.h",
+    "clone_context.h",
     "constant.h",
     "construct.h",
     "continue.h",
@@ -102,7 +103,6 @@
     "if.h",
     "instruction.h",
     "instruction_result.h",
-    "intrinsic_call.h",
     "let.h",
     "load.h",
     "load_vector_element.h",
@@ -188,7 +188,9 @@
     "store_vector_element_test.cc",
     "switch_test.cc",
     "swizzle_test.cc",
+    "terminate_invocation_test.cc",
     "unary_test.cc",
+    "unreachable_test.cc",
     "user_call_test.cc",
     "validator_test.cc",
     "value_test.cc",
diff --git a/src/tint/lang/core/ir/BUILD.cmake b/src/tint/lang/core/ir/BUILD.cmake
index 3e21f9e..ccd8312 100644
--- a/src/tint/lang/core/ir/BUILD.cmake
+++ b/src/tint/lang/core/ir/BUILD.cmake
@@ -46,6 +46,8 @@
   lang/core/ir/builtin_call.h
   lang/core/ir/call.cc
   lang/core/ir/call.h
+  lang/core/ir/clone_context.cc
+  lang/core/ir/clone_context.h
   lang/core/ir/constant.cc
   lang/core/ir/constant.h
   lang/core/ir/construct.cc
@@ -80,8 +82,6 @@
   lang/core/ir/instruction.h
   lang/core/ir/instruction_result.cc
   lang/core/ir/instruction_result.h
-  lang/core/ir/intrinsic_call.cc
-  lang/core/ir/intrinsic_call.h
   lang/core/ir/let.cc
   lang/core/ir/let.h
   lang/core/ir/load.cc
@@ -188,7 +188,9 @@
   lang/core/ir/store_vector_element_test.cc
   lang/core/ir/switch_test.cc
   lang/core/ir/swizzle_test.cc
+  lang/core/ir/terminate_invocation_test.cc
   lang/core/ir/unary_test.cc
+  lang/core/ir/unreachable_test.cc
   lang/core/ir/user_call_test.cc
   lang/core/ir/validator_test.cc
   lang/core/ir/value_test.cc
diff --git a/src/tint/lang/core/ir/BUILD.gn b/src/tint/lang/core/ir/BUILD.gn
index 4a844c9..a2a54cc 100644
--- a/src/tint/lang/core/ir/BUILD.gn
+++ b/src/tint/lang/core/ir/BUILD.gn
@@ -49,6 +49,8 @@
     "builtin_call.h",
     "call.cc",
     "call.h",
+    "clone_context.cc",
+    "clone_context.h",
     "constant.cc",
     "constant.h",
     "construct.cc",
@@ -83,8 +85,6 @@
     "instruction.h",
     "instruction_result.cc",
     "instruction_result.h",
-    "intrinsic_call.cc",
-    "intrinsic_call.h",
     "let.cc",
     "let.h",
     "load.cc",
@@ -189,7 +189,9 @@
       "store_vector_element_test.cc",
       "switch_test.cc",
       "swizzle_test.cc",
+      "terminate_invocation_test.cc",
       "unary_test.cc",
+      "unreachable_test.cc",
       "user_call_test.cc",
       "validator_test.cc",
       "value_test.cc",
diff --git a/src/tint/lang/core/ir/access.cc b/src/tint/lang/core/ir/access.cc
index 34051ac..73682b9 100644
--- a/src/tint/lang/core/ir/access.cc
+++ b/src/tint/lang/core/ir/access.cc
@@ -16,6 +16,9 @@
 
 #include <utility>
 
+#include "src/tint/lang/core/ir/clone_context.h"
+#include "src/tint/lang/core/ir/module.h"
+
 TINT_INSTANTIATE_TYPEINFO(tint::core::ir::Access);
 
 namespace tint::core::ir {
@@ -28,6 +31,13 @@
 }
 
 Access::~Access() = default;
+
+Access* Access::Clone(CloneContext& ctx) {
+    auto new_result = ctx.Clone(Result());
+    auto new_obj = ctx.Clone(Object());
+    auto new_indices = ctx.Clone<Access::kDefaultNumOperands>(Indices());
+    return ctx.ir.instructions.Create<Access>(new_result, new_obj, new_indices);
+}
 //! @endcond
 
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/access.h b/src/tint/lang/core/ir/access.h
index 4c76121..0221f50 100644
--- a/src/tint/lang/core/ir/access.h
+++ b/src/tint/lang/core/ir/access.h
@@ -23,7 +23,7 @@
 namespace tint::core::ir {
 
 /// An access instruction in the IR.
-class Access : public Castable<Access, OperandInstruction<3, 1>> {
+class Access final : public Castable<Access, OperandInstruction<3, 1>> {
   public:
     /// The offset in Operands() for the object being accessed
     static constexpr size_t kObjectOperandOffset = 0;
@@ -38,6 +38,9 @@
     Access(InstructionResult* result, Value* object, VectorRef<Value*> indices);
     ~Access() override;
 
+    /// @copydoc Instruction::Clone()
+    Access* Clone(CloneContext& ctx) override;
+
     /// @returns the object used for the access
     Value* Object() { return operands_[kObjectOperandOffset]; }
 
diff --git a/src/tint/lang/core/ir/access_test.cc b/src/tint/lang/core/ir/access_test.cc
index 8b3c310..7d9c4d6 100644
--- a/src/tint/lang/core/ir/access_test.cc
+++ b/src/tint/lang/core/ir/access_test.cc
@@ -18,7 +18,8 @@
 #include "gtest/gtest-spi.h"
 #include "src/tint/lang/core/ir/ir_helper_test.h"
 
-using namespace tint::core::fluent_types;  // NOLINT
+using namespace tint::core::fluent_types;     // NOLINT
+using namespace tint::core::number_suffixes;  // NOLINT
 
 namespace tint::core::ir {
 namespace {
@@ -60,5 +61,43 @@
         "");
 }
 
+TEST_F(IR_AccessTest, Clone) {
+    auto* type = ty.ptr<function, i32>();
+    auto* var = b.Var(type);
+    auto* idx1 = b.Constant(u32(1));
+    auto* idx2 = b.Constant(u32(2));
+    auto* a = b.Access(type, var, idx1, idx2);
+
+    auto* new_a = clone_ctx.Clone(a);
+
+    EXPECT_NE(a, new_a);
+
+    EXPECT_NE(a->Result(), new_a->Result());
+    EXPECT_EQ(type, new_a->Result()->Type());
+
+    EXPECT_NE(nullptr, new_a->Object());
+    EXPECT_NE(a->Object(), new_a->Object());
+
+    auto indices = new_a->Indices();
+    EXPECT_EQ(2u, indices.Length());
+
+    auto* val0 = indices[0]->As<Constant>()->Value();
+    EXPECT_EQ(1_u, val0->As<core::constant::Scalar<u32>>()->ValueAs<u32>());
+
+    auto* val1 = indices[1]->As<Constant>()->Value();
+    EXPECT_EQ(2_u, val1->As<core::constant::Scalar<u32>>()->ValueAs<u32>());
+}
+
+TEST_F(IR_AccessTest, CloneNoIndices) {
+    auto* type = ty.ptr<function, i32>();
+    auto* var = b.Var(type);
+    auto* a = b.Access(type, var);
+
+    auto* new_a = clone_ctx.Clone(a);
+
+    auto indices = new_a->Indices();
+    EXPECT_EQ(0u, indices.Length());
+}
+
 }  // namespace
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/binary.cc b/src/tint/lang/core/ir/binary.cc
index 1b1bdcf..251d587 100644
--- a/src/tint/lang/core/ir/binary.cc
+++ b/src/tint/lang/core/ir/binary.cc
@@ -14,6 +14,9 @@
 
 #include "src/tint/lang/core/ir/binary.h"
 
+#include "src/tint/lang/core/ir/clone_context.h"
+#include "src/tint/lang/core/ir/module.h"
+
 TINT_INSTANTIATE_TYPEINFO(tint::core::ir::Binary);
 
 namespace tint::core::ir {
@@ -26,6 +29,13 @@
 
 Binary::~Binary() = default;
 
+Binary* Binary::Clone(CloneContext& ctx) {
+    auto* new_result = ctx.Clone(Result());
+    auto* new_lhs = ctx.Clone(LHS());
+    auto* new_rhs = ctx.Clone(RHS());
+    return ctx.ir.instructions.Create<Binary>(new_result, kind_, new_lhs, new_rhs);
+}
+
 std::string_view ToString(enum Binary::Kind kind) {
     switch (kind) {
         case Binary::Kind::kAdd:
diff --git a/src/tint/lang/core/ir/binary.h b/src/tint/lang/core/ir/binary.h
index 53ddf1a..3f4c79a 100644
--- a/src/tint/lang/core/ir/binary.h
+++ b/src/tint/lang/core/ir/binary.h
@@ -23,7 +23,7 @@
 namespace tint::core::ir {
 
 /// A binary instruction in the IR.
-class Binary : public Castable<Binary, OperandInstruction<2, 1>> {
+class Binary final : public Castable<Binary, OperandInstruction<2, 1>> {
   public:
     /// The offset in Operands() for the LHS
     static constexpr size_t kLhsOperandOffset = 0;
@@ -62,6 +62,9 @@
     Binary(InstructionResult* result, enum Kind kind, Value* lhs, Value* rhs);
     ~Binary() override;
 
+    /// @copydoc Instruction::Clone()
+    Binary* Clone(CloneContext& ctx) override;
+
     /// @returns the kind of the binary instruction
     enum Kind Kind() { return kind_; }
 
diff --git a/src/tint/lang/core/ir/binary_test.cc b/src/tint/lang/core/ir/binary_test.cc
index 721bf45..bf8553f 100644
--- a/src/tint/lang/core/ir/binary_test.cc
+++ b/src/tint/lang/core/ir/binary_test.cc
@@ -374,5 +374,26 @@
     EXPECT_THAT(rhs_b->Usages(), testing::UnorderedElementsAre(Usage{inst, 1u}));
 }
 
+TEST_F(IR_BinaryTest, Clone) {
+    auto* lhs = b.Constant(2_i);
+    auto* rhs = b.Constant(4_i);
+    auto* inst = b.And(mod.Types().i32(), lhs, rhs);
+
+    auto* c = clone_ctx.Clone(inst);
+
+    EXPECT_NE(inst, c);
+
+    EXPECT_EQ(mod.Types().i32(), c->Result()->Type());
+    EXPECT_EQ(Binary::Kind::kAnd, c->Kind());
+
+    auto new_lhs = c->LHS()->As<Constant>()->Value();
+    ASSERT_TRUE(new_lhs->Is<core::constant::Scalar<i32>>());
+    EXPECT_EQ(2_i, new_lhs->As<core::constant::Scalar<i32>>()->ValueAs<i32>());
+
+    auto new_rhs = c->RHS()->As<Constant>()->Value();
+    ASSERT_TRUE(new_rhs->Is<core::constant::Scalar<i32>>());
+    EXPECT_EQ(4_i, new_rhs->As<core::constant::Scalar<i32>>()->ValueAs<i32>());
+}
+
 }  // namespace
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/bitcast.cc b/src/tint/lang/core/ir/bitcast.cc
index cdaf695..f1fc737 100644
--- a/src/tint/lang/core/ir/bitcast.cc
+++ b/src/tint/lang/core/ir/bitcast.cc
@@ -14,6 +14,9 @@
 
 #include "src/tint/lang/core/ir/bitcast.h"
 
+#include "src/tint/lang/core/ir/clone_context.h"
+#include "src/tint/lang/core/ir/module.h"
+
 TINT_INSTANTIATE_TYPEINFO(tint::core::ir::Bitcast);
 
 namespace tint::core::ir {
@@ -25,4 +28,10 @@
 
 Bitcast::~Bitcast() = default;
 
+Bitcast* Bitcast::Clone(CloneContext& ctx) {
+    auto* new_res = ctx.Clone(Result());
+    auto* new_val = ctx.Clone(Val());
+    return ctx.ir.instructions.Create<Bitcast>(new_res, new_val);
+}
+
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/bitcast.h b/src/tint/lang/core/ir/bitcast.h
index d5f7425..3c35280 100644
--- a/src/tint/lang/core/ir/bitcast.h
+++ b/src/tint/lang/core/ir/bitcast.h
@@ -23,7 +23,7 @@
 namespace tint::core::ir {
 
 /// A bitcast instruction in the IR.
-class Bitcast : public Castable<Bitcast, Call> {
+class Bitcast final : public Castable<Bitcast, Call> {
   public:
     /// The offset in Operands() for the value
     static constexpr size_t kValueOperandOffset = 0;
@@ -34,6 +34,9 @@
     Bitcast(InstructionResult* result, Value* val);
     ~Bitcast() override;
 
+    /// @copydoc Instruction::Clone()
+    Bitcast* Clone(CloneContext& ctx) override;
+
     /// @returns the operand value
     Value* Val() { return operands_[kValueOperandOffset]; }
 
diff --git a/src/tint/lang/core/ir/bitcast_test.cc b/src/tint/lang/core/ir/bitcast_test.cc
index 02b190d..b6d14ff 100644
--- a/src/tint/lang/core/ir/bitcast_test.cc
+++ b/src/tint/lang/core/ir/bitcast_test.cc
@@ -70,5 +70,19 @@
         "");
 }
 
+TEST_F(IR_BitcastTest, Clone) {
+    auto* inst = b.Bitcast(mod.Types().i32(), 4_i);
+
+    auto* n = clone_ctx.Clone(inst);
+
+    EXPECT_NE(inst, n);
+
+    EXPECT_EQ(mod.Types().i32(), n->Result()->Type());
+
+    auto new_val = n->Val()->As<Constant>()->Value();
+    ASSERT_TRUE(new_val->Is<core::constant::Scalar<i32>>());
+    EXPECT_EQ(4_i, new_val->As<core::constant::Scalar<i32>>()->ValueAs<i32>());
+}
+
 }  // namespace
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/block.cc b/src/tint/lang/core/ir/block.cc
index f49c9d0..00654b9 100644
--- a/src/tint/lang/core/ir/block.cc
+++ b/src/tint/lang/core/ir/block.cc
@@ -13,6 +13,10 @@
 // limitations under the License.
 
 #include "src/tint/lang/core/ir/block.h"
+
+#include "src/tint/lang/core/ir/clone_context.h"
+#include "src/tint/lang/core/ir/control_instruction.h"
+#include "src/tint/lang/core/ir/module.h"
 #include "src/tint/utils/ice/ice.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::core::ir::Block);
@@ -23,6 +27,30 @@
 
 Block::~Block() = default;
 
+Block* Block::Clone(CloneContext&) {
+    TINT_UNREACHABLE() << "blocks must be cloned with CloneInto";
+    return nullptr;
+}
+
+void Block::CloneInto(CloneContext& ctx, Block* out) {
+    // Note, the `parent_` is not cloned here. Doing so can end up in infinite loops as we try to
+    // clone a control instruction and the blocks inside of it. The `parent_` pointer should be set
+    // by the control instructions constructor.
+
+    for (auto* inst_in : *this) {
+        auto* inst_out = inst_in->Clone(ctx);
+        auto results_out = inst_out->Results();
+        auto results_in = inst_in->Results();
+        TINT_ASSERT(results_out.Length() == results_in.Length());
+
+        size_t len = results_out.Length();
+        for (size_t i = 0; i < len; ++i) {
+            ctx.Replace(results_in[i], results_out[i]);
+        }
+        out->Append(inst_out);
+    }
+}
+
 Instruction* Block::Prepend(Instruction* inst) {
     TINT_ASSERT_OR_RETURN_VALUE(inst, inst);
     TINT_ASSERT_OR_RETURN_VALUE(inst->Block() == nullptr, inst);
diff --git a/src/tint/lang/core/ir/block.h b/src/tint/lang/core/ir/block.h
index 1badce2..a01e8a0 100644
--- a/src/tint/lang/core/ir/block.h
+++ b/src/tint/lang/core/ir/block.h
@@ -36,6 +36,15 @@
     Block();
     ~Block() override;
 
+    /// @param ctx the CloneContext used to clone this block
+    /// @returns a clone of this block
+    virtual Block* Clone(CloneContext& ctx);
+
+    /// Clones the block contents into the given block
+    /// @param ctx the CloneContext used to clone
+    /// @param out the block to clone into
+    virtual void CloneInto(CloneContext& ctx, Block* out);
+
     /// @returns true if this is block has a terminator instruction
     bool HasTerminator() {
         return instructions_.last != nullptr && instructions_.last->Is<ir::Terminator>();
diff --git a/src/tint/lang/core/ir/block_param.cc b/src/tint/lang/core/ir/block_param.cc
index b2a992e..1993417 100644
--- a/src/tint/lang/core/ir/block_param.cc
+++ b/src/tint/lang/core/ir/block_param.cc
@@ -13,6 +13,9 @@
 // limitations under the License.
 
 #include "src/tint/lang/core/ir/block_param.h"
+
+#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::core::ir::BlockParam);
@@ -25,4 +28,14 @@
 
 BlockParam::~BlockParam() = default;
 
+BlockParam* BlockParam::Clone(CloneContext& ctx) {
+    auto* new_bp = ctx.ir.values.Create<BlockParam>(type_);
+
+    auto name = ctx.ir.NameOf(this);
+    if (name.IsValid()) {
+        ctx.ir.SetName(new_bp, ctx.ir.NameOf(this).Name());
+    }
+    return new_bp;
+}
+
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/block_param.h b/src/tint/lang/core/ir/block_param.h
index 315aba7..778a6f2 100644
--- a/src/tint/lang/core/ir/block_param.h
+++ b/src/tint/lang/core/ir/block_param.h
@@ -31,6 +31,9 @@
     /// @returns the type of the var
     const core::type::Type* Type() override { return type_; }
 
+    /// @copydoc Instruction::Clone()
+    BlockParam* Clone(CloneContext& ctx) override;
+
   private:
     /// the result type of the instruction
     const core::type::Type* type_ = nullptr;
diff --git a/src/tint/lang/core/ir/block_param_test.cc b/src/tint/lang/core/ir/block_param_test.cc
index 8677c83..ca29c98 100644
--- a/src/tint/lang/core/ir/block_param_test.cc
+++ b/src/tint/lang/core/ir/block_param_test.cc
@@ -12,8 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "src/tint/lang/core/ir/block_param.h"
+#include <string>
+
 #include "gtest/gtest-spi.h"
+#include "src/tint/lang/core/ir/block_param.h"
 #include "src/tint/lang/core/ir/ir_helper_test.h"
 
 namespace tint::core::ir {
@@ -32,5 +34,23 @@
         "");
 }
 
+TEST_F(IR_BlockParamTest, Clone) {
+    auto* inst = b.BlockParam(mod.Types().i32());
+
+    auto* new_inst = clone_ctx.Clone(inst);
+
+    EXPECT_NE(inst, new_inst);
+    EXPECT_EQ(mod.Types().i32(), new_inst->Type());
+}
+
+TEST_F(IR_BlockParamTest, CloneWithName) {
+    auto* inst = b.BlockParam("p", mod.Types().i32());
+
+    auto* new_inst = clone_ctx.Clone(inst);
+    EXPECT_EQ(mod.Types().i32(), new_inst->Type());
+
+    EXPECT_EQ(std::string("p"), mod.NameOf(new_inst).Name());
+}
+
 }  // namespace
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/break_if.cc b/src/tint/lang/core/ir/break_if.cc
index 56ce784..bf4a6c3 100644
--- a/src/tint/lang/core/ir/break_if.cc
+++ b/src/tint/lang/core/ir/break_if.cc
@@ -17,7 +17,9 @@
 #include <utility>
 
 #include "src/tint/lang/core/ir/block.h"
+#include "src/tint/lang/core/ir/clone_context.h"
 #include "src/tint/lang/core/ir/loop.h"
+#include "src/tint/lang/core/ir/module.h"
 #include "src/tint/lang/core/ir/multi_in_block.h"
 #include "src/tint/utils/ice/ice.h"
 
@@ -38,4 +40,11 @@
 
 BreakIf::~BreakIf() = default;
 
+BreakIf* BreakIf::Clone(CloneContext& ctx) {
+    auto* new_loop = ctx.Clone(loop_);
+    auto* new_cond = ctx.Clone(Condition());
+    auto new_args = ctx.Clone<BreakIf::kDefaultNumOperands>(Args());
+    return ctx.ir.instructions.Create<BreakIf>(new_cond, new_loop, new_args);
+}
+
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/break_if.h b/src/tint/lang/core/ir/break_if.h
index 1f73c2f..e672b90 100644
--- a/src/tint/lang/core/ir/break_if.h
+++ b/src/tint/lang/core/ir/break_if.h
@@ -29,7 +29,7 @@
 namespace tint::core::ir {
 
 /// A break-if iteration instruction.
-class BreakIf : public Castable<BreakIf, Terminator> {
+class BreakIf final : public Castable<BreakIf, Terminator> {
   public:
     /// The offset in Operands() for the condition
     static constexpr size_t kConditionOperandOffset = 0;
@@ -44,6 +44,9 @@
     BreakIf(Value* condition, ir::Loop* loop, VectorRef<Value*> args = tint::Empty);
     ~BreakIf() override;
 
+    /// @copydoc Instruction::Clone()
+    BreakIf* Clone(CloneContext& ctx) override;
+
     /// @returns the MultiInBlock arguments
     tint::Slice<Value* const> Args() override {
         return operands_.Slice().Offset(kArgsOperandOffset);
diff --git a/src/tint/lang/core/ir/break_if_test.cc b/src/tint/lang/core/ir/break_if_test.cc
index 644e2dd..18e5d2d 100644
--- a/src/tint/lang/core/ir/break_if_test.cc
+++ b/src/tint/lang/core/ir/break_if_test.cc
@@ -58,5 +58,49 @@
         "");
 }
 
+TEST_F(IR_BreakIfTest, Clone) {
+    auto* loop = b.Loop();
+    auto* cond = b.Constant(true);
+    auto* arg1 = b.Constant(1_u);
+    auto* arg2 = b.Constant(2_u);
+
+    auto* brk = b.BreakIf(loop, cond, arg1, arg2);
+
+    auto* new_loop = clone_ctx.Clone(loop);
+    clone_ctx.Replace(loop, new_loop);
+
+    auto* new_brk = clone_ctx.Clone(brk);
+
+    EXPECT_NE(brk, new_brk);
+
+    EXPECT_EQ(new_loop, new_brk->Loop());
+
+    auto args = new_brk->Args();
+    EXPECT_EQ(2u, args.Length());
+
+    auto new_cond = new_brk->Condition()->As<Constant>()->Value();
+    ASSERT_TRUE(new_cond->Is<core::constant::Scalar<bool>>());
+    EXPECT_TRUE(new_cond->As<core::constant::Scalar<bool>>()->ValueAs<bool>());
+
+    auto new_arg0 = args[0]->As<Constant>()->Value();
+    ASSERT_TRUE(new_arg0->Is<core::constant::Scalar<u32>>());
+    EXPECT_EQ(1_u, new_arg0->As<core::constant::Scalar<u32>>()->ValueAs<u32>());
+
+    auto new_arg1 = args[1]->As<Constant>()->Value();
+    ASSERT_TRUE(new_arg1->Is<core::constant::Scalar<u32>>());
+    EXPECT_EQ(2_u, new_arg1->As<core::constant::Scalar<u32>>()->ValueAs<u32>());
+}
+
+TEST_F(IR_BreakIfTest, CloneNoArgs) {
+    auto* loop = b.Loop();
+    auto* cond = b.Constant(true);
+
+    auto* brk = b.BreakIf(loop, cond);
+    auto* new_brk = clone_ctx.Clone(brk);
+
+    auto args = new_brk->Args();
+    EXPECT_EQ(0u, args.Length());
+}
+
 }  // namespace
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/builder.h b/src/tint/lang/core/ir/builder.h
index 90608d2..a74f3d5 100644
--- a/src/tint/lang/core/ir/builder.h
+++ b/src/tint/lang/core/ir/builder.h
@@ -38,7 +38,6 @@
 #include "src/tint/lang/core/ir/function_param.h"
 #include "src/tint/lang/core/ir/if.h"
 #include "src/tint/lang/core/ir/instruction_result.h"
-#include "src/tint/lang/core/ir/intrinsic_call.h"
 #include "src/tint/lang/core/ir/let.h"
 #include "src/tint/lang/core/ir/load.h"
 #include "src/tint/lang/core/ir/load_vector_element.h"
@@ -672,7 +671,7 @@
     /// @param args the call arguments
     /// @returns the instruction
     template <typename... ARGS>
-    ir::CoreBuiltinCall* Call(const core::type::Type* type, core::Function func, ARGS&&... args) {
+    ir::CoreBuiltinCall* Call(const core::type::Type* type, core::BuiltinFn func, ARGS&&... args) {
         return Append(ir.instructions.Create<ir::CoreBuiltinCall>(
             InstructionResult(type), func, Values(std::forward<ARGS>(args)...)));
     }
@@ -689,18 +688,6 @@
                                                     Values(std::forward<ARGS>(args)...)));
     }
 
-    /// Creates an intrinsic call instruction
-    /// @param type the return type of the call
-    /// @param kind the intrinsic function to call
-    /// @param args the call arguments
-    /// @returns the intrinsic call instruction
-    template <typename KLASS, typename KIND, typename... ARGS>
-    tint::traits::EnableIf<tint::traits::IsTypeOrDerived<KLASS, ir::IntrinsicCall>, KLASS*>
-    Call(const core::type::Type* type, KIND kind, ARGS&&... args) {
-        return Append(ir.instructions.Create<KLASS>(InstructionResult(type), kind,
-                                                    Values(std::forward<ARGS>(args)...)));
-    }
-
     /// Creates a value conversion instruction
     /// @param to the type converted to
     /// @param val the value to be converted
@@ -793,7 +780,6 @@
             return nullptr;
         }
         auto* let = Append(ir.instructions.Create<ir::Let>(InstructionResult(val->Type()), val));
-        ir.SetName(let, name);
         ir.SetName(let->Result(), name);
         return let;
     }
diff --git a/src/tint/lang/core/ir/builtin_call.h b/src/tint/lang/core/ir/builtin_call.h
index f152f7d..fe35766 100644
--- a/src/tint/lang/core/ir/builtin_call.h
+++ b/src/tint/lang/core/ir/builtin_call.h
@@ -21,7 +21,7 @@
 
 namespace tint::core::ir {
 
-/// A builtin call instruction in the IR.
+/// The base class for builtin call instructions in the IR.
 class BuiltinCall : public Castable<BuiltinCall, Call> {
   public:
     /// The base offset in Operands() for the args
diff --git a/src/tint/lang/core/ir/intrinsic_call.cc b/src/tint/lang/core/ir/clone_context.cc
similarity index 64%
rename from src/tint/lang/core/ir/intrinsic_call.cc
rename to src/tint/lang/core/ir/clone_context.cc
index a31ab1d..d5faa58 100644
--- a/src/tint/lang/core/ir/intrinsic_call.cc
+++ b/src/tint/lang/core/ir/clone_context.cc
@@ -12,19 +12,13 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "src/tint/lang/core/ir/intrinsic_call.h"
+#include "src/tint/lang/core/ir/clone_context.h"
 
-#include <utility>
-
-TINT_INSTANTIATE_TYPEINFO(tint::core::ir::IntrinsicCall);
+#include "src/tint/lang/core/ir/builder.h"
+#include "src/tint/lang/core/ir/let.h"
 
 namespace tint::core::ir {
 
-IntrinsicCall::IntrinsicCall(InstructionResult* result, VectorRef<Value*> arguments) {
-    AddOperands(IntrinsicCall::kArgsOperandOffset, std::move(arguments));
-    AddResult(result);
-}
-
-IntrinsicCall::~IntrinsicCall() = default;
+CloneContext::CloneContext(Module& module) : ir(module) {}
 
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/clone_context.h b/src/tint/lang/core/ir/clone_context.h
new file mode 100644
index 0000000..b5c375b
--- /dev/null
+++ b/src/tint/lang/core/ir/clone_context.h
@@ -0,0 +1,111 @@
+// Copyright 2023 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_LANG_CORE_IR_CLONE_CONTEXT_H_
+#define SRC_TINT_LANG_CORE_IR_CLONE_CONTEXT_H_
+
+#include "src/tint/utils/containers/hashmap.h"
+#include "src/tint/utils/containers/transform.h"
+#include "src/tint/utils/traits/traits.h"
+
+namespace tint::core::ir {
+class Block;
+class Instruction;
+class Module;
+class Value;
+}  // namespace tint::core::ir
+
+namespace tint::core::ir {
+
+/// Constant in the IR.
+class CloneContext {
+  public:
+    /// @param module the IR module
+    explicit CloneContext(Module& module);
+
+    /// The IR module
+    Module& ir;
+
+    /// Performs a clone of @p what.
+    /// @param what the item to clone
+    /// @return the cloned item
+    template <typename T>
+    T* Clone(T* what) {
+        if (auto replacement = replacements_.Get(what)) {
+            auto* cast = As<T>((*replacement)());
+            TINT_ASSERT(cast);
+            return cast;
+        }
+        auto* result = what->Clone(*this)->template As<T>();
+        Replace(what, result);
+        return result;
+    }
+
+    /// Performs a clone of all the elements in @p what.
+    /// @param what the elements to clone
+    /// @return the cloned elements
+    template <size_t N, typename T>
+    Vector<T*, N> Clone(Slice<T* const> what) {
+        return Transform<N>(what, [&](T* const p) { return Clone(p); });
+    }
+
+    /// Performs a clone of all the elements in @p what.
+    /// @param what the elements to clone
+    /// @return the cloned elements
+    template <size_t N, typename T>
+    Vector<T*, N> Clone(Slice<T*> what) {
+        return Transform<N>(what, [&](T* p) { return Clone(p); });
+    }
+
+    /// Performs a clone of all the elements in @p what.
+    /// @param what the elements to clone
+    /// @return the cloned elements
+    template <size_t N, typename T>
+    Vector<T*, N> Clone(Vector<T*, N> what) {
+        return Transform(what, [&](T* p) { return Clone(p); });
+    }
+
+    /// Registers the replacement of `what` with `with`
+    /// @param what the value or instruction to replace
+    /// @param with either a pointer to a replacement instruction, or a function with the signature
+    /// `T*(T*)` used to build the replacement
+    template <typename WHAT, typename WITH>
+    void Replace(WHAT* what, WITH&& with) {
+        using T = std::decay_t<WHAT>;
+        using F = std::decay_t<WITH>;
+
+        constexpr bool T_is_value = traits::IsTypeOrDerived<T, Value>;
+        constexpr bool T_is_instruction = traits::IsTypeOrDerived<T, Instruction>;
+        static_assert(T_is_value || T_is_instruction);
+
+        constexpr bool F_is_pointer = std::is_pointer_v<F>;
+        constexpr bool F_is_function = std::is_function_v<F>;
+        static_assert(F_is_pointer || F_is_function);
+
+        if constexpr (F_is_pointer) {
+            replacements_.Add(what, [with]() { return with; });
+        } else if constexpr (F_is_function) {
+            static_assert(std::is_same_v<traits::ParameterType<F, 0>, T*>);
+            static_assert(std::is_same_v<traits::ReturnType<F>, T*>);
+            replacements_.Add(what, [what, with]() { return with(what); });
+        }
+    }
+
+  private:
+    Hashmap<CastableBase*, std::function<CastableBase*()>, 8> replacements_;
+};
+
+}  // namespace tint::core::ir
+
+#endif  // SRC_TINT_LANG_CORE_IR_CLONE_CONTEXT_H_
diff --git a/src/tint/lang/core/ir/constant.cc b/src/tint/lang/core/ir/constant.cc
index 2510a39..a9cc837 100644
--- a/src/tint/lang/core/ir/constant.cc
+++ b/src/tint/lang/core/ir/constant.cc
@@ -25,4 +25,8 @@
 
 Constant::~Constant() = default;
 
+Constant* Constant::Clone(CloneContext&) {
+    return this;  // Constants are immutable so can just return ourselves.
+}
+
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/constant.h b/src/tint/lang/core/ir/constant.h
index a316e24..ebf584b 100644
--- a/src/tint/lang/core/ir/constant.h
+++ b/src/tint/lang/core/ir/constant.h
@@ -34,6 +34,9 @@
     /// @returns the type of the constant
     const core::type::Type* Type() override { return value_->Type(); }
 
+    /// @copydoc Value::Clone()
+    Constant* Clone(CloneContext& ctx) override;
+
   private:
     const core::constant::Value* const value_ = nullptr;
 };
diff --git a/src/tint/lang/core/ir/constant_test.cc b/src/tint/lang/core/ir/constant_test.cc
index 73b4282..1bdcfb9 100644
--- a/src/tint/lang/core/ir/constant_test.cc
+++ b/src/tint/lang/core/ir/constant_test.cc
@@ -113,5 +113,12 @@
         "");
 }
 
+TEST_F(IR_ConstantTest, Clone) {
+    auto* c = b.Constant(2_u);
+    auto* new_c = clone_ctx.Clone(c);
+
+    EXPECT_EQ(c, new_c);
+}
+
 }  // namespace
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/construct.cc b/src/tint/lang/core/ir/construct.cc
index bbdb1db..f9ae628 100644
--- a/src/tint/lang/core/ir/construct.cc
+++ b/src/tint/lang/core/ir/construct.cc
@@ -16,6 +16,9 @@
 
 #include <utility>
 
+#include "src/tint/lang/core/ir/clone_context.h"
+#include "src/tint/lang/core/ir/module.h"
+
 TINT_INSTANTIATE_TYPEINFO(tint::core::ir::Construct);
 
 namespace tint::core::ir {
@@ -27,4 +30,10 @@
 
 Construct::~Construct() = default;
 
+Construct* Construct::Clone(CloneContext& ctx) {
+    auto* new_result = ctx.Clone(Result());
+    auto new_args = ctx.Clone<Construct::kDefaultNumOperands>(Args());
+    return ctx.ir.instructions.Create<Construct>(new_result, new_args);
+}
+
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/construct.h b/src/tint/lang/core/ir/construct.h
index 61c9a76..29fe22c 100644
--- a/src/tint/lang/core/ir/construct.h
+++ b/src/tint/lang/core/ir/construct.h
@@ -23,7 +23,7 @@
 namespace tint::core::ir {
 
 /// A constructor instruction in the IR.
-class Construct : public Castable<Construct, Call> {
+class Construct final : public Castable<Construct, Call> {
   public:
     /// The base offset in Operands() for the args
     static constexpr size_t kArgsOperandOffset = 0;
@@ -34,6 +34,9 @@
     explicit Construct(InstructionResult* result, VectorRef<Value*> args = tint::Empty);
     ~Construct() override;
 
+    /// @copydoc Instruction::Clone()
+    Construct* Clone(CloneContext& ctx) override;
+
     /// @returns the friendly name for the instruction
     std::string FriendlyName() override { return "construct"; }
 };
diff --git a/src/tint/lang/core/ir/construct_test.cc b/src/tint/lang/core/ir/construct_test.cc
index 044fb87..0340892 100644
--- a/src/tint/lang/core/ir/construct_test.cc
+++ b/src/tint/lang/core/ir/construct_test.cc
@@ -54,5 +54,35 @@
         "");
 }
 
+TEST_F(IR_ConstructTest, Clone) {
+    auto* arg1 = b.Constant(true);
+    auto* arg2 = b.Constant(false);
+    auto* c = b.Construct(mod.Types().f32(), arg1, arg2);
+
+    auto* new_c = clone_ctx.Clone(c);
+
+    EXPECT_NE(c, new_c);
+    EXPECT_NE(c->Result(), new_c->Result());
+    EXPECT_EQ(mod.Types().f32(), new_c->Result()->Type());
+
+    auto args = new_c->Args();
+    EXPECT_EQ(2u, args.Length());
+
+    auto* val0 = args[0]->As<Constant>()->Value();
+    EXPECT_TRUE(val0->As<core::constant::Scalar<bool>>()->ValueAs<bool>());
+
+    auto* val1 = args[1]->As<Constant>()->Value();
+    EXPECT_FALSE(val1->As<core::constant::Scalar<bool>>()->ValueAs<bool>());
+}
+
+TEST_F(IR_ConstructTest, CloneEmpty) {
+    auto* c = b.Construct(mod.Types().f32());
+
+    auto* new_c = clone_ctx.Clone(c);
+    EXPECT_NE(c->Result(), new_c->Result());
+    EXPECT_EQ(mod.Types().f32(), new_c->Result()->Type());
+    EXPECT_TRUE(new_c->Args().IsEmpty());
+}
+
 }  // namespace
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/continue.cc b/src/tint/lang/core/ir/continue.cc
index 6fb585b..7ddb1a3 100644
--- a/src/tint/lang/core/ir/continue.cc
+++ b/src/tint/lang/core/ir/continue.cc
@@ -17,7 +17,9 @@
 #include <utility>
 
 #include "src/tint/lang/core/ir/block.h"
+#include "src/tint/lang/core/ir/clone_context.h"
 #include "src/tint/lang/core/ir/loop.h"
+#include "src/tint/lang/core/ir/module.h"
 #include "src/tint/lang/core/ir/multi_in_block.h"
 #include "src/tint/utils/ice/ice.h"
 
@@ -37,4 +39,11 @@
 
 Continue::~Continue() = default;
 
+Continue* Continue::Clone(CloneContext& ctx) {
+    auto* new_loop = ctx.Clone(Loop());
+    auto new_args = ctx.Clone<Continue::kDefaultNumOperands>(Args());
+
+    return ctx.ir.instructions.Create<Continue>(new_loop, new_args);
+}
+
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/continue.h b/src/tint/lang/core/ir/continue.h
index 1c393e1..b4a0178 100644
--- a/src/tint/lang/core/ir/continue.h
+++ b/src/tint/lang/core/ir/continue.h
@@ -28,7 +28,7 @@
 namespace tint::core::ir {
 
 /// A continue instruction.
-class Continue : public Castable<Continue, Terminator> {
+class Continue final : public Castable<Continue, Terminator> {
   public:
     /// The base offset in Operands() for the args
     static constexpr size_t kArgsOperandOffset = 0;
@@ -39,6 +39,9 @@
     explicit Continue(ir::Loop* loop, VectorRef<Value*> args = tint::Empty);
     ~Continue() override;
 
+    /// @copydoc Instruction::Clone()
+    Continue* Clone(CloneContext& ctx) override;
+
     /// @returns the loop owning the continue block
     ir::Loop* Loop() { return loop_; }
 
diff --git a/src/tint/lang/core/ir/continue_test.cc b/src/tint/lang/core/ir/continue_test.cc
index 0dfe024..88c25ec 100644
--- a/src/tint/lang/core/ir/continue_test.cc
+++ b/src/tint/lang/core/ir/continue_test.cc
@@ -56,5 +56,38 @@
         "");
 }
 
+TEST_F(IR_ContinueTest, Clone) {
+    auto* loop = b.Loop();
+    auto* cont = b.Continue(loop);
+
+    auto* new_loop = clone_ctx.Clone(loop);
+    clone_ctx.Replace(loop, new_loop);
+
+    auto* new_c = clone_ctx.Clone(cont);
+
+    EXPECT_NE(cont, new_c);
+    EXPECT_EQ(new_loop, new_c->Loop());
+    EXPECT_TRUE(new_c->Args().IsEmpty());
+}
+
+TEST_F(IR_ContinueTest, CloneWithArgs) {
+    auto* loop = b.Loop();
+    auto* arg1 = b.Constant(1_u);
+    auto* arg2 = b.Constant(2_u);
+
+    auto* cont = b.Continue(loop, arg1, arg2);
+
+    auto* new_c = clone_ctx.Clone(cont);
+
+    auto args = new_c->Args();
+    EXPECT_EQ(2u, args.Length());
+
+    auto* val0 = args[0]->As<Constant>()->Value();
+    EXPECT_EQ(1_u, val0->As<core::constant::Scalar<u32>>()->ValueAs<u32>());
+
+    auto* val1 = args[1]->As<Constant>()->Value();
+    EXPECT_EQ(2_u, val1->As<core::constant::Scalar<u32>>()->ValueAs<u32>());
+}
+
 }  // namespace
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/convert.cc b/src/tint/lang/core/ir/convert.cc
index 3f3a70b..c9a5696 100644
--- a/src/tint/lang/core/ir/convert.cc
+++ b/src/tint/lang/core/ir/convert.cc
@@ -16,6 +16,9 @@
 
 #include <utility>
 
+#include "src/tint/lang/core/ir/clone_context.h"
+#include "src/tint/lang/core/ir/module.h"
+
 TINT_INSTANTIATE_TYPEINFO(tint::core::ir::Convert);
 
 namespace tint::core::ir {
@@ -27,4 +30,10 @@
 
 Convert::~Convert() = default;
 
+Convert* Convert::Clone(CloneContext& ctx) {
+    auto* new_result = ctx.Clone(Result());
+    auto* new_val = ctx.Clone(Args()[0]);
+    return ctx.ir.instructions.Create<Convert>(new_result, new_val);
+}
+
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/convert.h b/src/tint/lang/core/ir/convert.h
index 1c58228..6699c18 100644
--- a/src/tint/lang/core/ir/convert.h
+++ b/src/tint/lang/core/ir/convert.h
@@ -24,7 +24,7 @@
 namespace tint::core::ir {
 
 /// A value conversion instruction in the IR.
-class Convert : public Castable<Convert, Call> {
+class Convert final : public Castable<Convert, Call> {
   public:
     /// The offset in Operands() for the value
     static constexpr size_t kValueOperandOffset = 0;
@@ -35,6 +35,9 @@
     Convert(InstructionResult* result, Value* value);
     ~Convert() override;
 
+    /// @copydoc Instruction::Clone()
+    Convert* Clone(CloneContext& ctx) override;
+
     /// @returns the friendly name for the instruction
     std::string FriendlyName() override { return "convert"; }
 };
diff --git a/src/tint/lang/core/ir/convert_test.cc b/src/tint/lang/core/ir/convert_test.cc
index d239dcc..3732b8f 100644
--- a/src/tint/lang/core/ir/convert_test.cc
+++ b/src/tint/lang/core/ir/convert_test.cc
@@ -41,5 +41,21 @@
     EXPECT_EQ(c->Result()->Source(), c);
 }
 
+TEST_F(IR_ConvertTest, Clone) {
+    auto* c = b.Convert(mod.Types().f32(), 1_u);
+
+    auto* new_c = clone_ctx.Clone(c);
+
+    EXPECT_NE(c, new_c);
+    EXPECT_NE(c->Result(), new_c->Result());
+    EXPECT_EQ(mod.Types().f32(), new_c->Result()->Type());
+
+    auto args = new_c->Args();
+    EXPECT_EQ(1u, args.Length());
+
+    auto* val0 = args[0]->As<Constant>()->Value();
+    EXPECT_EQ(1_u, val0->As<core::constant::Scalar<u32>>()->ValueAs<u32>());
+}
+
 }  // namespace
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/core_builtin_call.cc b/src/tint/lang/core/ir/core_builtin_call.cc
index 68a49c8..86325ac 100644
--- a/src/tint/lang/core/ir/core_builtin_call.cc
+++ b/src/tint/lang/core/ir/core_builtin_call.cc
@@ -16,6 +16,8 @@
 
 #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::core::ir::CoreBuiltinCall);
@@ -23,13 +25,19 @@
 namespace tint::core::ir {
 
 CoreBuiltinCall::CoreBuiltinCall(InstructionResult* result,
-                                 core::Function func,
+                                 core::BuiltinFn func,
                                  VectorRef<Value*> arguments)
     : Base(result, arguments), func_(func) {
-    TINT_ASSERT(func != core::Function::kNone);
-    TINT_ASSERT(func != core::Function::kTintMaterialize);
+    TINT_ASSERT(func != core::BuiltinFn::kNone);
+    TINT_ASSERT(func != core::BuiltinFn::kTintMaterialize);
 }
 
 CoreBuiltinCall::~CoreBuiltinCall() = default;
 
+CoreBuiltinCall* CoreBuiltinCall::Clone(CloneContext& ctx) {
+    auto* new_result = ctx.Clone(Result());
+    auto new_args = ctx.Clone<CoreBuiltinCall::kDefaultNumOperands>(Args());
+    return ctx.ir.instructions.Create<CoreBuiltinCall>(new_result, func_, new_args);
+}
+
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/core_builtin_call.h b/src/tint/lang/core/ir/core_builtin_call.h
index bc1907b..24c998d 100644
--- a/src/tint/lang/core/ir/core_builtin_call.h
+++ b/src/tint/lang/core/ir/core_builtin_call.h
@@ -17,7 +17,7 @@
 
 #include <string>
 
-#include "src/tint/lang/core/function.h"
+#include "src/tint/lang/core/builtin_fn.h"
 #include "src/tint/lang/core/intrinsic/data/data.h"
 #include "src/tint/lang/core/intrinsic/table_data.h"
 #include "src/tint/lang/core/ir/builtin_call.h"
@@ -26,19 +26,22 @@
 namespace tint::core::ir {
 
 /// A core builtin call instruction in the IR.
-class CoreBuiltinCall : public Castable<CoreBuiltinCall, BuiltinCall> {
+class CoreBuiltinCall final : public Castable<CoreBuiltinCall, BuiltinCall> {
   public:
     /// Constructor
     /// @param result the result value
     /// @param func the builtin function
     /// @param args the conversion arguments
     CoreBuiltinCall(InstructionResult* result,
-                    core::Function func,
+                    core::BuiltinFn func,
                     VectorRef<Value*> args = tint::Empty);
     ~CoreBuiltinCall() override;
 
+    /// @copydoc Instruction::Clone()
+    CoreBuiltinCall* Clone(CloneContext& ctx) override;
+
     /// @returns the builtin function
-    core::Function Func() { return func_; }
+    core::BuiltinFn Func() { return func_; }
 
     /// @returns the identifier for the function
     size_t FuncId() override { return static_cast<size_t>(func_); }
@@ -53,7 +56,7 @@
     const core::intrinsic::TableData& TableData() override { return core::intrinsic::data::kData; }
 
   private:
-    core::Function func_;
+    core::BuiltinFn func_;
 };
 
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/core_builtin_call_test.cc b/src/tint/lang/core/ir/core_builtin_call_test.cc
index 1a201e4..290af5a 100644
--- a/src/tint/lang/core/ir/core_builtin_call_test.cc
+++ b/src/tint/lang/core/ir/core_builtin_call_test.cc
@@ -26,7 +26,7 @@
 TEST_F(IR_CoreBuiltinCallTest, Usage) {
     auto* arg1 = b.Constant(1_u);
     auto* arg2 = b.Constant(2_u);
-    auto* builtin = b.Call(mod.Types().f32(), core::Function::kAbs, arg1, arg2);
+    auto* builtin = b.Call(mod.Types().f32(), core::BuiltinFn::kAbs, arg1, arg2);
 
     EXPECT_THAT(arg1->Usages(), testing::UnorderedElementsAre(Usage{builtin, 0u}));
     EXPECT_THAT(arg2->Usages(), testing::UnorderedElementsAre(Usage{builtin, 1u}));
@@ -35,7 +35,7 @@
 TEST_F(IR_CoreBuiltinCallTest, Result) {
     auto* arg1 = b.Constant(1_u);
     auto* arg2 = b.Constant(2_u);
-    auto* builtin = b.Call(mod.Types().f32(), core::Function::kAbs, arg1, arg2);
+    auto* builtin = b.Call(mod.Types().f32(), core::BuiltinFn::kAbs, arg1, arg2);
 
     EXPECT_TRUE(builtin->HasResults());
     EXPECT_FALSE(builtin->HasMultiResults());
@@ -48,7 +48,7 @@
         {
             Module mod;
             Builder b{mod};
-            b.Call(nullptr, core::Function::kAbs);
+            b.Call(nullptr, core::BuiltinFn::kAbs);
         },
         "");
 }
@@ -58,7 +58,7 @@
         {
             Module mod;
             Builder b{mod};
-            b.Call(mod.Types().f32(), core::Function::kNone);
+            b.Call(mod.Types().f32(), core::BuiltinFn::kNone);
         },
         "");
 }
@@ -68,10 +68,44 @@
         {
             Module mod;
             Builder b{mod};
-            b.Call(mod.Types().f32(), core::Function::kTintMaterialize);
+            b.Call(mod.Types().f32(), core::BuiltinFn::kTintMaterialize);
         },
         "");
 }
 
+TEST_F(IR_CoreBuiltinCallTest, Clone) {
+    auto* builtin = b.Call(mod.Types().f32(), core::BuiltinFn::kAbs, 1_u, 2_u);
+
+    auto* new_b = clone_ctx.Clone(builtin);
+
+    EXPECT_NE(builtin, new_b);
+    EXPECT_NE(builtin->Result(), new_b->Result());
+    EXPECT_EQ(mod.Types().f32(), new_b->Result()->Type());
+
+    EXPECT_EQ(core::BuiltinFn::kAbs, new_b->Func());
+
+    auto args = new_b->Args();
+    EXPECT_EQ(2u, args.Length());
+
+    auto* val0 = args[0]->As<Constant>()->Value();
+    EXPECT_EQ(1_u, val0->As<core::constant::Scalar<u32>>()->ValueAs<u32>());
+
+    auto* val1 = args[1]->As<Constant>()->Value();
+    EXPECT_EQ(2_u, val1->As<core::constant::Scalar<u32>>()->ValueAs<u32>());
+}
+
+TEST_F(IR_CoreBuiltinCallTest, CloneNoArgs) {
+    auto* builtin = b.Call(mod.Types().f32(), core::BuiltinFn::kAbs);
+
+    auto* new_b = clone_ctx.Clone(builtin);
+    EXPECT_NE(builtin->Result(), new_b->Result());
+    EXPECT_EQ(mod.Types().f32(), new_b->Result()->Type());
+
+    EXPECT_EQ(core::BuiltinFn::kAbs, new_b->Func());
+
+    auto args = new_b->Args();
+    EXPECT_TRUE(args.IsEmpty());
+}
+
 }  // namespace
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/disassembler.cc b/src/tint/lang/core/ir/disassembler.cc
index 079fe43..191f4d1 100644
--- a/src/tint/lang/core/ir/disassembler.cc
+++ b/src/tint/lang/core/ir/disassembler.cc
@@ -35,7 +35,6 @@
 #include "src/tint/lang/core/ir/exit_switch.h"
 #include "src/tint/lang/core/ir/if.h"
 #include "src/tint/lang/core/ir/instruction_result.h"
-#include "src/tint/lang/core/ir/intrinsic_call.h"
 #include "src/tint/lang/core/ir/let.h"
 #include "src/tint/lang/core/ir/load.h"
 #include "src/tint/lang/core/ir/load_vector_element.h"
diff --git a/src/tint/lang/core/ir/discard.cc b/src/tint/lang/core/ir/discard.cc
index a4aaa19..7112f01 100644
--- a/src/tint/lang/core/ir/discard.cc
+++ b/src/tint/lang/core/ir/discard.cc
@@ -13,7 +13,9 @@
 // limitations under the License.
 
 #include "src/tint/lang/core/ir/discard.h"
-#include "src/tint/lang/core/type/void.h"
+
+#include "src/tint/lang/core/ir/clone_context.h"
+#include "src/tint/lang/core/ir/module.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::core::ir::Discard);
 
@@ -23,4 +25,8 @@
 
 Discard::~Discard() = default;
 
+Discard* Discard::Clone(CloneContext& ctx) {
+    return ctx.ir.instructions.Create<Discard>();
+}
+
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/discard.h b/src/tint/lang/core/ir/discard.h
index b98c753..b9d3ee9 100644
--- a/src/tint/lang/core/ir/discard.h
+++ b/src/tint/lang/core/ir/discard.h
@@ -23,12 +23,15 @@
 namespace tint::core::ir {
 
 /// A discard instruction in the IR.
-class Discard : public Castable<Discard, Call> {
+class Discard final : public Castable<Discard, Call> {
   public:
     /// Constructor
     Discard();
     ~Discard() override;
 
+    /// @copydoc Instruction::Clone()
+    Discard* Clone(CloneContext& ctx) override;
+
     /// @returns the friendly name for the instruction
     std::string FriendlyName() override { return "discard"; }
 };
diff --git a/src/tint/lang/core/ir/discard_test.cc b/src/tint/lang/core/ir/discard_test.cc
index 2ebb80b..cb3bf9b 100644
--- a/src/tint/lang/core/ir/discard_test.cc
+++ b/src/tint/lang/core/ir/discard_test.cc
@@ -34,5 +34,13 @@
     EXPECT_FALSE(inst->HasMultiResults());
 }
 
+TEST_F(IR_DiscardTest, Clone) {
+    auto* d = b.Discard();
+    auto* new_d = clone_ctx.Clone(d);
+
+    EXPECT_NE(d, new_d);
+    EXPECT_NE(nullptr, new_d);
+}
+
 }  // namespace
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/exit_if.cc b/src/tint/lang/core/ir/exit_if.cc
index 641df8c..8bb6539 100644
--- a/src/tint/lang/core/ir/exit_if.cc
+++ b/src/tint/lang/core/ir/exit_if.cc
@@ -16,7 +16,9 @@
 
 #include <utility>
 
+#include "src/tint/lang/core/ir/clone_context.h"
 #include "src/tint/lang/core/ir/if.h"
+#include "src/tint/lang/core/ir/module.h"
 #include "src/tint/lang/core/ir/multi_in_block.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::core::ir::ExitIf);
@@ -30,6 +32,12 @@
 
 ExitIf::~ExitIf() = default;
 
+ExitIf* ExitIf::Clone(CloneContext& ctx) {
+    auto* new_if = ctx.Clone(If());
+    auto new_args = ctx.Clone<ExitIf::kDefaultNumOperands>(Args());
+    return ctx.ir.instructions.Create<ExitIf>(new_if, new_args);
+}
+
 void ExitIf::SetIf(ir::If* i) {
     SetControlInstruction(i);
 }
diff --git a/src/tint/lang/core/ir/exit_if.h b/src/tint/lang/core/ir/exit_if.h
index 31e6d55..57950ee 100644
--- a/src/tint/lang/core/ir/exit_if.h
+++ b/src/tint/lang/core/ir/exit_if.h
@@ -28,7 +28,7 @@
 namespace tint::core::ir {
 
 /// A exit if instruction.
-class ExitIf : public Castable<ExitIf, Exit> {
+class ExitIf final : public Castable<ExitIf, Exit> {
   public:
     /// The base offset in Operands() for the args
     static constexpr size_t kArgsOperandOffset = 0;
@@ -39,6 +39,9 @@
     explicit ExitIf(ir::If* i, VectorRef<Value*> args = tint::Empty);
     ~ExitIf() override;
 
+    /// @copydoc Instruction::Clone()
+    ExitIf* Clone(CloneContext& ctx) override;
+
     /// Re-associates the exit with the given if instruction
     /// @param i the new If to exit from
     void SetIf(ir::If* i);
diff --git a/src/tint/lang/core/ir/exit_if_test.cc b/src/tint/lang/core/ir/exit_if_test.cc
index a74ae52..46b70e1 100644
--- a/src/tint/lang/core/ir/exit_if_test.cc
+++ b/src/tint/lang/core/ir/exit_if_test.cc
@@ -53,5 +53,40 @@
     EXPECT_FALSE(exit->Alive());
 }
 
+TEST_F(IR_ExitIfTest, Clone) {
+    auto* arg1 = b.Constant(1_u);
+    auto* arg2 = b.Constant(2_u);
+    auto* if_ = b.If(true);
+    auto* e = b.ExitIf(if_, arg1, arg2);
+
+    auto* new_if = clone_ctx.Clone(if_);
+    auto* new_exit = clone_ctx.Clone(e);
+
+    EXPECT_NE(e, new_exit);
+    EXPECT_EQ(new_if, new_exit->If());
+
+    auto args = new_exit->Args();
+    ASSERT_EQ(2u, args.Length());
+
+    auto new_arg1 = args[0]->As<Constant>()->Value();
+    ASSERT_TRUE(new_arg1->Is<core::constant::Scalar<u32>>());
+    EXPECT_EQ(1_u, new_arg1->As<core::constant::Scalar<u32>>()->ValueAs<u32>());
+
+    auto new_arg2 = args[1]->As<Constant>()->Value();
+    ASSERT_TRUE(new_arg2->Is<core::constant::Scalar<u32>>());
+    EXPECT_EQ(2_u, new_arg2->As<core::constant::Scalar<u32>>()->ValueAs<u32>());
+}
+
+TEST_F(IR_ExitIfTest, CloneNoArgs) {
+    auto* if_ = b.If(true);
+    auto* e = b.ExitIf(if_);
+
+    auto* new_if = clone_ctx.Clone(if_);
+    auto* new_exit = clone_ctx.Clone(e);
+
+    EXPECT_EQ(new_if, new_exit->If());
+    EXPECT_TRUE(new_exit->Args().IsEmpty());
+}
+
 }  // namespace
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/exit_loop.cc b/src/tint/lang/core/ir/exit_loop.cc
index 674ce88..8e8d504 100644
--- a/src/tint/lang/core/ir/exit_loop.cc
+++ b/src/tint/lang/core/ir/exit_loop.cc
@@ -17,7 +17,9 @@
 #include <utility>
 
 #include "src/tint/lang/core/ir/block.h"
+#include "src/tint/lang/core/ir/clone_context.h"
 #include "src/tint/lang/core/ir/loop.h"
+#include "src/tint/lang/core/ir/module.h"
 #include "src/tint/lang/core/ir/multi_in_block.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::core::ir::ExitLoop);
@@ -31,6 +33,12 @@
 
 ExitLoop::~ExitLoop() = default;
 
+ExitLoop* ExitLoop::Clone(CloneContext& ctx) {
+    auto* new_loop = ctx.Clone(Loop());
+    auto new_args = ctx.Clone<ExitLoop::kDefaultNumOperands>(Args());
+    return ctx.ir.instructions.Create<ExitLoop>(new_loop, new_args);
+}
+
 void ExitLoop::SetLoop(ir::Loop* l) {
     SetControlInstruction(l);
 }
diff --git a/src/tint/lang/core/ir/exit_loop.h b/src/tint/lang/core/ir/exit_loop.h
index 6e173df..e50c7f7 100644
--- a/src/tint/lang/core/ir/exit_loop.h
+++ b/src/tint/lang/core/ir/exit_loop.h
@@ -28,7 +28,7 @@
 namespace tint::core::ir {
 
 /// A exit loop instruction.
-class ExitLoop : public Castable<ExitLoop, Exit> {
+class ExitLoop final : public Castable<ExitLoop, Exit> {
   public:
     /// The base offset in Operands() for the args
     static constexpr size_t kArgsOperandOffset = 0;
@@ -39,6 +39,9 @@
     explicit ExitLoop(ir::Loop* loop, VectorRef<Value*> args = tint::Empty);
     ~ExitLoop() override;
 
+    /// @copydoc Instruction::Clone()
+    ExitLoop* Clone(CloneContext& ctx) override;
+
     /// Re-associates the exit with the given loop instruction
     /// @param l the new loop to exit from
     void SetLoop(ir::Loop* l);
diff --git a/src/tint/lang/core/ir/exit_loop_test.cc b/src/tint/lang/core/ir/exit_loop_test.cc
index 0853d6b..79ce43a 100644
--- a/src/tint/lang/core/ir/exit_loop_test.cc
+++ b/src/tint/lang/core/ir/exit_loop_test.cc
@@ -43,5 +43,40 @@
     EXPECT_FALSE(exit->Alive());
 }
 
+TEST_F(IR_ExitLoopTest, Clone) {
+    auto* arg1 = b.Constant(1_u);
+    auto* arg2 = b.Constant(2_u);
+    auto* loop = b.Loop();
+    auto* e = b.ExitLoop(loop, arg1, arg2);
+
+    auto* new_loop = clone_ctx.Clone(loop);
+    auto* new_exit = clone_ctx.Clone(e);
+
+    EXPECT_NE(e, new_exit);
+    EXPECT_EQ(new_loop, new_exit->Loop());
+
+    auto args = new_exit->Args();
+    ASSERT_EQ(2u, args.Length());
+
+    auto new_arg1 = args[0]->As<Constant>()->Value();
+    ASSERT_TRUE(new_arg1->Is<core::constant::Scalar<u32>>());
+    EXPECT_EQ(1_u, new_arg1->As<core::constant::Scalar<u32>>()->ValueAs<u32>());
+
+    auto new_arg2 = args[1]->As<Constant>()->Value();
+    ASSERT_TRUE(new_arg2->Is<core::constant::Scalar<u32>>());
+    EXPECT_EQ(2_u, new_arg2->As<core::constant::Scalar<u32>>()->ValueAs<u32>());
+}
+
+TEST_F(IR_ExitLoopTest, CloneNoArgs) {
+    auto* loop = b.Loop();
+    auto* e = b.ExitLoop(loop);
+
+    auto* new_loop = clone_ctx.Clone(loop);
+    auto* new_exit = clone_ctx.Clone(e);
+
+    EXPECT_EQ(new_loop, new_exit->Loop());
+    EXPECT_TRUE(new_exit->Args().IsEmpty());
+}
+
 }  // namespace
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/exit_switch.cc b/src/tint/lang/core/ir/exit_switch.cc
index 7a70073..2216c8f 100644
--- a/src/tint/lang/core/ir/exit_switch.cc
+++ b/src/tint/lang/core/ir/exit_switch.cc
@@ -16,6 +16,8 @@
 
 #include <utility>
 
+#include "src/tint/lang/core/ir/clone_context.h"
+#include "src/tint/lang/core/ir/module.h"
 #include "src/tint/lang/core/ir/multi_in_block.h"
 #include "src/tint/lang/core/ir/switch.h"
 
@@ -30,6 +32,12 @@
 
 ExitSwitch::~ExitSwitch() = default;
 
+ExitSwitch* ExitSwitch::Clone(CloneContext& ctx) {
+    auto* new_switch = ctx.Clone(Switch());
+    auto new_args = ctx.Clone<ExitSwitch::kDefaultNumOperands>(Args());
+    return ctx.ir.instructions.Create<ExitSwitch>(new_switch, new_args);
+}
+
 void ExitSwitch::SetSwitch(ir::Switch* s) {
     SetControlInstruction(s);
 }
diff --git a/src/tint/lang/core/ir/exit_switch.h b/src/tint/lang/core/ir/exit_switch.h
index 45a2733..c73ad6b 100644
--- a/src/tint/lang/core/ir/exit_switch.h
+++ b/src/tint/lang/core/ir/exit_switch.h
@@ -28,7 +28,7 @@
 namespace tint::core::ir {
 
 /// A exit switch instruction.
-class ExitSwitch : public Castable<ExitSwitch, Exit> {
+class ExitSwitch final : public Castable<ExitSwitch, Exit> {
   public:
     /// The base offset in Operands() for the args
     static constexpr size_t kArgsOperandOffset = 0;
@@ -39,6 +39,9 @@
     explicit ExitSwitch(ir::Switch* sw, VectorRef<Value*> args = tint::Empty);
     ~ExitSwitch() override;
 
+    /// @copydoc Instruction::Clone()
+    ExitSwitch* Clone(CloneContext& ctx) override;
+
     /// Re-associates the exit with the given switch instruction
     /// @param s the new switch to exit from
     void SetSwitch(ir::Switch* s);
diff --git a/src/tint/lang/core/ir/exit_switch_test.cc b/src/tint/lang/core/ir/exit_switch_test.cc
index d476cda..23f2f9c 100644
--- a/src/tint/lang/core/ir/exit_switch_test.cc
+++ b/src/tint/lang/core/ir/exit_switch_test.cc
@@ -53,5 +53,40 @@
     EXPECT_FALSE(exit->Alive());
 }
 
+TEST_F(IR_ExitSwitchTest, Clone) {
+    auto* arg1 = b.Constant(1_u);
+    auto* arg2 = b.Constant(2_u);
+    auto* switch_ = b.Switch(true);
+    auto* e = b.ExitSwitch(switch_, arg1, arg2);
+
+    auto* new_switch = clone_ctx.Clone(switch_);
+    auto* new_exit = clone_ctx.Clone(e);
+
+    EXPECT_NE(e, new_exit);
+    EXPECT_EQ(new_switch, new_exit->Switch());
+
+    auto args = new_exit->Args();
+    ASSERT_EQ(2u, args.Length());
+
+    auto new_arg1 = args[0]->As<Constant>()->Value();
+    ASSERT_TRUE(new_arg1->Is<core::constant::Scalar<u32>>());
+    EXPECT_EQ(1_u, new_arg1->As<core::constant::Scalar<u32>>()->ValueAs<u32>());
+
+    auto new_arg2 = args[1]->As<Constant>()->Value();
+    ASSERT_TRUE(new_arg2->Is<core::constant::Scalar<u32>>());
+    EXPECT_EQ(2_u, new_arg2->As<core::constant::Scalar<u32>>()->ValueAs<u32>());
+}
+
+TEST_F(IR_ExitSwitchTest, CloneNoArgs) {
+    auto* switch_ = b.Switch(true);
+    auto* e = b.ExitSwitch(switch_);
+
+    auto* new_switch = clone_ctx.Clone(switch_);
+    auto* new_exit = clone_ctx.Clone(e);
+
+    EXPECT_EQ(new_switch, new_exit->Switch());
+    EXPECT_TRUE(new_exit->Args().IsEmpty());
+}
+
 }  // namespace
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/function.cc b/src/tint/lang/core/ir/function.cc
index 28a23f2..57309c0 100644
--- a/src/tint/lang/core/ir/function.cc
+++ b/src/tint/lang/core/ir/function.cc
@@ -14,6 +14,8 @@
 
 #include "src/tint/lang/core/ir/function.h"
 
+#include "src/tint/lang/core/ir/clone_context.h"
+#include "src/tint/lang/core/ir/module.h"
 #include "src/tint/utils/containers/predicates.h"
 #include "src/tint/utils/ice/ice.h"
 
@@ -32,6 +34,22 @@
 
 Function::~Function() = default;
 
+Function* Function::Clone(CloneContext& ctx) {
+    auto* new_func = ctx.ir.values.Create<Function>(return_.type, pipeline_stage_, workgroup_size_);
+    new_func->block_ = ctx.ir.blocks.Create<ir::Block>();
+    new_func->params_ = ctx.Clone<1>(params_.Slice());
+    new_func->return_.builtin = return_.builtin;
+    new_func->return_.location = return_.location;
+    new_func->return_.invariant = return_.invariant;
+
+    ctx.Replace(this, new_func);
+    block_->CloneInto(ctx, new_func->block_);
+
+    ctx.ir.SetName(new_func, ctx.ir.NameOf(this).Name());
+    ctx.ir.functions.Push(new_func);
+    return new_func;
+}
+
 void Function::SetParams(VectorRef<FunctionParam*> params) {
     params_ = std::move(params);
     TINT_ASSERT(!params_.Any(IsNull));
diff --git a/src/tint/lang/core/ir/function.h b/src/tint/lang/core/ir/function.h
index c24db41..34a9276 100644
--- a/src/tint/lang/core/ir/function.h
+++ b/src/tint/lang/core/ir/function.h
@@ -67,6 +67,9 @@
              std::optional<std::array<uint32_t, 3>> wg_size = {});
     ~Function() override;
 
+    /// @copydoc Instruction::Clone()
+    Function* Clone(CloneContext& ctx) override;
+
     /// Sets the function stage
     /// @param stage the stage to set
     void SetStage(PipelineStage stage) { pipeline_stage_ = stage; }
diff --git a/src/tint/lang/core/ir/function_param.cc b/src/tint/lang/core/ir/function_param.cc
index 877364e..8d667ee 100644
--- a/src/tint/lang/core/ir/function_param.cc
+++ b/src/tint/lang/core/ir/function_param.cc
@@ -14,6 +14,8 @@
 
 #include "src/tint/lang/core/ir/function_param.h"
 
+#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::core::ir::FunctionParam);
@@ -58,4 +60,18 @@
     return "<unknown>";
 }
 
+FunctionParam* FunctionParam::Clone(CloneContext& ctx) {
+    auto* out = ctx.ir.values.Create<FunctionParam>(type_);
+    out->builtin_ = builtin_;
+    out->location_ = location_;
+    out->binding_point_ = binding_point_;
+    out->invariant_ = invariant_;
+
+    auto name = ctx.ir.NameOf(this);
+    if (name.IsValid()) {
+        ctx.ir.SetName(out, name);
+    }
+    return out;
+}
+
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/function_param.h b/src/tint/lang/core/ir/function_param.h
index d9bf75c..f3b83b7 100644
--- a/src/tint/lang/core/ir/function_param.h
+++ b/src/tint/lang/core/ir/function_param.h
@@ -67,6 +67,9 @@
     /// @returns the type of the var
     const core::type::Type* Type() override { return type_; }
 
+    /// @copydoc Value::Clone()
+    FunctionParam* Clone(CloneContext& ctx) override;
+
     /// Sets the builtin information. Note, it is currently an error if the builtin is already set.
     /// @param val the builtin to set
     void SetBuiltin(FunctionParam::Builtin val) {
diff --git a/src/tint/lang/core/ir/function_param_test.cc b/src/tint/lang/core/ir/function_param_test.cc
index 1ca35d7..eb1b4c1 100644
--- a/src/tint/lang/core/ir/function_param_test.cc
+++ b/src/tint/lang/core/ir/function_param_test.cc
@@ -12,8 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "src/tint/lang/core/ir/function_param.h"
+#include <string>
+
 #include "gtest/gtest-spi.h"
+#include "src/tint/lang/core/ir/function_param.h"
 #include "src/tint/lang/core/ir/ir_helper_test.h"
 
 namespace tint::core::ir {
@@ -44,5 +46,53 @@
         "");
 }
 
+TEST_F(IR_FunctionParamTest, CloneEmpty) {
+    auto* fp = b.FunctionParam(mod.Types().f32());
+
+    auto* new_fp = clone_ctx.Clone(fp);
+    EXPECT_EQ(new_fp->Type(), mod.Types().f32());
+    EXPECT_FALSE(new_fp->Builtin().has_value());
+    EXPECT_FALSE(new_fp->Location().has_value());
+    EXPECT_FALSE(new_fp->BindingPoint().has_value());
+    EXPECT_FALSE(new_fp->Invariant());
+}
+
+TEST_F(IR_FunctionParamTest, Clone) {
+    auto* fp = b.FunctionParam(mod.Types().f32());
+    fp->SetBuiltin(FunctionParam::Builtin::kVertexIndex);
+    fp->SetLocation(
+        1, Interpolation{core::InterpolationType::kFlat, core::InterpolationSampling::kCentroid});
+    fp->SetInvariant(true);
+    fp->SetBindingPoint(1, 2);
+
+    auto* new_fp = clone_ctx.Clone(fp);
+
+    EXPECT_NE(fp, new_fp);
+    EXPECT_EQ(new_fp->Type(), mod.Types().f32());
+
+    EXPECT_TRUE(new_fp->Builtin().has_value());
+    EXPECT_EQ(FunctionParam::Builtin::kVertexIndex, new_fp->Builtin().value());
+
+    EXPECT_TRUE(new_fp->Location().has_value());
+    auto loc = new_fp->Location();
+    EXPECT_EQ(1u, loc->value);
+    EXPECT_EQ(core::InterpolationType::kFlat, loc->interpolation->type);
+    EXPECT_EQ(core::InterpolationSampling::kCentroid, loc->interpolation->sampling);
+
+    EXPECT_TRUE(new_fp->BindingPoint().has_value());
+    auto bp = new_fp->BindingPoint();
+    EXPECT_EQ(1u, bp->group);
+    EXPECT_EQ(2u, bp->binding);
+
+    EXPECT_TRUE(new_fp->Invariant());
+}
+
+TEST_F(IR_FunctionParamTest, CloneWithName) {
+    auto* fp = b.FunctionParam("fp", mod.Types().f32());
+    auto* new_fp = clone_ctx.Clone(fp);
+
+    EXPECT_EQ(std::string("fp"), mod.NameOf(new_fp).Name());
+}
+
 }  // namespace
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/function_test.cc b/src/tint/lang/core/ir/function_test.cc
index 862c9ab..6739793 100644
--- a/src/tint/lang/core/ir/function_test.cc
+++ b/src/tint/lang/core/ir/function_test.cc
@@ -12,8 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "src/tint/lang/core/ir/function.h"
+#include <string>
+
 #include "gtest/gtest-spi.h"
+#include "src/tint/lang/core/ir/function.h"
 #include "src/tint/lang/core/ir/ir_helper_test.h"
 
 namespace tint::core::ir {
@@ -66,5 +68,61 @@
         "");
 }
 
+TEST_F(IR_FunctionTest, Clone) {
+    auto* f =
+        b.Function("my_func", mod.Types().i32(), Function::PipelineStage::kCompute, {{2, 3, 4}});
+    f->SetReturnBuiltin(Function::ReturnBuiltin::kFragDepth);
+    f->SetReturnLocation(
+        1, Interpolation{core::InterpolationType::kFlat, core::InterpolationSampling::kCentroid});
+    f->SetReturnInvariant(true);
+
+    auto* param1 = b.FunctionParam("a", mod.Types().i32());
+    auto* param2 = b.FunctionParam("b", mod.Types().f32());
+    f->SetParams({param1, param2});
+
+    auto* new_param1 = clone_ctx.Clone(param1);
+    auto* new_param2 = clone_ctx.Clone(param2);
+    auto* new_f = clone_ctx.Clone(f);
+
+    EXPECT_NE(f, new_f);
+    EXPECT_EQ(std::string("my_func"), mod.NameOf(new_f).Name());
+
+    EXPECT_EQ(Function::PipelineStage::kCompute, new_f->Stage());
+    EXPECT_TRUE(new_f->WorkgroupSize().has_value());
+    auto wg = new_f->WorkgroupSize().value();
+    EXPECT_EQ(2u, wg[0]);
+    EXPECT_EQ(3u, wg[1]);
+    EXPECT_EQ(4u, wg[2]);
+
+    EXPECT_EQ(mod.Types().i32(), new_f->ReturnType());
+
+    EXPECT_TRUE(new_f->ReturnBuiltin().has_value());
+    EXPECT_EQ(Function::ReturnBuiltin::kFragDepth, new_f->ReturnBuiltin().value());
+
+    EXPECT_TRUE(new_f->ReturnLocation().has_value());
+    auto loc = new_f->ReturnLocation().value();
+    EXPECT_EQ(1u, loc.value);
+    EXPECT_EQ(core::InterpolationType::kFlat, loc.interpolation->type);
+    EXPECT_EQ(core::InterpolationSampling::kCentroid, loc.interpolation->sampling);
+
+    EXPECT_TRUE(new_f->ReturnInvariant());
+
+    EXPECT_EQ(2u, new_f->Params().Length());
+    EXPECT_EQ(new_param1, new_f->Params()[0]);
+    EXPECT_EQ(new_param2, new_f->Params()[1]);
+
+    EXPECT_EQ(new_f, mod.functions.Back());
+}
+
+TEST_F(IR_FunctionTest, CloneWithExits) {
+    auto* f = b.Function("my_func", mod.Types().void_());
+    b.Append(f->Block(), [&] { b.Return(f); });
+
+    auto* new_f = clone_ctx.Clone(f);
+    EXPECT_EQ(1u, new_f->Block()->Length());
+    EXPECT_TRUE(new_f->Block()->Front()->Is<Return>());
+    EXPECT_EQ(new_f, new_f->Block()->Front()->As<Return>()->Func());
+}
+
 }  // namespace
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/if.cc b/src/tint/lang/core/ir/if.cc
index a57adf5..6368db2 100644
--- a/src/tint/lang/core/ir/if.cc
+++ b/src/tint/lang/core/ir/if.cc
@@ -16,6 +16,8 @@
 
 TINT_INSTANTIATE_TYPEINFO(tint::core::ir::If);
 
+#include "src/tint/lang/core/ir/clone_context.h"
+#include "src/tint/lang/core/ir/module.h"
 #include "src/tint/lang/core/ir/multi_in_block.h"
 #include "src/tint/utils/ice/ice.h"
 
@@ -46,4 +48,17 @@
     }
 }
 
+If* If::Clone(CloneContext& ctx) {
+    auto* new_cond = ctx.Clone(Condition());
+    auto* new_true = ctx.ir.blocks.Create<ir::Block>();
+    auto* new_false = ctx.ir.blocks.Create<ir::Block>();
+
+    auto* new_if = ctx.ir.instructions.Create<If>(new_cond, new_true, new_false);
+    ctx.Replace(this, new_if);
+
+    true_->CloneInto(ctx, new_true);
+    false_->CloneInto(ctx, new_false);
+    return new_if;
+}
+
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/if.h b/src/tint/lang/core/ir/if.h
index aa68508..d4f031b 100644
--- a/src/tint/lang/core/ir/if.h
+++ b/src/tint/lang/core/ir/if.h
@@ -42,7 +42,7 @@
 ///                    â–¼
 ///                   out
 /// ```
-class If : public Castable<If, ControlInstruction> {
+class If final : public Castable<If, ControlInstruction> {
   public:
     /// The index of the condition operand
     static constexpr size_t kConditionOperandOffset = 0;
@@ -54,6 +54,9 @@
     If(Value* cond, ir::Block* t, ir::Block* f);
     ~If() override;
 
+    /// @copydoc Instruction::Clone()
+    If* Clone(CloneContext& ctx) override;
+
     /// @copydoc ControlInstruction::ForeachBlock
     void ForeachBlock(const std::function<void(ir::Block*)>& cb) override;
 
diff --git a/src/tint/lang/core/ir/if_test.cc b/src/tint/lang/core/ir/if_test.cc
index 8ecb742..01a8538 100644
--- a/src/tint/lang/core/ir/if_test.cc
+++ b/src/tint/lang/core/ir/if_test.cc
@@ -63,5 +63,34 @@
         "");
 }
 
+TEST_F(IR_IfTest, Clone) {
+    auto* if_ = b.If(b.Constant(true));
+    auto* new_if = clone_ctx.Clone(if_);
+
+    EXPECT_NE(if_, new_if);
+
+    auto new_cond = new_if->Condition()->As<Constant>()->Value();
+    ASSERT_TRUE(new_cond->Is<core::constant::Scalar<bool>>());
+    EXPECT_TRUE(new_cond->As<core::constant::Scalar<bool>>()->ValueAs<bool>());
+
+    EXPECT_NE(nullptr, new_if->True());
+    EXPECT_NE(nullptr, new_if->False());
+    EXPECT_NE(if_->True(), new_if->True());
+    EXPECT_NE(if_->False(), new_if->False());
+}
+
+TEST_F(IR_IfTest, CloneWithExits) {
+    If* new_if = nullptr;
+    {
+        auto* if_ = b.If(true);
+        b.Append(if_->True(), [&] { b.ExitIf(if_); });
+        new_if = clone_ctx.Clone(if_);
+    }
+
+    ASSERT_EQ(1u, new_if->True()->Length());
+    EXPECT_TRUE(new_if->True()->Front()->Is<ExitIf>());
+    EXPECT_EQ(new_if, new_if->True()->Front()->As<ExitIf>()->If());
+}
+
 }  // namespace
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/instruction.h b/src/tint/lang/core/ir/instruction.h
index d2503d2..7a6f790 100644
--- a/src/tint/lang/core/ir/instruction.h
+++ b/src/tint/lang/core/ir/instruction.h
@@ -25,6 +25,7 @@
 // Forward declarations
 namespace tint::core::ir {
 class Block;
+class CloneContext;
 }  // namespace tint::core::ir
 
 namespace tint::core::ir {
@@ -62,6 +63,10 @@
     /// @returns the friendly name for the instruction
     virtual std::string FriendlyName() = 0;
 
+    /// @param ctx the CloneContext used to clone this instruction
+    /// @returns a clone of this instruction
+    virtual Instruction* Clone(CloneContext& ctx) = 0;
+
     /// @returns true if the Instruction has not been destroyed with Destroy()
     bool Alive() const { return !flags_.Contains(Flag::kDead); }
 
diff --git a/src/tint/lang/core/ir/instruction_result.cc b/src/tint/lang/core/ir/instruction_result.cc
index cc78b33..01e8d61 100644
--- a/src/tint/lang/core/ir/instruction_result.cc
+++ b/src/tint/lang/core/ir/instruction_result.cc
@@ -14,8 +14,10 @@
 
 #include "src/tint/lang/core/ir/instruction_result.h"
 
+#include "src/tint/lang/core/ir/clone_context.h"
 #include "src/tint/lang/core/ir/constant.h"
 #include "src/tint/lang/core/ir/instruction.h"
+#include "src/tint/lang/core/ir/module.h"
 #include "src/tint/utils/ice/ice.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::core::ir::InstructionResult);
@@ -33,4 +35,10 @@
     Base::Destroy();
 }
 
+InstructionResult* InstructionResult::Clone(CloneContext& ctx) {
+    // Do not clone the `Source`. It will be set when this result is placed in the new parent
+    // instruction.
+    return ctx.ir.values.Create<InstructionResult>(type_);
+}
+
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/instruction_result.h b/src/tint/lang/core/ir/instruction_result.h
index d139a3c..c3b747c 100644
--- a/src/tint/lang/core/ir/instruction_result.h
+++ b/src/tint/lang/core/ir/instruction_result.h
@@ -36,6 +36,9 @@
     /// @returns the type of the value
     const core::type::Type* Type() override { return type_; }
 
+    /// @copydoc Value::Clone()
+    InstructionResult* Clone(CloneContext& ctx) override;
+
     /// Sets the type of the value to @p type
     /// @param type the new type of the value
     void SetType(const core::type::Type* type) { type_ = type; }
diff --git a/src/tint/lang/core/ir/instruction_result_test.cc b/src/tint/lang/core/ir/instruction_result_test.cc
index 68951b7..0991ea3 100644
--- a/src/tint/lang/core/ir/instruction_result_test.cc
+++ b/src/tint/lang/core/ir/instruction_result_test.cc
@@ -35,5 +35,14 @@
         "");
 }
 
+TEST_F(IR_InstructionResultTest, Clone) {
+    auto* val = b.Add(mod.Types().i32(), 1_i, 2_i)->Result();
+    auto* new_res = clone_ctx.Clone(val);
+
+    EXPECT_NE(val, new_res);
+    EXPECT_EQ(nullptr, new_res->Source());
+    EXPECT_EQ(mod.Types().i32(), new_res->Type());
+}
+
 }  // namespace
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/intrinsic_call.h b/src/tint/lang/core/ir/intrinsic_call.h
deleted file mode 100644
index 175d902..0000000
--- a/src/tint/lang/core/ir/intrinsic_call.h
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2023 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_TINT_LANG_CORE_IR_INTRINSIC_CALL_H_
-#define SRC_TINT_LANG_CORE_IR_INTRINSIC_CALL_H_
-
-#include <string>
-
-#include "src/tint/lang/core/ir/call.h"
-#include "src/tint/utils/rtti/castable.h"
-
-namespace tint::core::ir {
-
-/// A backend intrinsic call instruction in the IR.
-class IntrinsicCall : public Castable<IntrinsicCall, Call> {
-  public:
-    /// The base offset in Operands() for the args
-    static constexpr size_t kArgsOperandOffset = 0;
-
-    /// Constructor
-    /// @param result the result value
-    /// @param args the intrinsic call arguments
-    explicit IntrinsicCall(InstructionResult* result, VectorRef<Value*> args = tint::Empty);
-    ~IntrinsicCall() override;
-};
-
-}  // namespace tint::core::ir
-
-#endif  // SRC_TINT_LANG_CORE_IR_INTRINSIC_CALL_H_
diff --git a/src/tint/lang/core/ir/ir_helper_test.h b/src/tint/lang/core/ir/ir_helper_test.h
index 9e13af9..8d749c2 100644
--- a/src/tint/lang/core/ir/ir_helper_test.h
+++ b/src/tint/lang/core/ir/ir_helper_test.h
@@ -17,6 +17,7 @@
 
 #include "gtest/gtest.h"
 #include "src/tint/lang/core/ir/builder.h"
+#include "src/tint/lang/core/ir/clone_context.h"
 #include "src/tint/lang/core/ir/module.h"
 
 namespace tint::core::ir {
@@ -34,6 +35,9 @@
     Builder b{mod};
     /// The type manager
     core::type::Manager& ty{mod.Types()};
+
+    /// CloneContext
+    CloneContext clone_ctx{mod};
 };
 
 using IRTestHelper = IRTestHelperBase<testing::Test>;
diff --git a/src/tint/lang/core/ir/let.cc b/src/tint/lang/core/ir/let.cc
index 4e7236a..b843ee5 100644
--- a/src/tint/lang/core/ir/let.cc
+++ b/src/tint/lang/core/ir/let.cc
@@ -13,6 +13,9 @@
 // limitations under the License.
 
 #include "src/tint/lang/core/ir/let.h"
+
+#include "src/tint/lang/core/ir/clone_context.h"
+#include "src/tint/lang/core/ir/module.h"
 #include "src/tint/lang/core/ir/store.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::core::ir::Let);
@@ -26,4 +29,15 @@
 
 Let::~Let() = default;
 
+Let* Let::Clone(CloneContext& ctx) {
+    auto* new_result = ctx.Clone(Result());
+    auto* new_val = ctx.Clone(Value());
+    auto* new_let = ctx.ir.instructions.Create<Let>(new_result, new_val);
+
+    auto name = ctx.ir.NameOf(this);
+    ctx.ir.SetName(new_let, name.Name());
+
+    return new_let;
+}
+
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/let.h b/src/tint/lang/core/ir/let.h
index 5c0355b..4077534e 100644
--- a/src/tint/lang/core/ir/let.h
+++ b/src/tint/lang/core/ir/let.h
@@ -22,7 +22,7 @@
 namespace tint::core::ir {
 
 /// A no-op instruction in the IR, used to position and name a value
-class Let : public Castable<Let, OperandInstruction<1, 1>> {
+class Let final : public Castable<Let, OperandInstruction<1, 1>> {
   public:
     /// The offset in Operands() for the value
     static constexpr size_t kValueOperandOffset = 0;
@@ -33,6 +33,9 @@
     Let(InstructionResult* result, Value* value);
     ~Let() override;
 
+    /// @copydoc Instruction::Clone()
+    Let* Clone(CloneContext& ctx) override;
+
     /// @returns the value
     ir::Value* Value() { return operands_[kValueOperandOffset]; }
 
diff --git a/src/tint/lang/core/ir/let_test.cc b/src/tint/lang/core/ir/let_test.cc
index b0f716c..6769734 100644
--- a/src/tint/lang/core/ir/let_test.cc
+++ b/src/tint/lang/core/ir/let_test.cc
@@ -49,5 +49,23 @@
     EXPECT_EQ(let->Result()->Type(), value->Type());
 }
 
+TEST_F(IR_LetTest, Clone) {
+    auto* value = b.Constant(4_f);
+    auto* let = b.Let("l", value);
+
+    auto* new_let = clone_ctx.Clone(let);
+
+    EXPECT_NE(let, new_let);
+    EXPECT_NE(nullptr, new_let->Result());
+    EXPECT_NE(let->Result(), new_let->Result());
+
+    auto new_val = new_let->Value()->As<Constant>()->Value();
+    ASSERT_TRUE(new_val->Is<core::constant::Scalar<f32>>());
+    EXPECT_FLOAT_EQ(4_f, new_val->As<core::constant::Scalar<f32>>()->ValueAs<f32>());
+
+    EXPECT_EQ(std::string("l"), mod.NameOf(new_let).Name());
+    EXPECT_EQ(std::string("l"), mod.NameOf(new_let->Result()).Name());
+}
+
 }  // namespace
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/load.cc b/src/tint/lang/core/ir/load.cc
index d60e151..361643b 100644
--- a/src/tint/lang/core/ir/load.cc
+++ b/src/tint/lang/core/ir/load.cc
@@ -14,6 +14,8 @@
 
 #include "src/tint/lang/core/ir/load.h"
 
+#include "src/tint/lang/core/ir/clone_context.h"
+#include "src/tint/lang/core/ir/module.h"
 #include "src/tint/lang/core/type/pointer.h"
 #include "src/tint/utils/ice/ice.h"
 
@@ -33,4 +35,10 @@
 
 Load::~Load() = default;
 
+Load* Load::Clone(CloneContext& ctx) {
+    auto* new_result = ctx.Clone(Result());
+    auto* new_from = ctx.Clone(From());
+    return ctx.ir.instructions.Create<Load>(new_result, new_from);
+}
+
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/load.h b/src/tint/lang/core/ir/load.h
index 55fb983..1bf1ca1 100644
--- a/src/tint/lang/core/ir/load.h
+++ b/src/tint/lang/core/ir/load.h
@@ -23,7 +23,7 @@
 namespace tint::core::ir {
 
 /// A load instruction in the IR.
-class Load : public Castable<Load, OperandInstruction<1, 1>> {
+class Load final : public Castable<Load, OperandInstruction<1, 1>> {
   public:
     /// The offset in Operands() for the from value
     static constexpr size_t kFromOperandOffset = 0;
@@ -35,6 +35,9 @@
 
     ~Load() override;
 
+    /// @copydoc Instruction::Clone()
+    Load* Clone(CloneContext& ctx) override;
+
     /// @returns the value being loaded from
     Value* From() { return operands_[kFromOperandOffset]; }
 
diff --git a/src/tint/lang/core/ir/load_test.cc b/src/tint/lang/core/ir/load_test.cc
index fe4c6fe..1a07476 100644
--- a/src/tint/lang/core/ir/load_test.cc
+++ b/src/tint/lang/core/ir/load_test.cc
@@ -69,5 +69,19 @@
         "");
 }
 
+TEST_F(IR_LoadTest, Clone) {
+    auto* var = b.Var(ty.ptr<function, i32>());
+    auto* inst = b.Load(var);
+
+    auto* new_var = clone_ctx.Clone(var);
+    auto* new_inst = clone_ctx.Clone(inst);
+
+    EXPECT_NE(inst, new_inst);
+    EXPECT_NE(nullptr, new_inst->Result());
+    EXPECT_NE(inst->Result(), new_inst->Result());
+
+    EXPECT_EQ(new_var->Result(), new_inst->From());
+}
+
 }  // namespace
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/load_vector_element.cc b/src/tint/lang/core/ir/load_vector_element.cc
index e0969b9..aebf4d5 100644
--- a/src/tint/lang/core/ir/load_vector_element.cc
+++ b/src/tint/lang/core/ir/load_vector_element.cc
@@ -14,6 +14,9 @@
 
 #include "src/tint/lang/core/ir/load_vector_element.h"
 
+#include "src/tint/lang/core/ir/clone_context.h"
+#include "src/tint/lang/core/ir/module.h"
+
 TINT_INSTANTIATE_TYPEINFO(tint::core::ir::LoadVectorElement);
 
 namespace tint::core::ir {
@@ -28,4 +31,11 @@
 
 LoadVectorElement::~LoadVectorElement() = default;
 
+LoadVectorElement* LoadVectorElement::Clone(CloneContext& ctx) {
+    auto* new_result = ctx.Clone(Result());
+    auto* new_from = ctx.Clone(From());
+    auto* new_index = ctx.Clone(Index());
+    return ctx.ir.instructions.Create<LoadVectorElement>(new_result, new_from, new_index);
+}
+
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/load_vector_element.h b/src/tint/lang/core/ir/load_vector_element.h
index cd7e206..c141a0b 100644
--- a/src/tint/lang/core/ir/load_vector_element.h
+++ b/src/tint/lang/core/ir/load_vector_element.h
@@ -23,7 +23,7 @@
 namespace tint::core::ir {
 
 /// A load instruction for a single vector element in the IR.
-class LoadVectorElement : public Castable<LoadVectorElement, OperandInstruction<3, 0>> {
+class LoadVectorElement final : public Castable<LoadVectorElement, OperandInstruction<3, 0>> {
   public:
     /// The offset in Operands() for the `from` value
     static constexpr size_t kFromOperandOffset = 0;
@@ -38,6 +38,9 @@
     LoadVectorElement(InstructionResult* result, ir::Value* from, ir::Value* index);
     ~LoadVectorElement() override;
 
+    /// @copydoc Instruction::Clone()
+    LoadVectorElement* Clone(CloneContext& ctx) override;
+
     /// @returns the vector pointer value
     ir::Value* From() { return operands_[kFromOperandOffset]; }
 
diff --git a/src/tint/lang/core/ir/load_vector_element_test.cc b/src/tint/lang/core/ir/load_vector_element_test.cc
index 35a755d..744cf2b 100644
--- a/src/tint/lang/core/ir/load_vector_element_test.cc
+++ b/src/tint/lang/core/ir/load_vector_element_test.cc
@@ -58,5 +58,23 @@
     EXPECT_FALSE(inst->HasMultiResults());
 }
 
+TEST_F(IR_LoadVectorElementTest, Clone) {
+    auto* from = b.Var(ty.ptr<private_, vec3<i32>>());
+    auto* inst = b.LoadVectorElement(from, 2_i);
+
+    auto* new_from = clone_ctx.Clone(from);
+    auto* new_inst = clone_ctx.Clone(inst);
+
+    EXPECT_NE(inst, new_inst);
+    EXPECT_NE(nullptr, new_inst->Result());
+    EXPECT_NE(inst->Result(), new_inst->Result());
+
+    EXPECT_EQ(new_from->Result(), new_inst->From());
+
+    auto new_idx = new_inst->Index()->As<Constant>()->Value();
+    ASSERT_TRUE(new_idx->Is<core::constant::Scalar<i32>>());
+    EXPECT_EQ(2_i, new_idx->As<core::constant::Scalar<i32>>()->ValueAs<i32>());
+}
+
 }  // namespace
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/loop.cc b/src/tint/lang/core/ir/loop.cc
index 5112718..ada2516 100644
--- a/src/tint/lang/core/ir/loop.cc
+++ b/src/tint/lang/core/ir/loop.cc
@@ -16,6 +16,8 @@
 
 #include <utility>
 
+#include "src/tint/lang/core/ir/clone_context.h"
+#include "src/tint/lang/core/ir/module.h"
 #include "src/tint/lang/core/ir/multi_in_block.h"
 #include "src/tint/utils/ice/ice.h"
 
@@ -42,6 +44,21 @@
 
 Loop::~Loop() = default;
 
+Loop* Loop::Clone(CloneContext& ctx) {
+    auto* new_init = ctx.ir.blocks.Create<MultiInBlock>();
+    auto* new_body = ctx.ir.blocks.Create<MultiInBlock>();
+    auto* new_continuing = ctx.ir.blocks.Create<MultiInBlock>();
+
+    auto* new_loop = ctx.ir.instructions.Create<Loop>(new_init, new_body, new_continuing);
+    ctx.Replace(this, new_loop);
+
+    initializer_->CloneInto(ctx, new_init);
+    body_->CloneInto(ctx, new_body);
+    continuing_->CloneInto(ctx, new_continuing);
+
+    return new_loop;
+}
+
 void Loop::ForeachBlock(const std::function<void(ir::Block*)>& cb) {
     if (initializer_) {
         cb(initializer_);
diff --git a/src/tint/lang/core/ir/loop.h b/src/tint/lang/core/ir/loop.h
index 689b9e2..5e97226 100644
--- a/src/tint/lang/core/ir/loop.h
+++ b/src/tint/lang/core/ir/loop.h
@@ -56,7 +56,7 @@
 ///                     out
 ///
 /// ```
-class Loop : public Castable<Loop, ControlInstruction> {
+class Loop final : public Castable<Loop, ControlInstruction> {
   public:
     /// Constructor
     /// @param i the initializer block
@@ -65,6 +65,9 @@
     Loop(ir::Block* i, ir::MultiInBlock* b, ir::MultiInBlock* c);
     ~Loop() override;
 
+    /// @copydoc Instruction::Clone()
+    Loop* Clone(CloneContext& ctx) override;
+
     /// @copydoc ControlInstruction::ForeachBlock
     void ForeachBlock(const std::function<void(ir::Block*)>& cb) override;
 
diff --git a/src/tint/lang/core/ir/loop_test.cc b/src/tint/lang/core/ir/loop_test.cc
index 1a4beb5..89699f0 100644
--- a/src/tint/lang/core/ir/loop_test.cc
+++ b/src/tint/lang/core/ir/loop_test.cc
@@ -65,5 +65,57 @@
         "");
 }
 
+TEST_F(IR_LoopTest, Clone) {
+    auto* loop = b.Loop();
+    auto* new_loop = clone_ctx.Clone(loop);
+
+    EXPECT_NE(loop, new_loop);
+    EXPECT_FALSE(new_loop->HasResults());
+    EXPECT_EQ(0u, new_loop->Exits().Count());
+    EXPECT_NE(nullptr, new_loop->Initializer());
+    EXPECT_NE(loop->Initializer(), new_loop->Initializer());
+
+    EXPECT_NE(nullptr, new_loop->Body());
+    EXPECT_NE(loop->Body(), new_loop->Body());
+
+    EXPECT_NE(nullptr, new_loop->Continuing());
+    EXPECT_NE(loop->Continuing(), new_loop->Continuing());
+}
+
+TEST_F(IR_LoopTest, CloneWithExits) {
+    Loop* new_loop = nullptr;
+    {
+        auto* loop = b.Loop();
+        b.Append(loop->Body(), [&] {
+            auto* if_ = b.If(true);
+            b.Append(if_->True(), [&] { b.Continue(loop); });
+            b.Append(if_->False(), [&] { b.ExitLoop(loop); });
+            b.Append(loop->Continuing(), [&] { b.BreakIf(loop, false); });
+
+            b.NextIteration(loop);
+        });
+        new_loop = clone_ctx.Clone(loop);
+    }
+
+    ASSERT_EQ(2u, new_loop->Body()->Length());
+    EXPECT_TRUE(new_loop->Body()->Front()->Is<If>());
+
+    auto* new_if = new_loop->Body()->Front()->As<If>();
+    ASSERT_EQ(1u, new_if->True()->Length());
+    EXPECT_TRUE(new_if->True()->Front()->Is<Continue>());
+    EXPECT_EQ(new_loop, new_if->True()->Front()->As<Continue>()->Loop());
+
+    ASSERT_EQ(1u, new_if->False()->Length());
+    EXPECT_TRUE(new_if->False()->Front()->Is<ExitLoop>());
+    EXPECT_EQ(new_loop, new_if->False()->Front()->As<ExitLoop>()->Loop());
+
+    ASSERT_EQ(1u, new_loop->Continuing()->Length());
+    EXPECT_TRUE(new_loop->Continuing()->Front()->Is<BreakIf>());
+    EXPECT_EQ(new_loop, new_loop->Continuing()->Front()->As<BreakIf>()->Loop());
+
+    EXPECT_TRUE(new_loop->Body()->Back()->Is<NextIteration>());
+    EXPECT_EQ(new_loop, new_loop->Body()->Back()->As<NextIteration>()->Loop());
+}
+
 }  // namespace
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/multi_in_block.cc b/src/tint/lang/core/ir/multi_in_block.cc
index 6916512..676691b 100644
--- a/src/tint/lang/core/ir/multi_in_block.cc
+++ b/src/tint/lang/core/ir/multi_in_block.cc
@@ -14,6 +14,9 @@
 
 #include "src/tint/lang/core/ir/multi_in_block.h"
 
+#include "src/tint/lang/core/ir/block_param.h"
+#include "src/tint/lang/core/ir/clone_context.h"
+#include "src/tint/lang/core/ir/module.h"
 #include "src/tint/utils/containers/predicates.h"
 #include "src/tint/utils/ice/ice.h"
 
@@ -25,6 +28,19 @@
 
 MultiInBlock::~MultiInBlock() = default;
 
+MultiInBlock* MultiInBlock::Clone(CloneContext&) {
+    TINT_UNREACHABLE() << "blocks must be cloned with CloneInto";
+    return nullptr;
+}
+
+void MultiInBlock::CloneInto(CloneContext& ctx, Block* out) {
+    TINT_ASSERT(out->Is<MultiInBlock>());
+
+    auto new_params = ctx.Clone(params_);
+    out->As<MultiInBlock>()->SetParams(new_params);
+    Block::CloneInto(ctx, out);
+}
+
 void MultiInBlock::SetParams(VectorRef<BlockParam*> params) {
     params_ = std::move(params);
 }
diff --git a/src/tint/lang/core/ir/multi_in_block.h b/src/tint/lang/core/ir/multi_in_block.h
index 1fecb6d..5934dca 100644
--- a/src/tint/lang/core/ir/multi_in_block.h
+++ b/src/tint/lang/core/ir/multi_in_block.h
@@ -35,6 +35,12 @@
     MultiInBlock();
     ~MultiInBlock() override;
 
+    /// @copydoc Block::Clone()
+    MultiInBlock* Clone(CloneContext& ctx) override;
+
+    /// @copydoc Block::CloneInto()
+    void CloneInto(CloneContext& ctx, Block* out) override;
+
     /// Sets the params to the block
     /// @param params the params for the block
     void SetParams(VectorRef<BlockParam*> params);
diff --git a/src/tint/lang/core/ir/multi_in_block_test.cc b/src/tint/lang/core/ir/multi_in_block_test.cc
index f2145af..2e7552e 100644
--- a/src/tint/lang/core/ir/multi_in_block_test.cc
+++ b/src/tint/lang/core/ir/multi_in_block_test.cc
@@ -35,5 +35,43 @@
         "");
 }
 
+TEST_F(IR_MultiInBlockTest, CloneInto) {
+    auto* loop = b.Loop();
+
+    auto* blk = b.MultiInBlock();
+    auto* add = b.Add(mod.Types().i32(), 1_i, 2_i);
+    blk->Append(add);
+    blk->SetParams({b.BlockParam(mod.Types().i32()), b.BlockParam(mod.Types().f32())});
+    blk->SetParent(loop);
+
+    auto* terminate = b.TerminateInvocation();
+    blk->AddInboundSiblingBranch(terminate);
+
+    auto* new_blk = b.MultiInBlock();
+    blk->CloneInto(clone_ctx, new_blk);
+
+    EXPECT_EQ(0u, new_blk->InboundSiblingBranches().Length());
+
+    EXPECT_EQ(2u, new_blk->Params().Length());
+    EXPECT_EQ(mod.Types().i32(), new_blk->Params()[0]->Type());
+    EXPECT_EQ(mod.Types().f32(), new_blk->Params()[1]->Type());
+
+    EXPECT_EQ(nullptr, new_blk->Parent());
+
+    EXPECT_EQ(1u, new_blk->Length());
+    EXPECT_NE(add, new_blk->Front());
+    EXPECT_TRUE(new_blk->Front()->Is<Binary>());
+    EXPECT_EQ(Binary::Kind::kAdd, new_blk->Front()->As<Binary>()->Kind());
+}
+
+TEST_F(IR_MultiInBlockTest, CloneEmpty) {
+    auto* blk = b.MultiInBlock();
+    auto* new_blk = b.MultiInBlock();
+    blk->CloneInto(clone_ctx, new_blk);
+
+    EXPECT_EQ(0u, new_blk->InboundSiblingBranches().Length());
+    EXPECT_EQ(0u, new_blk->Params().Length());
+}
+
 }  // namespace
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/next_iteration.cc b/src/tint/lang/core/ir/next_iteration.cc
index 1aeb9f4..1c3fe65 100644
--- a/src/tint/lang/core/ir/next_iteration.cc
+++ b/src/tint/lang/core/ir/next_iteration.cc
@@ -16,7 +16,9 @@
 
 #include <utility>
 
+#include "src/tint/lang/core/ir/clone_context.h"
 #include "src/tint/lang/core/ir/loop.h"
+#include "src/tint/lang/core/ir/module.h"
 #include "src/tint/lang/core/ir/multi_in_block.h"
 #include "src/tint/utils/ice/ice.h"
 
@@ -37,4 +39,10 @@
 
 NextIteration::~NextIteration() = default;
 
+NextIteration* NextIteration::Clone(CloneContext& ctx) {
+    auto* new_loop = ctx.Clone(loop_);
+    auto new_args = ctx.Clone<NextIteration::kDefaultNumOperands>(Args());
+    return ctx.ir.instructions.Create<NextIteration>(new_loop, new_args);
+}
+
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/next_iteration.h b/src/tint/lang/core/ir/next_iteration.h
index 87de579..f845dee 100644
--- a/src/tint/lang/core/ir/next_iteration.h
+++ b/src/tint/lang/core/ir/next_iteration.h
@@ -28,7 +28,7 @@
 namespace tint::core::ir {
 
 /// A next iteration instruction.
-class NextIteration : public Castable<NextIteration, Terminator> {
+class NextIteration final : public Castable<NextIteration, Terminator> {
   public:
     /// The base offset in Operands() for the args
     static constexpr size_t kArgsOperandOffset = 0;
@@ -39,6 +39,9 @@
     explicit NextIteration(ir::Loop* loop, VectorRef<Value*> args = tint::Empty);
     ~NextIteration() override;
 
+    /// @copydoc Instruction::Clone()
+    NextIteration* Clone(CloneContext& ctx) override;
+
     /// @returns the loop being iterated
     ir::Loop* Loop() { return loop_; }
 
diff --git a/src/tint/lang/core/ir/next_iteration_test.cc b/src/tint/lang/core/ir/next_iteration_test.cc
index 3d81331..f1efc9e 100644
--- a/src/tint/lang/core/ir/next_iteration_test.cc
+++ b/src/tint/lang/core/ir/next_iteration_test.cc
@@ -39,5 +39,38 @@
     EXPECT_FALSE(inst->HasMultiResults());
 }
 
+TEST_F(IR_NextIterationTest, Clone) {
+    auto* arg1 = b.Constant(1_u);
+    auto* arg2 = b.Constant(2_u);
+
+    auto* loop = b.Loop();
+    auto* inst = b.NextIteration(loop, arg1, arg2);
+
+    auto* new_loop = clone_ctx.Clone(loop);
+    auto* new_inst = clone_ctx.Clone(inst);
+
+    EXPECT_NE(inst, new_inst);
+    EXPECT_EQ(new_loop, new_inst->Loop());
+
+    auto args = new_inst->Args();
+    EXPECT_EQ(2u, args.Length());
+
+    auto* val0 = args[0]->As<Constant>()->Value();
+    EXPECT_EQ(1_u, val0->As<core::constant::Scalar<u32>>()->ValueAs<u32>());
+
+    auto* val1 = args[1]->As<Constant>()->Value();
+    EXPECT_EQ(2_u, val1->As<core::constant::Scalar<u32>>()->ValueAs<u32>());
+}
+
+TEST_F(IR_NextIterationTest, CloneNoArgs) {
+    auto* loop = b.Loop();
+    auto* inst = b.NextIteration(loop);
+
+    auto* new_loop = clone_ctx.Clone(loop);
+    auto* new_inst = clone_ctx.Clone(inst);
+
+    EXPECT_EQ(new_loop, new_inst->Loop());
+}
+
 }  // namespace
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/operand_instruction.h b/src/tint/lang/core/ir/operand_instruction.h
index 49c2c85..980c696 100644
--- a/src/tint/lang/core/ir/operand_instruction.h
+++ b/src/tint/lang/core/ir/operand_instruction.h
@@ -134,6 +134,9 @@
     Vector<ir::Value*, N> operands_;
     /// The results of this instruction.
     Vector<ir::InstructionResult*, R> results_;
+
+    /// The default number of operands
+    static constexpr size_t kDefaultNumOperands = N;
 };
 
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/return.cc b/src/tint/lang/core/ir/return.cc
index ab6e841..49b5939 100644
--- a/src/tint/lang/core/ir/return.cc
+++ b/src/tint/lang/core/ir/return.cc
@@ -16,7 +16,9 @@
 
 #include <utility>
 
+#include "src/tint/lang/core/ir/clone_context.h"
 #include "src/tint/lang/core/ir/function.h"
+#include "src/tint/lang/core/ir/module.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::core::ir::Return);
 
@@ -33,6 +35,12 @@
 
 Return::~Return() = default;
 
+Return* Return::Clone(CloneContext& ctx) {
+    auto* new_func = ctx.Clone(Func());
+    auto new_val = Value() ? ctx.Clone(Value()) : nullptr;
+    return ctx.ir.instructions.Create<Return>(new_func, new_val);
+}
+
 Function* Return::Func() const {
     return tint::As<Function>(operands_[kFunctionOperandOffset]);
 }
diff --git a/src/tint/lang/core/ir/return.h b/src/tint/lang/core/ir/return.h
index f5b8c8a..0f7c434 100644
--- a/src/tint/lang/core/ir/return.h
+++ b/src/tint/lang/core/ir/return.h
@@ -28,7 +28,7 @@
 namespace tint::core::ir {
 
 /// A return instruction.
-class Return : public Castable<Return, Terminator> {
+class Return final : public Castable<Return, Terminator> {
   public:
     /// The offset in Operands() for the function being returned
     static constexpr size_t kFunctionOperandOffset = 0;
@@ -47,6 +47,9 @@
 
     ~Return() override;
 
+    /// @copydoc Instruction::Clone()
+    Return* Clone(CloneContext& ctx) override;
+
     /// @returns the function being returned
     Function* Func() const;
 
diff --git a/src/tint/lang/core/ir/return_test.cc b/src/tint/lang/core/ir/return_test.cc
index f08c4b5..dc68fd6 100644
--- a/src/tint/lang/core/ir/return_test.cc
+++ b/src/tint/lang/core/ir/return_test.cc
@@ -62,5 +62,33 @@
     }
 }
 
+TEST_F(IR_ReturnTest, Clone) {
+    auto* func = b.Function("func", ty.i32());
+    auto* ret = b.Return(func, b.Constant(1_i));
+
+    auto* new_func = clone_ctx.Clone(func);
+    auto* new_ret = clone_ctx.Clone(ret);
+
+    EXPECT_NE(ret, new_ret);
+    EXPECT_EQ(new_func, new_ret->Func());
+
+    EXPECT_EQ(1u, new_ret->Args().Length());
+
+    auto new_val = new_ret->Value()->As<Constant>()->Value();
+    ASSERT_TRUE(new_val->Is<core::constant::Scalar<i32>>());
+    EXPECT_EQ(1_i, new_val->As<core::constant::Scalar<i32>>()->ValueAs<i32>());
+}
+
+TEST_F(IR_ReturnTest, CloneWithoutArgs) {
+    auto* func = b.Function("func", ty.i32());
+    auto* ret = b.Return(func);
+
+    auto* new_func = clone_ctx.Clone(func);
+    auto* new_ret = clone_ctx.Clone(ret);
+
+    EXPECT_EQ(new_func, new_ret->Func());
+    EXPECT_EQ(nullptr, new_ret->Value());
+}
+
 }  // namespace
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/store.cc b/src/tint/lang/core/ir/store.cc
index c333ede..8088087 100644
--- a/src/tint/lang/core/ir/store.cc
+++ b/src/tint/lang/core/ir/store.cc
@@ -14,6 +14,9 @@
 
 #include "src/tint/lang/core/ir/store.h"
 
+#include "src/tint/lang/core/ir/clone_context.h"
+#include "src/tint/lang/core/ir/module.h"
+
 TINT_INSTANTIATE_TYPEINFO(tint::core::ir::Store);
 
 namespace tint::core::ir {
@@ -27,4 +30,10 @@
 
 Store::~Store() = default;
 
+Store* Store::Clone(CloneContext& ctx) {
+    auto* new_to = ctx.Clone(To());
+    auto* new_from = ctx.Clone(From());
+    return ctx.ir.instructions.Create<Store>(new_to, new_from);
+}
+
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/store.h b/src/tint/lang/core/ir/store.h
index 924a6b8..3c5071e 100644
--- a/src/tint/lang/core/ir/store.h
+++ b/src/tint/lang/core/ir/store.h
@@ -23,7 +23,7 @@
 namespace tint::core::ir {
 
 /// A store instruction in the IR.
-class Store : public Castable<Store, OperandInstruction<2, 0>> {
+class Store final : public Castable<Store, OperandInstruction<2, 0>> {
   public:
     /// The offset in Operands() for the `to` value
     static constexpr size_t kToOperandOffset = 0;
@@ -37,6 +37,9 @@
     Store(Value* to, Value* from);
     ~Store() override;
 
+    /// @copydoc Instruction::Clone()
+    Store* Clone(CloneContext& ctx) override;
+
     /// @returns the value being stored too
     Value* To() { return operands_[kToOperandOffset]; }
 
diff --git a/src/tint/lang/core/ir/store_test.cc b/src/tint/lang/core/ir/store_test.cc
index f13049b..c5677f0 100644
--- a/src/tint/lang/core/ir/store_test.cc
+++ b/src/tint/lang/core/ir/store_test.cc
@@ -58,5 +58,20 @@
     EXPECT_FALSE(inst->HasMultiResults());
 }
 
+TEST_F(IR_StoreTest, Clone) {
+    auto* v = b.Var("a", mod.Types().ptr<private_, i32>());
+    auto* s = b.Store(v, b.Constant(1_i));
+
+    auto* new_v = clone_ctx.Clone(v);
+    auto* new_s = clone_ctx.Clone(s);
+
+    EXPECT_NE(s, new_s);
+    EXPECT_EQ(new_v->Result(), new_s->To());
+
+    auto new_from = new_s->From()->As<Constant>()->Value();
+    ASSERT_TRUE(new_from->Is<core::constant::Scalar<i32>>());
+    EXPECT_EQ(1_i, new_from->As<core::constant::Scalar<i32>>()->ValueAs<i32>());
+}
+
 }  // namespace
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/store_vector_element.cc b/src/tint/lang/core/ir/store_vector_element.cc
index c133211..7d4a45d 100644
--- a/src/tint/lang/core/ir/store_vector_element.cc
+++ b/src/tint/lang/core/ir/store_vector_element.cc
@@ -14,6 +14,9 @@
 
 #include "src/tint/lang/core/ir/store_vector_element.h"
 
+#include "src/tint/lang/core/ir/clone_context.h"
+#include "src/tint/lang/core/ir/module.h"
+
 TINT_INSTANTIATE_TYPEINFO(tint::core::ir::StoreVectorElement);
 
 namespace tint::core::ir {
@@ -28,4 +31,11 @@
 
 StoreVectorElement::~StoreVectorElement() = default;
 
+StoreVectorElement* StoreVectorElement::Clone(CloneContext& ctx) {
+    auto* new_to = ctx.Clone(To());
+    auto* new_idx = ctx.Clone(Index());
+    auto* new_val = ctx.Clone(Value());
+    return ctx.ir.instructions.Create<StoreVectorElement>(new_to, new_idx, new_val);
+}
+
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/store_vector_element.h b/src/tint/lang/core/ir/store_vector_element.h
index a4b31a8..5f89adb 100644
--- a/src/tint/lang/core/ir/store_vector_element.h
+++ b/src/tint/lang/core/ir/store_vector_element.h
@@ -23,7 +23,7 @@
 namespace tint::core::ir {
 
 /// A store instruction for a single vector element in the IR.
-class StoreVectorElement : public Castable<StoreVectorElement, OperandInstruction<3, 0>> {
+class StoreVectorElement final : public Castable<StoreVectorElement, OperandInstruction<3, 0>> {
   public:
     /// The offset in Operands() for the `to` value
     static constexpr size_t kToOperandOffset = 0;
@@ -41,6 +41,9 @@
     StoreVectorElement(ir::Value* to, ir::Value* index, ir::Value* value);
     ~StoreVectorElement() override;
 
+    /// @copydoc Instruction::Clone()
+    StoreVectorElement* Clone(CloneContext& ctx) override;
+
     /// @returns the vector pointer value
     ir::Value* To() { return operands_[kToOperandOffset]; }
 
diff --git a/src/tint/lang/core/ir/store_vector_element_test.cc b/src/tint/lang/core/ir/store_vector_element_test.cc
index 03e147e..28f417e 100644
--- a/src/tint/lang/core/ir/store_vector_element_test.cc
+++ b/src/tint/lang/core/ir/store_vector_element_test.cc
@@ -66,5 +66,24 @@
     EXPECT_FALSE(inst->HasMultiResults());
 }
 
+TEST_F(IR_StoreVectorElementTest, Clone) {
+    auto* to = b.Var(ty.ptr<private_, vec3<i32>>());
+    auto* inst = b.StoreVectorElement(to, 2_i, 4_i);
+
+    auto* new_to = clone_ctx.Clone(to);
+    auto* new_inst = clone_ctx.Clone(inst);
+
+    EXPECT_NE(inst, new_inst);
+    EXPECT_EQ(new_to->Result(), new_inst->To());
+
+    auto new_idx = new_inst->Index()->As<Constant>()->Value();
+    ASSERT_TRUE(new_idx->Is<core::constant::Scalar<i32>>());
+    EXPECT_EQ(2_i, new_idx->As<core::constant::Scalar<i32>>()->ValueAs<i32>());
+
+    auto new_val = new_inst->Value()->As<Constant>()->Value();
+    ASSERT_TRUE(new_val->Is<core::constant::Scalar<i32>>());
+    EXPECT_EQ(4_i, new_val->As<core::constant::Scalar<i32>>()->ValueAs<i32>());
+}
+
 }  // namespace
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/switch.cc b/src/tint/lang/core/ir/switch.cc
index df353ab..d260921 100644
--- a/src/tint/lang/core/ir/switch.cc
+++ b/src/tint/lang/core/ir/switch.cc
@@ -14,6 +14,8 @@
 
 #include "src/tint/lang/core/ir/switch.h"
 
+#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::core::ir::Switch);
@@ -34,4 +36,25 @@
     }
 }
 
+Switch* Switch::Clone(CloneContext& ctx) {
+    auto* new_cond = ctx.Clone(Condition());
+    auto* new_switch = ctx.ir.instructions.Create<Switch>(new_cond);
+    ctx.Replace(this, new_switch);
+
+    new_switch->cases_.Reserve(cases_.Length());
+    for (const auto& cse : cases_) {
+        Switch::Case new_case{};
+        new_case.block = ctx.ir.blocks.Create<ir::Block>();
+        cse.block->CloneInto(ctx, new_case.block);
+
+        new_case.selectors.Reserve(cse.selectors.Length());
+        for (const auto& sel : cse.selectors) {
+            auto* new_val = sel.val ? ctx.Clone(sel.val) : nullptr;
+            new_case.selectors.Push(Switch::CaseSelector{new_val});
+        }
+        new_switch->cases_.Push(new_case);
+    }
+    return new_switch;
+}
+
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/switch.h b/src/tint/lang/core/ir/switch.h
index 1b450ac..44f0e6a 100644
--- a/src/tint/lang/core/ir/switch.h
+++ b/src/tint/lang/core/ir/switch.h
@@ -43,7 +43,7 @@
 ///                            â–¼
 ///                           out
 /// ```
-class Switch : public Castable<Switch, ControlInstruction> {
+class Switch final : public Castable<Switch, ControlInstruction> {
   public:
     /// The offset in Operands() for the condition
     static constexpr size_t kConditionOperandOffset = 0;
@@ -73,6 +73,9 @@
     explicit Switch(Value* cond);
     ~Switch() override;
 
+    /// @copydoc Instruction::Clone()
+    Switch* Clone(CloneContext& ctx) override;
+
     /// @copydoc ControlInstruction::ForeachBlock
     void ForeachBlock(const std::function<void(ir::Block*)>& cb) override;
 
diff --git a/src/tint/lang/core/ir/switch_test.cc b/src/tint/lang/core/ir/switch_test.cc
index 5d1442c..83cc223 100644
--- a/src/tint/lang/core/ir/switch_test.cc
+++ b/src/tint/lang/core/ir/switch_test.cc
@@ -22,6 +22,7 @@
 namespace {
 
 using namespace tint::core::number_suffixes;  // NOLINT
+
 using IR_SwitchTest = IRTestHelper;
 
 TEST_F(IR_SwitchTest, Usage) {
@@ -43,5 +44,62 @@
     EXPECT_THAT(switch_->Cases().Front().Block()->Parent(), switch_);
 }
 
+TEST_F(IR_SwitchTest, Clone) {
+    auto* switch_ = b.Switch(1_i);
+    switch_->Cases().Push(
+        Switch::Case{{Switch::CaseSelector{}, Switch::CaseSelector{b.Constant(2_i)}}, b.Block()});
+    switch_->Cases().Push(Switch::Case{{Switch::CaseSelector{b.Constant(3_i)}}, b.Block()});
+
+    auto* new_switch = clone_ctx.Clone(switch_);
+
+    EXPECT_NE(switch_, new_switch);
+
+    auto new_cond = new_switch->Condition()->As<Constant>()->Value();
+    ASSERT_TRUE(new_cond->Is<core::constant::Scalar<i32>>());
+    EXPECT_EQ(1_i, new_cond->As<core::constant::Scalar<i32>>()->ValueAs<i32>());
+
+    auto& cases = new_switch->Cases();
+    ASSERT_EQ(2u, cases.Length());
+
+    {
+        auto& case1 = cases[0];
+        EXPECT_NE(nullptr, case1.block);
+        EXPECT_NE(switch_->Cases()[0].block, case1.block);
+
+        ASSERT_EQ(2u, case1.selectors.Length());
+        EXPECT_EQ(nullptr, case1.selectors[0].val);
+        auto val = case1.selectors[1].val->Value();
+        ASSERT_TRUE(val->Is<core::constant::Scalar<i32>>());
+        EXPECT_EQ(2_i, val->As<core::constant::Scalar<i32>>()->ValueAs<i32>());
+    }
+
+    {
+        auto& case2 = cases[1];
+        EXPECT_NE(nullptr, case2.block);
+        EXPECT_NE(switch_->Cases()[1].block, case2.block);
+
+        ASSERT_EQ(1u, case2.selectors.Length());
+        auto val = case2.selectors[0].val->Value();
+        ASSERT_TRUE(val->Is<core::constant::Scalar<i32>>());
+        EXPECT_EQ(3_i, val->As<core::constant::Scalar<i32>>()->ValueAs<i32>());
+    }
+}
+
+TEST_F(IR_SwitchTest, CloneWithExits) {
+    Switch* new_switch = nullptr;
+    {
+        auto* switch_ = b.Switch(1_i);
+
+        auto* blk = b.Block();
+        b.Append(blk, [&] { b.ExitSwitch(switch_); });
+        switch_->Cases().Push(Switch::Case{{Switch::CaseSelector{b.Constant(3_i)}}, blk});
+        new_switch = clone_ctx.Clone(switch_);
+    }
+
+    auto& case_ = new_switch->Cases().Front();
+    ASSERT_TRUE(case_.block->Front()->Is<ExitSwitch>());
+    EXPECT_EQ(new_switch, case_.block->Front()->As<ExitSwitch>()->Switch());
+}
+
 }  // namespace
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/swizzle.cc b/src/tint/lang/core/ir/swizzle.cc
index 3789021..0180d48 100644
--- a/src/tint/lang/core/ir/swizzle.cc
+++ b/src/tint/lang/core/ir/swizzle.cc
@@ -16,6 +16,8 @@
 
 #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::core::ir::Swizzle);
@@ -37,4 +39,10 @@
 
 Swizzle::~Swizzle() = default;
 
+Swizzle* Swizzle::Clone(CloneContext& ctx) {
+    auto* result = ctx.Clone(Result());
+    auto* new_obj = ctx.Clone(Object());
+    return ctx.ir.instructions.Create<Swizzle>(result, new_obj, indices_);
+}
+
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/swizzle.h b/src/tint/lang/core/ir/swizzle.h
index 48a838e..880e5c6 100644
--- a/src/tint/lang/core/ir/swizzle.h
+++ b/src/tint/lang/core/ir/swizzle.h
@@ -23,7 +23,7 @@
 namespace tint::core::ir {
 
 /// A swizzle instruction in the IR.
-class Swizzle : public Castable<Swizzle, OperandInstruction<1, 1>> {
+class Swizzle final : public Castable<Swizzle, OperandInstruction<1, 1>> {
   public:
     /// The offset in Operands() for the object being swizzled
     static constexpr size_t kObjectOperandOffset = 0;
@@ -35,6 +35,9 @@
     Swizzle(InstructionResult* result, Value* object, VectorRef<uint32_t> indices);
     ~Swizzle() override;
 
+    /// @copydoc Instruction::Clone()
+    Swizzle* Clone(CloneContext& ctx) override;
+
     /// @returns the object used for the access
     Value* Object() { return operands_[kObjectOperandOffset]; }
 
diff --git a/src/tint/lang/core/ir/swizzle_test.cc b/src/tint/lang/core/ir/swizzle_test.cc
index 3f435ef..09eb7e9 100644
--- a/src/tint/lang/core/ir/swizzle_test.cc
+++ b/src/tint/lang/core/ir/swizzle_test.cc
@@ -86,5 +86,22 @@
         "");
 }
 
+TEST_F(IR_SwizzleTest, Clone) {
+    auto* var = b.Var(ty.ptr<function, i32>());
+    auto* s = b.Swizzle(mod.Types().i32(), var, {2u});
+
+    auto* new_var = clone_ctx.Clone(var);
+    auto* new_s = clone_ctx.Clone(s);
+
+    EXPECT_NE(s, new_s);
+    EXPECT_NE(nullptr, new_s->Result());
+    EXPECT_NE(s->Result(), new_s->Result());
+
+    EXPECT_EQ(new_var->Result(), new_s->Object());
+
+    EXPECT_EQ(1u, new_s->Indices().Length());
+    EXPECT_EQ(2u, new_s->Indices().Front());
+}
+
 }  // namespace
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/terminate_invocation.cc b/src/tint/lang/core/ir/terminate_invocation.cc
index a7ab28e..9fa5b54 100644
--- a/src/tint/lang/core/ir/terminate_invocation.cc
+++ b/src/tint/lang/core/ir/terminate_invocation.cc
@@ -14,10 +14,17 @@
 
 #include "src/tint/lang/core/ir/terminate_invocation.h"
 
+#include "src/tint/lang/core/ir/clone_context.h"
+#include "src/tint/lang/core/ir/module.h"
+
 TINT_INSTANTIATE_TYPEINFO(tint::core::ir::TerminateInvocation);
 
 namespace tint::core::ir {
 
 TerminateInvocation::~TerminateInvocation() = default;
 
+TerminateInvocation* TerminateInvocation::Clone(CloneContext& ctx) {
+    return ctx.ir.instructions.Create<TerminateInvocation>();
+}
+
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/terminate_invocation.h b/src/tint/lang/core/ir/terminate_invocation.h
index 8edf03e..097145c 100644
--- a/src/tint/lang/core/ir/terminate_invocation.h
+++ b/src/tint/lang/core/ir/terminate_invocation.h
@@ -22,10 +22,13 @@
 namespace tint::core::ir {
 
 /// An terminate invocation instruction in the IR.
-class TerminateInvocation : public Castable<TerminateInvocation, Terminator> {
+class TerminateInvocation final : public Castable<TerminateInvocation, Terminator> {
   public:
     ~TerminateInvocation() override;
 
+    /// @copydoc Instruction::Clone()
+    TerminateInvocation* Clone(CloneContext& ctx) override;
+
     /// @returns the friendly name for the instruction
     std::string FriendlyName() override { return "terminate_invocation"; }
 };
diff --git a/src/tint/lang/core/ir/intrinsic_call.cc b/src/tint/lang/core/ir/terminate_invocation_test.cc
similarity index 61%
copy from src/tint/lang/core/ir/intrinsic_call.cc
copy to src/tint/lang/core/ir/terminate_invocation_test.cc
index a31ab1d..459aae0 100644
--- a/src/tint/lang/core/ir/intrinsic_call.cc
+++ b/src/tint/lang/core/ir/terminate_invocation_test.cc
@@ -12,19 +12,23 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "src/tint/lang/core/ir/intrinsic_call.h"
-
-#include <utility>
-
-TINT_INSTANTIATE_TYPEINFO(tint::core::ir::IntrinsicCall);
+#include "src/tint/lang/core/ir/terminate_invocation.h"
+#include "gtest/gtest.h"
+#include "src/tint/lang/core/ir/ir_helper_test.h"
 
 namespace tint::core::ir {
+namespace {
 
-IntrinsicCall::IntrinsicCall(InstructionResult* result, VectorRef<Value*> arguments) {
-    AddOperands(IntrinsicCall::kArgsOperandOffset, std::move(arguments));
-    AddResult(result);
+using IR_TerminateInvocationTest = IRTestHelper;
+
+TEST_F(IR_TerminateInvocationTest, Clone) {
+    auto* ti = b.TerminateInvocation();
+    auto* new_ti = clone_ctx.Clone(ti);
+
+    EXPECT_NE(ti, new_ti);
+    EXPECT_NE(nullptr, new_ti);
+    EXPECT_NE(ti, new_ti);
 }
 
-IntrinsicCall::~IntrinsicCall() = default;
-
+}  // namespace
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/transform/bgra8unorm_polyfill.cc b/src/tint/lang/core/ir/transform/bgra8unorm_polyfill.cc
index d4fcbf2..4acfbfb 100644
--- a/src/tint/lang/core/ir/transform/bgra8unorm_polyfill.cc
+++ b/src/tint/lang/core/ir/transform/bgra8unorm_polyfill.cc
@@ -140,7 +140,7 @@
                 [&](CoreBuiltinCall* call) {
                     // Replace arguments to builtin functions and add swizzles if necessary.
                     call->SetOperand(use.operand_index, new_value);
-                    if (call->Func() == core::Function::kTextureStore) {
+                    if (call->Func() == core::BuiltinFn::kTextureStore) {
                         // Swizzle the value argument of a `textureStore()` builtin.
                         auto* tex = old_value->Type()->As<core::type::StorageTexture>();
                         auto index = core::type::IsTextureArray(tex->dim()) ? 3u : 2u;
@@ -148,7 +148,7 @@
                         auto* swizzle = b.Swizzle(value->Type(), value, Vector{2u, 1u, 0u, 3u});
                         swizzle->InsertBefore(call);
                         call->SetOperand(index, swizzle->Result());
-                    } else if (call->Func() == core::Function::kTextureLoad) {
+                    } else if (call->Func() == core::BuiltinFn::kTextureLoad) {
                         // Swizzle the result of a `textureLoad()` builtin.
                         auto* swizzle =
                             b.Swizzle(call->Result()->Type(), nullptr, Vector{2u, 1u, 0u, 3u});
diff --git a/src/tint/lang/core/ir/transform/bgra8unorm_polyfill_test.cc b/src/tint/lang/core/ir/transform/bgra8unorm_polyfill_test.cc
index e8d19a3..aab9aa5 100644
--- a/src/tint/lang/core/ir/transform/bgra8unorm_polyfill_test.cc
+++ b/src/tint/lang/core/ir/transform/bgra8unorm_polyfill_test.cc
@@ -60,7 +60,7 @@
     func->SetParams({value, coords});
     b.Append(func->Block(), [&] {
         auto* load = b.Load(var->Result());
-        b.Call(ty.void_(), core::Function::kTextureStore, load, coords, value);
+        b.Call(ty.void_(), core::BuiltinFn::kTextureStore, load, coords, value);
         b.Return(func);
     });
 
@@ -97,7 +97,7 @@
     auto* value = b.FunctionParam("value", ty.vec4<f32>());
     func->SetParams({texture, coords, value});
     b.Append(func->Block(), [&] {
-        b.Call(ty.void_(), core::Function::kTextureStore, texture, coords, value);
+        b.Call(ty.void_(), core::BuiltinFn::kTextureStore, texture, coords, value);
         b.Return(func);
     });
 
@@ -133,7 +133,7 @@
     func->SetParams({value, coords});
     b.Append(func->Block(), [&] {
         auto* load = b.Load(var->Result());
-        b.Call(ty.void_(), core::Function::kTextureStore, load, coords, value);
+        b.Call(ty.void_(), core::BuiltinFn::kTextureStore, load, coords, value);
         b.Return(func);
     });
 
@@ -183,7 +183,7 @@
     auto* value = b.FunctionParam("value", ty.vec4<f32>());
     func->SetParams({texture, coords, value});
     b.Append(func->Block(), [&] {
-        b.Call(ty.void_(), core::Function::kTextureStore, texture, coords, value);
+        b.Call(ty.void_(), core::BuiltinFn::kTextureStore, texture, coords, value);
         b.Return(func);
     });
 
@@ -228,7 +228,7 @@
         auto* value = b.FunctionParam("value", ty.vec4<f32>());
         bar->SetParams({texture, coords, value});
         b.Append(bar->Block(), [&] {
-            b.Call(ty.void_(), core::Function::kTextureStore, texture, coords, value);
+            b.Call(ty.void_(), core::BuiltinFn::kTextureStore, texture, coords, value);
             b.Return(bar);
         });
     }
@@ -316,9 +316,9 @@
         auto* value = b.FunctionParam("value", ty.vec4<f32>());
         bar->SetParams({texture_a, texture_b, texture_c, coords, value});
         b.Append(bar->Block(), [&] {
-            b.Call(ty.void_(), core::Function::kTextureStore, texture_a, coords, value);
-            b.Call(ty.void_(), core::Function::kTextureStore, texture_b, coords, value);
-            b.Call(ty.void_(), core::Function::kTextureStore, texture_c, coords, value);
+            b.Call(ty.void_(), core::BuiltinFn::kTextureStore, texture_a, coords, value);
+            b.Call(ty.void_(), core::BuiltinFn::kTextureStore, texture_b, coords, value);
+            b.Call(ty.void_(), core::BuiltinFn::kTextureStore, texture_c, coords, value);
             b.Return(bar);
         });
     }
@@ -414,9 +414,9 @@
         auto* value = b.FunctionParam("value", ty.vec4<f32>());
         bar->SetParams({texture, coords, value});
         b.Append(bar->Block(), [&] {
-            b.Call(ty.void_(), core::Function::kTextureStore, texture, coords, value);
-            b.Call(ty.void_(), core::Function::kTextureStore, texture, coords, value);
-            b.Call(ty.void_(), core::Function::kTextureStore, texture, coords, value);
+            b.Call(ty.void_(), core::BuiltinFn::kTextureStore, texture, coords, value);
+            b.Call(ty.void_(), core::BuiltinFn::kTextureStore, texture, coords, value);
+            b.Call(ty.void_(), core::BuiltinFn::kTextureStore, texture, coords, value);
             b.Return(bar);
         });
     }
@@ -428,7 +428,7 @@
         foo->SetParams({coords, value});
         b.Append(foo->Block(), [&] {
             auto* load_a = b.Load(var_a->Result());
-            b.Call(ty.void_(), core::Function::kTextureStore, load_a, coords, value);
+            b.Call(ty.void_(), core::BuiltinFn::kTextureStore, load_a, coords, value);
             auto* load_b = b.Load(var_a->Result());
             b.Call(ty.void_(), bar, load_b, coords, value);
             auto* load_c = b.Load(var_a->Result());
@@ -515,7 +515,7 @@
     func->SetParams({value, coords});
     b.Append(func->Block(), [&] {
         auto* load = b.Load(var->Result());
-        b.Call(ty.void_(), core::Function::kTextureStore, load, coords, index, value);
+        b.Call(ty.void_(), core::BuiltinFn::kTextureStore, load, coords, index, value);
         b.Return(func);
     });
 
@@ -566,7 +566,7 @@
     auto* func = b.Function("foo", ty.vec2<u32>());
     b.Append(func->Block(), [&] {
         auto* load = b.Load(var->Result());
-        auto* dims = b.Call(ty.vec2<u32>(), core::Function::kTextureDimensions, load);
+        auto* dims = b.Call(ty.vec2<u32>(), core::BuiltinFn::kTextureDimensions, load);
         b.Return(func, dims);
         mod.SetName(dims, "dims");
     });
@@ -619,7 +619,7 @@
     func->SetParams({coords});
     b.Append(func->Block(), [&] {
         auto* load = b.Load(var->Result());
-        auto* result = b.Call(ty.vec4<f32>(), core::Function::kTextureLoad, load, coords);
+        auto* result = b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureLoad, load, coords);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -673,8 +673,8 @@
     func->SetParams({coords});
     b.Append(func->Block(), [&] {
         auto* load = b.Load(var->Result());
-        auto* result = b.Call(ty.vec4<f32>(), core::Function::kTextureLoad, load, coords);
-        b.Call(ty.void_(), core::Function::kTextureStore, load, coords, result);
+        auto* result = b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureLoad, load, coords);
+        b.Call(ty.void_(), core::BuiltinFn::kTextureStore, load, coords, result);
         b.Return(func);
         mod.SetName(result, "result");
     });
diff --git a/src/tint/lang/core/ir/transform/binary_polyfill.cc b/src/tint/lang/core/ir/transform/binary_polyfill.cc
index fcf6b7e..e4bed28 100644
--- a/src/tint/lang/core/ir/transform/binary_polyfill.cc
+++ b/src/tint/lang/core/ir/transform/binary_polyfill.cc
@@ -180,7 +180,7 @@
                     auto* rhs_is_minus_one = b.Equal(bool_ty, rhs, minus_one);
                     cond = b.Or(bool_ty, cond, b.And(bool_ty, lhs_is_lowest, rhs_is_minus_one));
                 }
-                auto* rhs_or_one = b.Call(result_ty, core::Function::kSelect, rhs, one, cond);
+                auto* rhs_or_one = b.Call(result_ty, core::BuiltinFn::kSelect, rhs, one, cond);
 
                 if (binary->Kind() == Binary::Kind::kDivide) {
                     // Perform the divide with the modified RHS.
diff --git a/src/tint/lang/core/ir/transform/builtin_polyfill.cc b/src/tint/lang/core/ir/transform/builtin_polyfill.cc
index d8dc094..e5d13d8 100644
--- a/src/tint/lang/core/ir/transform/builtin_polyfill.cc
+++ b/src/tint/lang/core/ir/transform/builtin_polyfill.cc
@@ -55,32 +55,32 @@
             }
             if (auto* builtin = inst->As<ir::CoreBuiltinCall>()) {
                 switch (builtin->Func()) {
-                    case core::Function::kCountLeadingZeros:
+                    case core::BuiltinFn::kCountLeadingZeros:
                         if (config.count_leading_zeros) {
                             worklist.Push(builtin);
                         }
                         break;
-                    case core::Function::kCountTrailingZeros:
+                    case core::BuiltinFn::kCountTrailingZeros:
                         if (config.count_trailing_zeros) {
                             worklist.Push(builtin);
                         }
                         break;
-                    case core::Function::kFirstLeadingBit:
+                    case core::BuiltinFn::kFirstLeadingBit:
                         if (config.first_leading_bit) {
                             worklist.Push(builtin);
                         }
                         break;
-                    case core::Function::kFirstTrailingBit:
+                    case core::BuiltinFn::kFirstTrailingBit:
                         if (config.first_trailing_bit) {
                             worklist.Push(builtin);
                         }
                         break;
-                    case core::Function::kSaturate:
+                    case core::BuiltinFn::kSaturate:
                         if (config.saturate) {
                             worklist.Push(builtin);
                         }
                         break;
-                    case core::Function::kTextureSampleBaseClampToEdge:
+                    case core::BuiltinFn::kTextureSampleBaseClampToEdge:
                         if (config.texture_sample_base_clamp_to_edge_2d_f32) {
                             auto* tex =
                                 builtin->Args()[0]->Type()->As<core::type::SampledTexture>();
@@ -100,22 +100,22 @@
         for (auto* builtin : worklist) {
             ir::Value* replacement = nullptr;
             switch (builtin->Func()) {
-                case core::Function::kCountLeadingZeros:
+                case core::BuiltinFn::kCountLeadingZeros:
                     replacement = CountLeadingZeros(builtin);
                     break;
-                case core::Function::kCountTrailingZeros:
+                case core::BuiltinFn::kCountTrailingZeros:
                     replacement = CountTrailingZeros(builtin);
                     break;
-                case core::Function::kFirstLeadingBit:
+                case core::BuiltinFn::kFirstLeadingBit:
                     replacement = FirstLeadingBit(builtin);
                     break;
-                case core::Function::kFirstTrailingBit:
+                case core::BuiltinFn::kFirstTrailingBit:
                     replacement = FirstTrailingBit(builtin);
                     break;
-                case core::Function::kSaturate:
+                case core::BuiltinFn::kSaturate:
                     replacement = Saturate(builtin);
                     break;
-                case core::Function::kTextureSampleBaseClampToEdge:
+                case core::BuiltinFn::kTextureSampleBaseClampToEdge:
                     replacement = TextureSampleBaseClampToEdge_2d_f32(builtin);
                     break;
                 default:
@@ -191,22 +191,22 @@
             if (result_ty->is_signed_integer_scalar_or_vector()) {
                 x = b.Bitcast(uint_ty, x)->Result();
             }
-            auto* b16 = b.Call(uint_ty, core::Function::kSelect, V(0), V(16),
+            auto* b16 = b.Call(uint_ty, core::BuiltinFn::kSelect, V(0), V(16),
                                b.LessThanEqual(bool_ty, x, V(0x0000ffff)));
             x = b.ShiftLeft(uint_ty, x, b16)->Result();
-            auto* b8 = b.Call(uint_ty, core::Function::kSelect, V(0), V(8),
+            auto* b8 = b.Call(uint_ty, core::BuiltinFn::kSelect, V(0), V(8),
                               b.LessThanEqual(bool_ty, x, V(0x00ffffff)));
             x = b.ShiftLeft(uint_ty, x, b8)->Result();
-            auto* b4 = b.Call(uint_ty, core::Function::kSelect, V(0), V(4),
+            auto* b4 = b.Call(uint_ty, core::BuiltinFn::kSelect, V(0), V(4),
                               b.LessThanEqual(bool_ty, x, V(0x0fffffff)));
             x = b.ShiftLeft(uint_ty, x, b4)->Result();
-            auto* b2 = b.Call(uint_ty, core::Function::kSelect, V(0), V(2),
+            auto* b2 = b.Call(uint_ty, core::BuiltinFn::kSelect, V(0), V(2),
                               b.LessThanEqual(bool_ty, x, V(0x3fffffff)));
             x = b.ShiftLeft(uint_ty, x, b2)->Result();
-            auto* b1 = b.Call(uint_ty, core::Function::kSelect, V(0), V(1),
+            auto* b1 = b.Call(uint_ty, core::BuiltinFn::kSelect, V(0), V(1),
                               b.LessThanEqual(bool_ty, x, V(0x7fffffff)));
             auto* b0 =
-                b.Call(uint_ty, core::Function::kSelect, V(0), V(1), b.Equal(bool_ty, x, V(0)));
+                b.Call(uint_ty, core::BuiltinFn::kSelect, V(0), V(1), b.Equal(bool_ty, x, V(0)));
             result = b.Add(uint_ty,
                            b.Or(uint_ty, b16,
                                 b.Or(uint_ty, b8,
@@ -254,22 +254,22 @@
             if (result_ty->is_signed_integer_scalar_or_vector()) {
                 x = b.Bitcast(uint_ty, x)->Result();
             }
-            auto* b16 = b.Call(uint_ty, core::Function::kSelect, V(0), V(16),
+            auto* b16 = b.Call(uint_ty, core::BuiltinFn::kSelect, V(0), V(16),
                                b.Equal(bool_ty, b.And(uint_ty, x, V(0x0000ffff)), V(0)));
             x = b.ShiftRight(uint_ty, x, b16)->Result();
-            auto* b8 = b.Call(uint_ty, core::Function::kSelect, V(0), V(8),
+            auto* b8 = b.Call(uint_ty, core::BuiltinFn::kSelect, V(0), V(8),
                               b.Equal(bool_ty, b.And(uint_ty, x, V(0x000000ff)), V(0)));
             x = b.ShiftRight(uint_ty, x, b8)->Result();
-            auto* b4 = b.Call(uint_ty, core::Function::kSelect, V(0), V(4),
+            auto* b4 = b.Call(uint_ty, core::BuiltinFn::kSelect, V(0), V(4),
                               b.Equal(bool_ty, b.And(uint_ty, x, V(0x0000000f)), V(0)));
             x = b.ShiftRight(uint_ty, x, b4)->Result();
-            auto* b2 = b.Call(uint_ty, core::Function::kSelect, V(0), V(2),
+            auto* b2 = b.Call(uint_ty, core::BuiltinFn::kSelect, V(0), V(2),
                               b.Equal(bool_ty, b.And(uint_ty, x, V(0x00000003)), V(0)));
             x = b.ShiftRight(uint_ty, x, b2)->Result();
-            auto* b1 = b.Call(uint_ty, core::Function::kSelect, V(0), V(1),
+            auto* b1 = b.Call(uint_ty, core::BuiltinFn::kSelect, V(0), V(1),
                               b.Equal(bool_ty, b.And(uint_ty, x, V(0x00000001)), V(0)));
             auto* b0 =
-                b.Call(uint_ty, core::Function::kSelect, V(0), V(1), b.Equal(bool_ty, x, V(0)));
+                b.Call(uint_ty, core::BuiltinFn::kSelect, V(0), V(1), b.Equal(bool_ty, x, V(0)));
             result = b.Add(uint_ty,
                            b.Or(uint_ty, b16,
                                 b.Or(uint_ty, b8, b.Or(uint_ty, b4, b.Or(uint_ty, b2, b1)))),
@@ -316,27 +316,27 @@
             if (result_ty->is_signed_integer_scalar_or_vector()) {
                 x = b.Bitcast(uint_ty, x)->Result();
                 auto* inverted = b.Complement(uint_ty, x);
-                x = b.Call(uint_ty, core::Function::kSelect, inverted, x,
+                x = b.Call(uint_ty, core::BuiltinFn::kSelect, inverted, x,
                            b.LessThan(bool_ty, x, V(0x80000000)))
                         ->Result();
             }
-            auto* b16 = b.Call(uint_ty, core::Function::kSelect, V(16), V(0),
+            auto* b16 = b.Call(uint_ty, core::BuiltinFn::kSelect, V(16), V(0),
                                b.Equal(bool_ty, b.And(uint_ty, x, V(0xffff0000)), V(0)));
             x = b.ShiftRight(uint_ty, x, b16)->Result();
-            auto* b8 = b.Call(uint_ty, core::Function::kSelect, V(8), V(0),
+            auto* b8 = b.Call(uint_ty, core::BuiltinFn::kSelect, V(8), V(0),
                               b.Equal(bool_ty, b.And(uint_ty, x, V(0x0000ff00)), V(0)));
             x = b.ShiftRight(uint_ty, x, b8)->Result();
-            auto* b4 = b.Call(uint_ty, core::Function::kSelect, V(4), V(0),
+            auto* b4 = b.Call(uint_ty, core::BuiltinFn::kSelect, V(4), V(0),
                               b.Equal(bool_ty, b.And(uint_ty, x, V(0x000000f0)), V(0)));
             x = b.ShiftRight(uint_ty, x, b4)->Result();
-            auto* b2 = b.Call(uint_ty, core::Function::kSelect, V(2), V(0),
+            auto* b2 = b.Call(uint_ty, core::BuiltinFn::kSelect, V(2), V(0),
                               b.Equal(bool_ty, b.And(uint_ty, x, V(0x0000000c)), V(0)));
             x = b.ShiftRight(uint_ty, x, b2)->Result();
-            auto* b1 = b.Call(uint_ty, core::Function::kSelect, V(1), V(0),
+            auto* b1 = b.Call(uint_ty, core::BuiltinFn::kSelect, V(1), V(0),
                               b.Equal(bool_ty, b.And(uint_ty, x, V(0x00000002)), V(0)));
             result = b.Or(uint_ty, b16, b.Or(uint_ty, b8, b.Or(uint_ty, b4, b.Or(uint_ty, b2, b1))))
                          ->Result();
-            result = b.Call(uint_ty, core::Function::kSelect, result, V(0xffffffff),
+            result = b.Call(uint_ty, core::BuiltinFn::kSelect, result, V(0xffffffff),
                             b.Equal(bool_ty, x, V(0)))
                          ->Result();
             if (result_ty->is_signed_integer_scalar_or_vector()) {
@@ -380,23 +380,23 @@
             if (result_ty->is_signed_integer_scalar_or_vector()) {
                 x = b.Bitcast(uint_ty, x)->Result();
             }
-            auto* b16 = b.Call(uint_ty, core::Function::kSelect, V(0), V(16),
+            auto* b16 = b.Call(uint_ty, core::BuiltinFn::kSelect, V(0), V(16),
                                b.Equal(bool_ty, b.And(uint_ty, x, V(0x0000ffff)), V(0)));
             x = b.ShiftRight(uint_ty, x, b16)->Result();
-            auto* b8 = b.Call(uint_ty, core::Function::kSelect, V(0), V(8),
+            auto* b8 = b.Call(uint_ty, core::BuiltinFn::kSelect, V(0), V(8),
                               b.Equal(bool_ty, b.And(uint_ty, x, V(0x000000ff)), V(0)));
             x = b.ShiftRight(uint_ty, x, b8)->Result();
-            auto* b4 = b.Call(uint_ty, core::Function::kSelect, V(0), V(4),
+            auto* b4 = b.Call(uint_ty, core::BuiltinFn::kSelect, V(0), V(4),
                               b.Equal(bool_ty, b.And(uint_ty, x, V(0x0000000f)), V(0)));
             x = b.ShiftRight(uint_ty, x, b4)->Result();
-            auto* b2 = b.Call(uint_ty, core::Function::kSelect, V(0), V(2),
+            auto* b2 = b.Call(uint_ty, core::BuiltinFn::kSelect, V(0), V(2),
                               b.Equal(bool_ty, b.And(uint_ty, x, V(0x00000003)), V(0)));
             x = b.ShiftRight(uint_ty, x, b2)->Result();
-            auto* b1 = b.Call(uint_ty, core::Function::kSelect, V(0), V(1),
+            auto* b1 = b.Call(uint_ty, core::BuiltinFn::kSelect, V(0), V(1),
                               b.Equal(bool_ty, b.And(uint_ty, x, V(0x00000001)), V(0)));
             result = b.Or(uint_ty, b16, b.Or(uint_ty, b8, b.Or(uint_ty, b4, b.Or(uint_ty, b2, b1))))
                          ->Result();
-            result = b.Call(uint_ty, core::Function::kSelect, result, V(0xffffffff),
+            result = b.Call(uint_ty, core::BuiltinFn::kSelect, result, V(0xffffffff),
                             b.Equal(bool_ty, x, V(0)))
                          ->Result();
             if (result_ty->is_signed_integer_scalar_or_vector()) {
@@ -421,7 +421,7 @@
             zero = MatchWidth(b.Constant(0_h), type);
             one = MatchWidth(b.Constant(1_h), type);
         }
-        auto* clamp = b.Call(type, core::Function::kClamp, Vector{call->Args()[0], zero, one});
+        auto* clamp = b.Call(type, core::BuiltinFn::kClamp, Vector{call->Args()[0], zero, one});
         clamp->InsertBefore(call);
         return clamp->Result();
     }
@@ -441,13 +441,13 @@
         auto* coords = call->Args()[2];
         b.InsertBefore(call, [&] {
             auto* vec2f = ty.vec2<f32>();
-            auto* dims = b.Call(ty.vec2<u32>(), core::Function::kTextureDimensions, texture);
+            auto* dims = b.Call(ty.vec2<u32>(), core::BuiltinFn::kTextureDimensions, texture);
             auto* fdims = b.Convert(vec2f, dims);
             auto* half_texel = b.Divide(vec2f, b.Splat(vec2f, 0.5_f, 2), fdims);
             auto* one_minus_half_texel = b.Subtract(vec2f, b.Splat(vec2f, 1_f, 2), half_texel);
             auto* clamped =
-                b.Call(vec2f, core::Function::kClamp, coords, half_texel, one_minus_half_texel);
-            result = b.Call(ty.vec4<f32>(), core::Function::kTextureSampleLevel, texture, sampler,
+                b.Call(vec2f, core::BuiltinFn::kClamp, coords, half_texel, one_minus_half_texel);
+            result = b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureSampleLevel, texture, sampler,
                             clamped, 0_f)
                          ->Result();
         });
diff --git a/src/tint/lang/core/ir/transform/builtin_polyfill_test.cc b/src/tint/lang/core/ir/transform/builtin_polyfill_test.cc
index a89ed53..869c89c 100644
--- a/src/tint/lang/core/ir/transform/builtin_polyfill_test.cc
+++ b/src/tint/lang/core/ir/transform/builtin_polyfill_test.cc
@@ -31,7 +31,7 @@
     /// @param builtin the builtin to call
     /// @param result_ty the result type of the builtin call
     /// @param arg_types the arguments types for the builtin call
-    void Build(core::Function builtin,
+    void Build(core::BuiltinFn builtin,
                const core::type::Type* result_ty,
                VectorRef<const core::type::Type*> arg_types) {
         Vector<FunctionParam*, 4> args;
@@ -49,7 +49,7 @@
 };
 
 TEST_F(IR_BuiltinPolyfillTest, Saturate_NoPolyfill) {
-    Build(core::Function::kSaturate, ty.f32(), Vector{ty.f32()});
+    Build(core::BuiltinFn::kSaturate, ty.f32(), Vector{ty.f32()});
     auto* src = R"(
 %foo = func(%arg:f32):f32 -> %b1 {
   %b1 = block {
@@ -69,7 +69,7 @@
 }
 
 TEST_F(IR_BuiltinPolyfillTest, Saturate_F32) {
-    Build(core::Function::kSaturate, ty.f32(), Vector{ty.f32()});
+    Build(core::BuiltinFn::kSaturate, ty.f32(), Vector{ty.f32()});
     auto* src = R"(
 %foo = func(%arg:f32):f32 -> %b1 {
   %b1 = block {
@@ -96,7 +96,7 @@
 }
 
 TEST_F(IR_BuiltinPolyfillTest, Saturate_F16) {
-    Build(core::Function::kSaturate, ty.f16(), Vector{ty.f16()});
+    Build(core::BuiltinFn::kSaturate, ty.f16(), Vector{ty.f16()});
     auto* src = R"(
 %foo = func(%arg:f16):f16 -> %b1 {
   %b1 = block {
@@ -122,7 +122,7 @@
 }
 
 TEST_F(IR_BuiltinPolyfillTest, Saturate_Vec2F32) {
-    Build(core::Function::kSaturate, ty.vec2<f32>(), Vector{ty.vec2<f32>()});
+    Build(core::BuiltinFn::kSaturate, ty.vec2<f32>(), Vector{ty.vec2<f32>()});
     auto* src = R"(
 %foo = func(%arg:vec2<f32>):vec2<f32> -> %b1 {
   %b1 = block {
@@ -149,7 +149,7 @@
 }
 
 TEST_F(IR_BuiltinPolyfillTest, Saturate_Vec4F16) {
-    Build(core::Function::kSaturate, ty.vec4<f16>(), Vector{ty.vec4<f16>()});
+    Build(core::BuiltinFn::kSaturate, ty.vec4<f16>(), Vector{ty.vec4<f16>()});
     auto* src = R"(
 %foo = func(%arg:vec4<f16>):vec4<f16> -> %b1 {
   %b1 = block {
@@ -176,7 +176,7 @@
 }
 
 TEST_F(IR_BuiltinPolyfillTest, CountLeadingZeros_NoPolyfill) {
-    Build(core::Function::kCountLeadingZeros, ty.u32(), Vector{ty.u32()});
+    Build(core::BuiltinFn::kCountLeadingZeros, ty.u32(), Vector{ty.u32()});
     auto* src = R"(
 %foo = func(%arg:u32):u32 -> %b1 {
   %b1 = block {
@@ -196,7 +196,7 @@
 }
 
 TEST_F(IR_BuiltinPolyfillTest, CountLeadingZeros_U32) {
-    Build(core::Function::kCountLeadingZeros, ty.u32(), Vector{ty.u32()});
+    Build(core::BuiltinFn::kCountLeadingZeros, ty.u32(), Vector{ty.u32()});
     auto* src = R"(
 %foo = func(%arg:u32):u32 -> %b1 {
   %b1 = block {
@@ -244,7 +244,7 @@
 }
 
 TEST_F(IR_BuiltinPolyfillTest, CountLeadingZeros_I32) {
-    Build(core::Function::kCountLeadingZeros, ty.i32(), Vector{ty.i32()});
+    Build(core::BuiltinFn::kCountLeadingZeros, ty.i32(), Vector{ty.i32()});
     auto* src = R"(
 %foo = func(%arg:i32):i32 -> %b1 {
   %b1 = block {
@@ -294,7 +294,7 @@
 }
 
 TEST_F(IR_BuiltinPolyfillTest, CountLeadingZeros_Vec2U32) {
-    Build(core::Function::kCountLeadingZeros, ty.vec2<u32>(), Vector{ty.vec2<u32>()});
+    Build(core::BuiltinFn::kCountLeadingZeros, ty.vec2<u32>(), Vector{ty.vec2<u32>()});
     auto* src = R"(
 %foo = func(%arg:vec2<u32>):vec2<u32> -> %b1 {
   %b1 = block {
@@ -342,7 +342,7 @@
 }
 
 TEST_F(IR_BuiltinPolyfillTest, CountLeadingZeros_Vec4I32) {
-    Build(core::Function::kCountLeadingZeros, ty.vec4<i32>(), Vector{ty.vec4<i32>()});
+    Build(core::BuiltinFn::kCountLeadingZeros, ty.vec4<i32>(), Vector{ty.vec4<i32>()});
     auto* src = R"(
 %foo = func(%arg:vec4<i32>):vec4<i32> -> %b1 {
   %b1 = block {
@@ -392,7 +392,7 @@
 }
 
 TEST_F(IR_BuiltinPolyfillTest, CountTrailingZeros_NoPolyfill) {
-    Build(core::Function::kCountTrailingZeros, ty.u32(), Vector{ty.u32()});
+    Build(core::BuiltinFn::kCountTrailingZeros, ty.u32(), Vector{ty.u32()});
     auto* src = R"(
 %foo = func(%arg:u32):u32 -> %b1 {
   %b1 = block {
@@ -412,7 +412,7 @@
 }
 
 TEST_F(IR_BuiltinPolyfillTest, CountTrailingZeros_U32) {
-    Build(core::Function::kCountTrailingZeros, ty.u32(), Vector{ty.u32()});
+    Build(core::BuiltinFn::kCountTrailingZeros, ty.u32(), Vector{ty.u32()});
     auto* src = R"(
 %foo = func(%arg:u32):u32 -> %b1 {
   %b1 = block {
@@ -464,7 +464,7 @@
 }
 
 TEST_F(IR_BuiltinPolyfillTest, CountTrailingZeros_I32) {
-    Build(core::Function::kCountTrailingZeros, ty.i32(), Vector{ty.i32()});
+    Build(core::BuiltinFn::kCountTrailingZeros, ty.i32(), Vector{ty.i32()});
     auto* src = R"(
 %foo = func(%arg:i32):i32 -> %b1 {
   %b1 = block {
@@ -518,7 +518,7 @@
 }
 
 TEST_F(IR_BuiltinPolyfillTest, CountTrailingZeros_Vec2U32) {
-    Build(core::Function::kCountTrailingZeros, ty.vec2<u32>(), Vector{ty.vec2<u32>()});
+    Build(core::BuiltinFn::kCountTrailingZeros, ty.vec2<u32>(), Vector{ty.vec2<u32>()});
     auto* src = R"(
 %foo = func(%arg:vec2<u32>):vec2<u32> -> %b1 {
   %b1 = block {
@@ -570,7 +570,7 @@
 }
 
 TEST_F(IR_BuiltinPolyfillTest, CountTrailingZeros_Vec4I32) {
-    Build(core::Function::kCountTrailingZeros, ty.vec4<i32>(), Vector{ty.vec4<i32>()});
+    Build(core::BuiltinFn::kCountTrailingZeros, ty.vec4<i32>(), Vector{ty.vec4<i32>()});
     auto* src = R"(
 %foo = func(%arg:vec4<i32>):vec4<i32> -> %b1 {
   %b1 = block {
@@ -597,7 +597,7 @@
 }
 
 TEST_F(IR_BuiltinPolyfillTest, FirstLeadingBit_NoPolyfill) {
-    Build(core::Function::kFirstLeadingBit, ty.u32(), Vector{ty.u32()});
+    Build(core::BuiltinFn::kFirstLeadingBit, ty.u32(), Vector{ty.u32()});
     auto* src = R"(
 %foo = func(%arg:u32):u32 -> %b1 {
   %b1 = block {
@@ -617,7 +617,7 @@
 }
 
 TEST_F(IR_BuiltinPolyfillTest, FirstLeadingBit_U32) {
-    Build(core::Function::kFirstLeadingBit, ty.u32(), Vector{ty.u32()});
+    Build(core::BuiltinFn::kFirstLeadingBit, ty.u32(), Vector{ty.u32()});
     auto* src = R"(
 %foo = func(%arg:u32):u32 -> %b1 {
   %b1 = block {
@@ -668,7 +668,7 @@
 }
 
 TEST_F(IR_BuiltinPolyfillTest, FirstLeadingBit_I32) {
-    Build(core::Function::kFirstLeadingBit, ty.i32(), Vector{ty.i32()});
+    Build(core::BuiltinFn::kFirstLeadingBit, ty.i32(), Vector{ty.i32()});
     auto* src = R"(
 %foo = func(%arg:i32):i32 -> %b1 {
   %b1 = block {
@@ -724,7 +724,7 @@
 }
 
 TEST_F(IR_BuiltinPolyfillTest, FirstLeadingBit_Vec2U32) {
-    Build(core::Function::kFirstLeadingBit, ty.vec2<u32>(), Vector{ty.vec2<u32>()});
+    Build(core::BuiltinFn::kFirstLeadingBit, ty.vec2<u32>(), Vector{ty.vec2<u32>()});
     auto* src = R"(
 %foo = func(%arg:vec2<u32>):vec2<u32> -> %b1 {
   %b1 = block {
@@ -775,7 +775,7 @@
 }
 
 TEST_F(IR_BuiltinPolyfillTest, FirstLeadingBit_Vec4I32) {
-    Build(core::Function::kFirstLeadingBit, ty.vec4<i32>(), Vector{ty.vec4<i32>()});
+    Build(core::BuiltinFn::kFirstLeadingBit, ty.vec4<i32>(), Vector{ty.vec4<i32>()});
     auto* src = R"(
 %foo = func(%arg:vec4<i32>):vec4<i32> -> %b1 {
   %b1 = block {
@@ -831,7 +831,7 @@
 }
 
 TEST_F(IR_BuiltinPolyfillTest, FirstTrailingBit_NoPolyfill) {
-    Build(core::Function::kFirstTrailingBit, ty.u32(), Vector{ty.u32()});
+    Build(core::BuiltinFn::kFirstTrailingBit, ty.u32(), Vector{ty.u32()});
     auto* src = R"(
 %foo = func(%arg:u32):u32 -> %b1 {
   %b1 = block {
@@ -851,7 +851,7 @@
 }
 
 TEST_F(IR_BuiltinPolyfillTest, FirstTrailingBit_U32) {
-    Build(core::Function::kFirstTrailingBit, ty.u32(), Vector{ty.u32()});
+    Build(core::BuiltinFn::kFirstTrailingBit, ty.u32(), Vector{ty.u32()});
     auto* src = R"(
 %foo = func(%arg:u32):u32 -> %b1 {
   %b1 = block {
@@ -902,7 +902,7 @@
 }
 
 TEST_F(IR_BuiltinPolyfillTest, FirstTrailingBit_I32) {
-    Build(core::Function::kFirstTrailingBit, ty.i32(), Vector{ty.i32()});
+    Build(core::BuiltinFn::kFirstTrailingBit, ty.i32(), Vector{ty.i32()});
     auto* src = R"(
 %foo = func(%arg:i32):i32 -> %b1 {
   %b1 = block {
@@ -955,7 +955,7 @@
 }
 
 TEST_F(IR_BuiltinPolyfillTest, FirstTrailingBit_Vec2U32) {
-    Build(core::Function::kFirstTrailingBit, ty.vec2<u32>(), Vector{ty.vec2<u32>()});
+    Build(core::BuiltinFn::kFirstTrailingBit, ty.vec2<u32>(), Vector{ty.vec2<u32>()});
     auto* src = R"(
 %foo = func(%arg:vec2<u32>):vec2<u32> -> %b1 {
   %b1 = block {
@@ -1006,7 +1006,7 @@
 }
 
 TEST_F(IR_BuiltinPolyfillTest, FirstTrailingBit_Vec4I32) {
-    Build(core::Function::kFirstTrailingBit, ty.vec4<i32>(), Vector{ty.vec4<i32>()});
+    Build(core::BuiltinFn::kFirstTrailingBit, ty.vec4<i32>(), Vector{ty.vec4<i32>()});
     auto* src = R"(
 %foo = func(%arg:vec4<i32>):vec4<i32> -> %b1 {
   %b1 = block {
@@ -1061,7 +1061,7 @@
 TEST_F(IR_BuiltinPolyfillTest, TextureSampleBaseClampToEdge_2d_f32_NoPolyfill) {
     auto* texture_ty =
         ty.Get<core::type::SampledTexture>(core::type::TextureDimension::k2d, ty.f32());
-    Build(core::Function::kTextureSampleBaseClampToEdge, ty.vec4<f32>(),
+    Build(core::BuiltinFn::kTextureSampleBaseClampToEdge, ty.vec4<f32>(),
           Vector{texture_ty, ty.sampler(), ty.vec2<f32>()});
     auto* src = R"(
 %foo = func(%arg:texture_2d<f32>, %arg_1:sampler, %arg_2:vec2<f32>):vec4<f32> -> %b1 {  # %arg_1: 'arg', %arg_2: 'arg'
@@ -1084,7 +1084,7 @@
 TEST_F(IR_BuiltinPolyfillTest, TextureSampleBaseClampToEdge_2d_f32) {
     auto* texture_ty =
         ty.Get<core::type::SampledTexture>(core::type::TextureDimension::k2d, ty.f32());
-    Build(core::Function::kTextureSampleBaseClampToEdge, ty.vec4<f32>(),
+    Build(core::BuiltinFn::kTextureSampleBaseClampToEdge, ty.vec4<f32>(),
           Vector{texture_ty, ty.sampler(), ty.vec2<f32>()});
     auto* src = R"(
 %foo = func(%arg:texture_2d<f32>, %arg_1:sampler, %arg_2:vec2<f32>):vec4<f32> -> %b1 {  # %arg_1: 'arg', %arg_2: 'arg'
diff --git a/src/tint/lang/core/ir/transform/demote_to_helper_test.cc b/src/tint/lang/core/ir/transform/demote_to_helper_test.cc
index 655c714..cc10af2 100644
--- a/src/tint/lang/core/ir/transform/demote_to_helper_test.cc
+++ b/src/tint/lang/core/ir/transform/demote_to_helper_test.cc
@@ -536,7 +536,7 @@
             b.Discard();
             b.ExitIf(ifelse);
         });
-        b.Call(ty.void_(), core::Function::kTextureStore, b.Load(texture), coord,
+        b.Call(ty.void_(), core::BuiltinFn::kTextureStore, b.Load(texture), coord,
                b.Splat(b.ir.Types().vec4<f32>(), 0.5_f, 4));
         b.Return(ep, 0.5_f);
     });
@@ -618,7 +618,7 @@
             b.Discard();
             b.ExitIf(ifelse);
         });
-        b.Call(ty.void_(), core::Function::kAtomicStore, buffer, 42_i);
+        b.Call(ty.void_(), core::BuiltinFn::kAtomicStore, buffer, 42_i);
         b.Return(ep, 0.5_f);
     });
 
@@ -697,7 +697,7 @@
             b.Discard();
             b.ExitIf(ifelse);
         });
-        auto* old = b.Call(ty.i32(), core::Function::kAtomicAdd, buffer, 42_i);
+        auto* old = b.Call(ty.i32(), core::BuiltinFn::kAtomicAdd, buffer, 42_i);
         b.Add(ty.i32(), old, 1_i);
         b.Return(ep, 0.5_f);
     });
@@ -782,7 +782,7 @@
         });
         auto* result =
             b.Call(core::type::CreateAtomicCompareExchangeResult(ty, mod.symbols, ty.i32()),
-                   core::Function::kAtomicCompareExchangeWeak, buffer, 0_i, 42_i);
+                   core::BuiltinFn::kAtomicCompareExchangeWeak, buffer, 0_i, 42_i);
         b.Add(ty.i32(), b.Access(ty.i32(), result, 0_i), 1_i);
         b.Return(ep, 0.5_f);
     });
diff --git a/src/tint/lang/core/ir/transform/multiplanar_external_texture.cc b/src/tint/lang/core/ir/transform/multiplanar_external_texture.cc
index 06eb634..2a94be1 100644
--- a/src/tint/lang/core/ir/transform/multiplanar_external_texture.cc
+++ b/src/tint/lang/core/ir/transform/multiplanar_external_texture.cc
@@ -202,10 +202,10 @@
                     load->Destroy();
                 },
                 [&](CoreBuiltinCall* call) {
-                    if (call->Func() == core::Function::kTextureDimensions) {
+                    if (call->Func() == core::BuiltinFn::kTextureDimensions) {
                         // Use the first plane for the `textureDimensions()` call.
                         call->SetOperand(use.operand_index, plane_0);
-                    } else if (call->Func() == core::Function::kTextureLoad) {
+                    } else if (call->Func() == core::BuiltinFn::kTextureLoad) {
                         // Convert the coordinates to unsigned integers if necessary.
                         auto* coords = call->Args()[1];
                         if (coords->Type()->is_signed_integer_vector()) {
@@ -220,7 +220,7 @@
                         helper->InsertBefore(call);
                         call->Result()->ReplaceAllUsesWith(helper->Result());
                         call->Destroy();
-                    } else if (call->Func() == core::Function::kTextureSampleBaseClampToEdge) {
+                    } else if (call->Func() == core::BuiltinFn::kTextureSampleBaseClampToEdge) {
                         // Call the `TextureSampleExternal()` helper function.
                         auto* sampler = call->Args()[1];
                         auto* coords = call->Args()[2];
@@ -320,17 +320,17 @@
             auto* F = b.Access(ty.f32(), params, 6_u);
             auto* G_splat = b.Construct(vec3f, G);
             auto* D_splat = b.Construct(vec3f, D);
-            auto* abs_v = b.Call(vec3f, core::Function::kAbs, v);
-            auto* sign_v = b.Call(vec3f, core::Function::kSign, v);
+            auto* abs_v = b.Call(vec3f, core::BuiltinFn::kAbs, v);
+            auto* sign_v = b.Call(vec3f, core::BuiltinFn::kSign, v);
             auto* cond = b.LessThan(ty.vec3<bool>(), abs_v, D_splat);
             auto* t = b.Multiply(vec3f, sign_v, b.Add(vec3f, b.Multiply(vec3f, C, abs_v), F));
             auto* f =
                 b.Multiply(vec3f, sign_v,
                            b.Add(vec3f,
-                                 b.Call(vec3f, core::Function::kPow,
+                                 b.Call(vec3f, core::BuiltinFn::kPow,
                                         b.Add(vec3f, b.Multiply(vec3f, A, abs_v), B), G_splat),
                                  E));
-            b.Return(gamma_correction, b.Call(vec3f, core::Function::kSelect, f, t, cond));
+            b.Return(gamma_correction, b.Call(vec3f, core::BuiltinFn::kSelect, f, t, cond));
         });
 
         return gamma_correction;
@@ -390,7 +390,7 @@
             if_planes_eq_1->SetResults(rgb_result, alpha_result);
             b.Append(if_planes_eq_1->True(), [&] {
                 // Load the texel from the first plane and split into separate rgb and a values.
-                auto* texel = b.Call(vec4f, core::Function::kTextureLoad, plane_0, coords, 0_u);
+                auto* texel = b.Call(vec4f, core::BuiltinFn::kTextureLoad, plane_0, coords, 0_u);
                 auto* rgb = b.Swizzle(vec3f, texel, {0u, 1u, 2u});
                 auto* a = b.Access(ty.f32(), texel, 3_u);
                 b.ExitIf(if_planes_eq_1, rgb, a);
@@ -398,14 +398,14 @@
             b.Append(if_planes_eq_1->False(), [&] {
                 // Load the y value from the first plane.
                 auto* y = b.Access(
-                    ty.f32(), b.Call(vec4f, core::Function::kTextureLoad, plane_0, coords, 0_u),
+                    ty.f32(), b.Call(vec4f, core::BuiltinFn::kTextureLoad, plane_0, coords, 0_u),
                     0_u);
 
                 // Load the uv value from the second plane.
                 auto* coord_uv =
                     b.ShiftRight(ty.vec2<u32>(), coords, b.Splat(ty.vec2<u32>(), 1_u, 2u));
                 auto* uv = b.Swizzle(
-                    vec2f, b.Call(vec4f, core::Function::kTextureLoad, plane_1, coord_uv, 0_u),
+                    vec2f, b.Call(vec4f, core::BuiltinFn::kTextureLoad, plane_1, coord_uv, 0_u),
                     {0u, 1u});
 
                 // Convert the combined yuv value into rgb and set the alpha to 1.0.
@@ -498,16 +498,16 @@
             auto* modified_coords =
                 b.Multiply(vec2f, transformation_matrix, b.Construct(vec3f, coords, 1_f));
             auto* plane0_dims = b.Convert(
-                vec2f, b.Call(ty.vec2<u32>(), core::Function::kTextureDimensions, plane_0));
+                vec2f, b.Call(ty.vec2<u32>(), core::BuiltinFn::kTextureDimensions, plane_0));
             auto* plane0_half_texel = b.Divide(vec2f, b.Splat(vec2f, 0.5_f, 2u), plane0_dims);
             auto* plane0_clamped =
-                b.Call(vec2f, core::Function::kClamp, modified_coords, plane0_half_texel,
+                b.Call(vec2f, core::BuiltinFn::kClamp, modified_coords, plane0_half_texel,
                        b.Subtract(vec2f, 1_f, plane0_half_texel));
             auto* plane1_dims = b.Convert(
-                vec2f, b.Call(ty.vec2<u32>(), core::Function::kTextureDimensions, plane_1));
+                vec2f, b.Call(ty.vec2<u32>(), core::BuiltinFn::kTextureDimensions, plane_1));
             auto* plane1_half_texel = b.Divide(vec2f, b.Splat(vec2f, 0.5_f, 2u), plane1_dims);
             auto* plane1_clamped =
-                b.Call(vec2f, core::Function::kClamp, modified_coords, plane1_half_texel,
+                b.Call(vec2f, core::BuiltinFn::kClamp, modified_coords, plane1_half_texel,
                        b.Subtract(vec2f, 1_f, plane1_half_texel));
 
             auto* rgb_result = b.InstructionResult(vec3f);
@@ -517,7 +517,7 @@
             if_planes_eq_1->SetResults(rgb_result, alpha_result);
             b.Append(if_planes_eq_1->True(), [&] {
                 // Sample the texel from the first plane and split into separate rgb and a values.
-                auto* texel = b.Call(vec4f, core::Function::kTextureSampleLevel, plane_0, sampler,
+                auto* texel = b.Call(vec4f, core::BuiltinFn::kTextureSampleLevel, plane_0, sampler,
                                      plane0_clamped, 0_f);
                 auto* rgb = b.Swizzle(vec3f, texel, {0u, 1u, 2u});
                 auto* a = b.Access(ty.f32(), texel, 3_u);
@@ -526,13 +526,13 @@
             b.Append(if_planes_eq_1->False(), [&] {
                 // Sample the y value from the first plane.
                 auto* y = b.Access(ty.f32(),
-                                   b.Call(vec4f, core::Function::kTextureSampleLevel, plane_0,
+                                   b.Call(vec4f, core::BuiltinFn::kTextureSampleLevel, plane_0,
                                           sampler, plane0_clamped, 0_f),
                                    0_u);
 
                 // Sample the uv value from the second plane.
                 auto* uv = b.Swizzle(vec2f,
-                                     b.Call(vec4f, core::Function::kTextureSampleLevel, plane_1,
+                                     b.Call(vec4f, core::BuiltinFn::kTextureSampleLevel, plane_1,
                                             sampler, plane1_clamped, 0_f),
                                      {0u, 1u});
 
diff --git a/src/tint/lang/core/ir/transform/multiplanar_external_texture.h b/src/tint/lang/core/ir/transform/multiplanar_external_texture.h
index 780953b..2fc6dd9 100644
--- a/src/tint/lang/core/ir/transform/multiplanar_external_texture.h
+++ b/src/tint/lang/core/ir/transform/multiplanar_external_texture.h
@@ -27,7 +27,9 @@
 
 namespace tint::core::ir::transform {
 
-/// MultiplanarExternalTexture is a transform that... TODO
+/// MultiplanarExternalTexture is a transform that splits texture_external bindings into two
+/// separate texture_2d<f32> bindings for two possible planes, along with a uniform buffer of
+/// parameters that describe how the texture should be sampled.
 /// @param module the module to transform
 /// @param options the external texture options
 /// @returns an error string on failure
diff --git a/src/tint/lang/core/ir/transform/multiplanar_external_texture_test.cc b/src/tint/lang/core/ir/transform/multiplanar_external_texture_test.cc
index 25cba9b..d38c80a 100644
--- a/src/tint/lang/core/ir/transform/multiplanar_external_texture_test.cc
+++ b/src/tint/lang/core/ir/transform/multiplanar_external_texture_test.cc
@@ -185,7 +185,7 @@
     auto* func = b.Function("foo", ty.vec2<u32>());
     b.Append(func->Block(), [&] {
         auto* load = b.Load(var->Result());
-        auto* result = b.Call(ty.vec2<u32>(), core::Function::kTextureDimensions, load);
+        auto* result = b.Call(ty.vec2<u32>(), core::BuiltinFn::kTextureDimensions, load);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -260,7 +260,7 @@
     func->SetParams({coords});
     b.Append(func->Block(), [&] {
         auto* load = b.Load(var->Result());
-        auto* result = b.Call(ty.vec4<f32>(), core::Function::kTextureLoad, load, coords);
+        auto* result = b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureLoad, load, coords);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -404,7 +404,7 @@
     func->SetParams({coords});
     b.Append(func->Block(), [&] {
         auto* load = b.Load(var->Result());
-        auto* result = b.Call(ty.vec4<f32>(), core::Function::kTextureLoad, load, coords);
+        auto* result = b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureLoad, load, coords);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -550,7 +550,7 @@
     func->SetParams({sampler, coords});
     b.Append(func->Block(), [&] {
         auto* load = b.Load(var->Result());
-        auto* result = b.Call(ty.vec4<f32>(), core::Function::kTextureSampleBaseClampToEdge, load,
+        auto* result = b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureSampleBaseClampToEdge, load,
                               sampler, coords);
         b.Return(func, result);
         mod.SetName(result, "result");
@@ -709,7 +709,7 @@
         auto* coords = b.FunctionParam("coords", ty.vec2<f32>());
         foo->SetParams({texture, sampler, coords});
         b.Append(foo->Block(), [&] {
-            auto* result = b.Call(ty.vec4<f32>(), core::Function::kTextureSampleBaseClampToEdge,
+            auto* result = b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureSampleBaseClampToEdge,
                                   texture, sampler, coords);
             b.Return(foo, result);
             mod.SetName(result, "result");
@@ -894,7 +894,7 @@
         auto* coords = b.FunctionParam("coords", ty.vec2<f32>());
         foo->SetParams({texture, sampler, coords});
         b.Append(foo->Block(), [&] {
-            auto* result = b.Call(ty.vec4<f32>(), core::Function::kTextureSampleBaseClampToEdge,
+            auto* result = b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureSampleBaseClampToEdge,
                                   texture, sampler, coords);
             b.Return(foo, result);
             mod.SetName(result, "result");
@@ -908,12 +908,12 @@
         bar->SetParams({sampler, coords_f});
         b.Append(bar->Block(), [&] {
             auto* load_a = b.Load(var->Result());
-            b.Call(ty.vec2<u32>(), core::Function::kTextureDimensions, load_a);
+            b.Call(ty.vec2<u32>(), core::BuiltinFn::kTextureDimensions, load_a);
             auto* load_b = b.Load(var->Result());
-            b.Call(ty.vec4<f32>(), core::Function::kTextureSampleBaseClampToEdge, load_b, sampler,
+            b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureSampleBaseClampToEdge, load_b, sampler,
                    coords_f);
             auto* load_c = b.Load(var->Result());
-            b.Call(ty.vec4<f32>(), core::Function::kTextureSampleBaseClampToEdge, load_c, sampler,
+            b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureSampleBaseClampToEdge, load_c, sampler,
                    coords_f);
             auto* load_d = b.Load(var->Result());
             auto* result_a = b.Call(ty.vec4<f32>(), foo, load_d, sampler, coords_f);
@@ -1117,11 +1117,11 @@
     foo->SetParams({coords});
     b.Append(foo->Block(), [&] {
         auto* load_a = b.Load(var_a->Result());
-        b.Call(ty.vec4<f32>(), core::Function::kTextureLoad, load_a, coords);
+        b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureLoad, load_a, coords);
         auto* load_b = b.Load(var_b->Result());
-        b.Call(ty.vec4<f32>(), core::Function::kTextureLoad, load_b, coords);
+        b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureLoad, load_b, coords);
         auto* load_c = b.Load(var_c->Result());
-        b.Call(ty.vec4<f32>(), core::Function::kTextureLoad, load_c, coords);
+        b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureLoad, load_c, coords);
         b.Return(foo);
     });
 
diff --git a/src/tint/lang/core/ir/transform/robustness.cc b/src/tint/lang/core/ir/transform/robustness.cc
index 6eef036..77216e1 100644
--- a/src/tint/lang/core/ir/transform/robustness.cc
+++ b/src/tint/lang/core/ir/transform/robustness.cc
@@ -86,9 +86,9 @@
                     [&](ir::CoreBuiltinCall* call) {
                         // Check if this is a texture builtin that needs to be clamped.
                         if (config.clamp_texture) {
-                            if (call->Func() == core::Function::kTextureDimensions ||
-                                call->Func() == core::Function::kTextureLoad ||
-                                call->Func() == core::Function::kTextureStore) {
+                            if (call->Func() == core::BuiltinFn::kTextureDimensions ||
+                                call->Func() == core::BuiltinFn::kTextureLoad ||
+                                call->Func() == core::BuiltinFn::kTextureStore) {
                                 texture_calls.Push(call);
                             }
                         }
@@ -192,7 +192,7 @@
                                                   const_limit->Value()->ValueAs<uint32_t>())));
         } else {
             // Clamp it to the dynamic limit.
-            clamped_idx = b.Call(ty.u32(), core::Function::kMin, CastToU32(idx), limit)->Result();
+            clamped_idx = b.Call(ty.u32(), core::BuiltinFn::kMin, CastToU32(idx), limit)->Result();
         }
 
         // Replace the index operand with the clamped version.
@@ -242,7 +242,7 @@
                     }
 
                     // Use the `arrayLength` builtin to get the limit of a runtime-sized array.
-                    auto* length = b.Call(ty.u32(), core::Function::kArrayLength, object);
+                    auto* length = b.Call(ty.u32(), core::BuiltinFn::kArrayLength, object);
                     return b.Subtract(ty.u32(), length, b.Constant(1_u))->Result();
                 });
 
@@ -268,10 +268,10 @@
         // Keep hold of the clamped value to use for clamping the coordinates.
         Value* clamped_level = nullptr;
         auto clamp_level = [&](uint32_t idx) {
-            auto* num_levels = b.Call(ty.u32(), core::Function::kTextureNumLevels, args[0]);
+            auto* num_levels = b.Call(ty.u32(), core::BuiltinFn::kTextureNumLevels, args[0]);
             auto* limit = b.Subtract(ty.u32(), num_levels, 1_u);
             clamped_level =
-                b.Call(ty.u32(), core::Function::kMin, CastToU32(args[idx]), limit)->Result();
+                b.Call(ty.u32(), core::BuiltinFn::kMin, CastToU32(args[idx]), limit)->Result();
             call->SetOperand(CoreBuiltinCall::kArgsOperandOffset + idx, clamped_level);
         };
 
@@ -283,33 +283,33 @@
                 type = ty.vec(type, vec->Width());
                 one = b.Splat(type, one, vec->Width());
             }
-            auto* dims = clamped_level ? b.Call(type, core::Function::kTextureDimensions, args[0],
+            auto* dims = clamped_level ? b.Call(type, core::BuiltinFn::kTextureDimensions, args[0],
                                                 clamped_level)
-                                       : b.Call(type, core::Function::kTextureDimensions, args[0]);
+                                       : b.Call(type, core::BuiltinFn::kTextureDimensions, args[0]);
             auto* limit = b.Subtract(type, dims, one);
             call->SetOperand(
                 CoreBuiltinCall::kArgsOperandOffset + idx,
-                b.Call(type, core::Function::kMin, CastToU32(args[idx]), limit)->Result());
+                b.Call(type, core::BuiltinFn::kMin, CastToU32(args[idx]), limit)->Result());
         };
 
         // Helper for clamping the array index.
         auto clamp_array_index = [&](uint32_t idx) {
-            auto* num_layers = b.Call(ty.u32(), core::Function::kTextureNumLayers, args[0]);
+            auto* num_layers = b.Call(ty.u32(), core::BuiltinFn::kTextureNumLayers, args[0]);
             auto* limit = b.Subtract(ty.u32(), num_layers, 1_u);
             call->SetOperand(
                 CoreBuiltinCall::kArgsOperandOffset + idx,
-                b.Call(ty.u32(), core::Function::kMin, CastToU32(args[idx]), limit)->Result());
+                b.Call(ty.u32(), core::BuiltinFn::kMin, CastToU32(args[idx]), limit)->Result());
         };
 
         // Select which arguments to clamp based on the function overload.
         switch (call->Func()) {
-            case core::Function::kTextureDimensions: {
+            case core::BuiltinFn::kTextureDimensions: {
                 if (args.Length() > 1) {
                     clamp_level(1u);
                 }
                 break;
             }
-            case core::Function::kTextureLoad: {
+            case core::BuiltinFn::kTextureLoad: {
                 clamp_coords(1u);
                 uint32_t next_arg = 2u;
                 if (type::IsTextureArray(texture->dim())) {
@@ -320,7 +320,7 @@
                 }
                 break;
             }
-            case core::Function::kTextureStore: {
+            case core::BuiltinFn::kTextureStore: {
                 clamp_coords(1u);
                 if (type::IsTextureArray(texture->dim())) {
                     clamp_array_index(2u);
diff --git a/src/tint/lang/core/ir/transform/robustness_test.cc b/src/tint/lang/core/ir/transform/robustness_test.cc
index e55a5de..b44f547 100644
--- a/src/tint/lang/core/ir/transform/robustness_test.cc
+++ b/src/tint/lang/core/ir/transform/robustness_test.cc
@@ -1944,7 +1944,7 @@
     auto* func = b.Function("foo", ty.vec2<u32>());
     b.Append(func->Block(), [&] {
         auto* handle = b.Load(texture);
-        auto* dims = b.Call(ty.vec2<u32>(), core::Function::kTextureDimensions, handle);
+        auto* dims = b.Call(ty.vec2<u32>(), core::BuiltinFn::kTextureDimensions, handle);
         b.Return(func, dims);
     });
 
@@ -1984,7 +1984,7 @@
     func->SetParams({level});
     b.Append(func->Block(), [&] {
         auto* handle = b.Load(texture);
-        auto* dims = b.Call(ty.vec2<u32>(), core::Function::kTextureDimensions, handle, level);
+        auto* dims = b.Call(ty.vec2<u32>(), core::BuiltinFn::kTextureDimensions, handle, level);
         b.Return(func, dims);
     });
 
@@ -2042,7 +2042,7 @@
         b.Append(func->Block(), [&] {
             auto* handle = b.Load(texture);
             auto* texel =
-                b.Call(ty.vec4<f32>(), core::Function::kTextureLoad, handle, coords, level);
+                b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureLoad, handle, coords, level);
             b.Return(func, texel);
         });
     }
@@ -2055,7 +2055,7 @@
         b.Append(func->Block(), [&] {
             auto* handle = b.Load(texture);
             auto* texel =
-                b.Call(ty.vec4<f32>(), core::Function::kTextureLoad, handle, coords, level);
+                b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureLoad, handle, coords, level);
             b.Return(func, texel);
         });
     }
@@ -2139,7 +2139,7 @@
         b.Append(func->Block(), [&] {
             auto* handle = b.Load(texture);
             auto* texel =
-                b.Call(ty.vec4<f32>(), core::Function::kTextureLoad, handle, coords, level);
+                b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureLoad, handle, coords, level);
             b.Return(func, texel);
         });
     }
@@ -2152,7 +2152,7 @@
         b.Append(func->Block(), [&] {
             auto* handle = b.Load(texture);
             auto* texel =
-                b.Call(ty.vec4<f32>(), core::Function::kTextureLoad, handle, coords, level);
+                b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureLoad, handle, coords, level);
             b.Return(func, texel);
         });
     }
@@ -2238,7 +2238,7 @@
         b.Append(func->Block(), [&] {
             auto* handle = b.Load(texture);
             auto* texel =
-                b.Call(ty.vec4<f32>(), core::Function::kTextureLoad, handle, coords, layer, level);
+                b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureLoad, handle, coords, layer, level);
             b.Return(func, texel);
         });
     }
@@ -2252,7 +2252,7 @@
         b.Append(func->Block(), [&] {
             auto* handle = b.Load(texture);
             auto* texel =
-                b.Call(ty.vec4<f32>(), core::Function::kTextureLoad, handle, coords, layer, level);
+                b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureLoad, handle, coords, layer, level);
             b.Return(func, texel);
         });
     }
@@ -2343,7 +2343,7 @@
         b.Append(func->Block(), [&] {
             auto* handle = b.Load(texture);
             auto* texel =
-                b.Call(ty.vec4<f32>(), core::Function::kTextureLoad, handle, coords, level);
+                b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureLoad, handle, coords, level);
             b.Return(func, texel);
         });
     }
@@ -2356,7 +2356,7 @@
         b.Append(func->Block(), [&] {
             auto* handle = b.Load(texture);
             auto* texel =
-                b.Call(ty.vec4<f32>(), core::Function::kTextureLoad, handle, coords, level);
+                b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureLoad, handle, coords, level);
             b.Return(func, texel);
         });
     }
@@ -2441,7 +2441,7 @@
         b.Append(func->Block(), [&] {
             auto* handle = b.Load(texture);
             auto* texel =
-                b.Call(ty.vec4<f32>(), core::Function::kTextureLoad, handle, coords, level);
+                b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureLoad, handle, coords, level);
             b.Return(func, texel);
         });
     }
@@ -2454,7 +2454,7 @@
         b.Append(func->Block(), [&] {
             auto* handle = b.Load(texture);
             auto* texel =
-                b.Call(ty.vec4<f32>(), core::Function::kTextureLoad, handle, coords, level);
+                b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureLoad, handle, coords, level);
             b.Return(func, texel);
         });
     }
@@ -2529,7 +2529,7 @@
         func->SetParams({coords, level});
         b.Append(func->Block(), [&] {
             auto* handle = b.Load(texture);
-            auto* texel = b.Call(ty.f32(), core::Function::kTextureLoad, handle, coords, level);
+            auto* texel = b.Call(ty.f32(), core::BuiltinFn::kTextureLoad, handle, coords, level);
             b.Return(func, texel);
         });
     }
@@ -2541,7 +2541,7 @@
         func->SetParams({coords, level});
         b.Append(func->Block(), [&] {
             auto* handle = b.Load(texture);
-            auto* texel = b.Call(ty.f32(), core::Function::kTextureLoad, handle, coords, level);
+            auto* texel = b.Call(ty.f32(), core::BuiltinFn::kTextureLoad, handle, coords, level);
             b.Return(func, texel);
         });
     }
@@ -2626,7 +2626,7 @@
         b.Append(func->Block(), [&] {
             auto* handle = b.Load(texture);
             auto* texel =
-                b.Call(ty.f32(), core::Function::kTextureLoad, handle, coords, layer, level);
+                b.Call(ty.f32(), core::BuiltinFn::kTextureLoad, handle, coords, layer, level);
             b.Return(func, texel);
         });
     }
@@ -2640,7 +2640,7 @@
         b.Append(func->Block(), [&] {
             auto* handle = b.Load(texture);
             auto* texel =
-                b.Call(ty.f32(), core::Function::kTextureLoad, handle, coords, layer, level);
+                b.Call(ty.f32(), core::BuiltinFn::kTextureLoad, handle, coords, layer, level);
             b.Return(func, texel);
         });
     }
@@ -2730,7 +2730,7 @@
         func->SetParams({coords, index});
         b.Append(func->Block(), [&] {
             auto* handle = b.Load(texture);
-            auto* texel = b.Call(ty.f32(), core::Function::kTextureLoad, handle, coords, index);
+            auto* texel = b.Call(ty.f32(), core::BuiltinFn::kTextureLoad, handle, coords, index);
             b.Return(func, texel);
         });
     }
@@ -2742,7 +2742,7 @@
         func->SetParams({coords, index});
         b.Append(func->Block(), [&] {
             auto* handle = b.Load(texture);
-            auto* texel = b.Call(ty.f32(), core::Function::kTextureLoad, handle, coords, index);
+            auto* texel = b.Call(ty.f32(), core::BuiltinFn::kTextureLoad, handle, coords, index);
             b.Return(func, texel);
         });
     }
@@ -2815,7 +2815,7 @@
         func->SetParams({coords});
         b.Append(func->Block(), [&] {
             auto* handle = b.Load(texture);
-            auto* texel = b.Call(ty.vec4<f32>(), core::Function::kTextureLoad, handle, coords);
+            auto* texel = b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureLoad, handle, coords);
             b.Return(func, texel);
         });
     }
@@ -2826,7 +2826,7 @@
         func->SetParams({coords});
         b.Append(func->Block(), [&] {
             auto* handle = b.Load(texture);
-            auto* texel = b.Call(ty.vec4<f32>(), core::Function::kTextureLoad, handle, coords);
+            auto* texel = b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureLoad, handle, coords);
             b.Return(func, texel);
         });
     }
@@ -2905,7 +2905,7 @@
         func->SetParams({coords});
         b.Append(func->Block(), [&] {
             auto* handle = b.Load(texture);
-            auto* texel = b.Call(ty.vec4<f32>(), core::Function::kTextureLoad, handle, coords);
+            auto* texel = b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureLoad, handle, coords);
             b.Return(func, texel);
         });
     }
@@ -2916,7 +2916,7 @@
         func->SetParams({coords});
         b.Append(func->Block(), [&] {
             auto* handle = b.Load(texture);
-            auto* texel = b.Call(ty.vec4<f32>(), core::Function::kTextureLoad, handle, coords);
+            auto* texel = b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureLoad, handle, coords);
             b.Return(func, texel);
         });
     }
@@ -2995,7 +2995,7 @@
         func->SetParams({coords});
         b.Append(func->Block(), [&] {
             auto* handle = b.Load(texture);
-            auto* texel = b.Call(ty.vec4<f32>(), core::Function::kTextureLoad, handle, coords);
+            auto* texel = b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureLoad, handle, coords);
             b.Return(func, texel);
         });
     }
@@ -3006,7 +3006,7 @@
         func->SetParams({coords});
         b.Append(func->Block(), [&] {
             auto* handle = b.Load(texture);
-            auto* texel = b.Call(ty.vec4<f32>(), core::Function::kTextureLoad, handle, coords);
+            auto* texel = b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureLoad, handle, coords);
             b.Return(func, texel);
         });
     }
@@ -3087,7 +3087,7 @@
         b.Append(func->Block(), [&] {
             auto* handle = b.Load(texture);
             auto* texel =
-                b.Call(ty.vec4<f32>(), core::Function::kTextureLoad, handle, coords, layer);
+                b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureLoad, handle, coords, layer);
             b.Return(func, texel);
         });
     }
@@ -3100,7 +3100,7 @@
         b.Append(func->Block(), [&] {
             auto* handle = b.Load(texture);
             auto* texel =
-                b.Call(ty.vec4<f32>(), core::Function::kTextureLoad, handle, coords, layer);
+                b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureLoad, handle, coords, layer);
             b.Return(func, texel);
         });
     }
@@ -3186,7 +3186,7 @@
         func->SetParams({coords});
         b.Append(func->Block(), [&] {
             auto* handle = b.Load(texture);
-            auto* texel = b.Call(ty.vec4<f32>(), core::Function::kTextureLoad, handle, coords);
+            auto* texel = b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureLoad, handle, coords);
             b.Return(func, texel);
         });
     }
@@ -3197,7 +3197,7 @@
         func->SetParams({coords});
         b.Append(func->Block(), [&] {
             auto* handle = b.Load(texture);
-            auto* texel = b.Call(ty.vec4<f32>(), core::Function::kTextureLoad, handle, coords);
+            auto* texel = b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureLoad, handle, coords);
             b.Return(func, texel);
         });
     }
@@ -3277,7 +3277,7 @@
         func->SetParams({coords, value});
         b.Append(func->Block(), [&] {
             auto* handle = b.Load(texture);
-            b.Call(ty.void_(), core::Function::kTextureStore, handle, coords, value);
+            b.Call(ty.void_(), core::BuiltinFn::kTextureStore, handle, coords, value);
             b.Return(func);
         });
     }
@@ -3289,7 +3289,7 @@
         func->SetParams({coords, value});
         b.Append(func->Block(), [&] {
             auto* handle = b.Load(texture);
-            b.Call(ty.void_(), core::Function::kTextureStore, handle, coords, value);
+            b.Call(ty.void_(), core::BuiltinFn::kTextureStore, handle, coords, value);
             b.Return(func);
         });
     }
@@ -3369,7 +3369,7 @@
         func->SetParams({coords, value});
         b.Append(func->Block(), [&] {
             auto* handle = b.Load(texture);
-            b.Call(ty.void_(), core::Function::kTextureStore, handle, coords, value);
+            b.Call(ty.void_(), core::BuiltinFn::kTextureStore, handle, coords, value);
             b.Return(func);
         });
     }
@@ -3381,7 +3381,7 @@
         func->SetParams({coords, value});
         b.Append(func->Block(), [&] {
             auto* handle = b.Load(texture);
-            b.Call(ty.void_(), core::Function::kTextureStore, handle, coords, value);
+            b.Call(ty.void_(), core::BuiltinFn::kTextureStore, handle, coords, value);
             b.Return(func);
         });
     }
@@ -3462,7 +3462,7 @@
         func->SetParams({coords, layer, value});
         b.Append(func->Block(), [&] {
             auto* handle = b.Load(texture);
-            b.Call(ty.void_(), core::Function::kTextureStore, handle, coords, layer, value);
+            b.Call(ty.void_(), core::BuiltinFn::kTextureStore, handle, coords, layer, value);
             b.Return(func);
         });
     }
@@ -3475,7 +3475,7 @@
         func->SetParams({coords, layer, value});
         b.Append(func->Block(), [&] {
             auto* handle = b.Load(texture);
-            b.Call(ty.void_(), core::Function::kTextureStore, handle, coords, layer, value);
+            b.Call(ty.void_(), core::BuiltinFn::kTextureStore, handle, coords, layer, value);
             b.Return(func);
         });
     }
@@ -3562,7 +3562,7 @@
         func->SetParams({coords, value});
         b.Append(func->Block(), [&] {
             auto* handle = b.Load(texture);
-            b.Call(ty.void_(), core::Function::kTextureStore, handle, coords, value);
+            b.Call(ty.void_(), core::BuiltinFn::kTextureStore, handle, coords, value);
             b.Return(func);
         });
     }
@@ -3574,7 +3574,7 @@
         func->SetParams({coords, value});
         b.Append(func->Block(), [&] {
             auto* handle = b.Load(texture);
-            b.Call(ty.void_(), core::Function::kTextureStore, handle, coords, value);
+            b.Call(ty.void_(), core::BuiltinFn::kTextureStore, handle, coords, value);
             b.Return(func);
         });
     }
diff --git a/src/tint/lang/core/ir/unary.cc b/src/tint/lang/core/ir/unary.cc
index d6ce129..69b6857 100644
--- a/src/tint/lang/core/ir/unary.cc
+++ b/src/tint/lang/core/ir/unary.cc
@@ -14,6 +14,9 @@
 
 #include "src/tint/lang/core/ir/unary.h"
 
+#include "src/tint/lang/core/ir/clone_context.h"
+#include "src/tint/lang/core/ir/module.h"
+
 TINT_INSTANTIATE_TYPEINFO(tint::core::ir::Unary);
 
 namespace tint::core::ir {
@@ -25,4 +28,10 @@
 
 Unary::~Unary() = default;
 
+Unary* Unary::Clone(CloneContext& ctx) {
+    auto* new_result = ctx.Clone(Result());
+    auto* new_val = ctx.Clone(Val());
+    return ctx.ir.instructions.Create<Unary>(new_result, kind_, new_val);
+}
+
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/unary.h b/src/tint/lang/core/ir/unary.h
index 2e55cc3..15fdfa3 100644
--- a/src/tint/lang/core/ir/unary.h
+++ b/src/tint/lang/core/ir/unary.h
@@ -23,7 +23,7 @@
 namespace tint::core::ir {
 
 /// A unary instruction in the IR.
-class Unary : public Castable<Unary, OperandInstruction<1, 1>> {
+class Unary final : public Castable<Unary, OperandInstruction<1, 1>> {
   public:
     /// The offset in Operands() for the value
     static constexpr size_t kValueOperandOffset = 0;
@@ -41,6 +41,9 @@
     Unary(InstructionResult* result, enum Kind kind, Value* val);
     ~Unary() override;
 
+    /// @copydoc Instruction::Clone()
+    Unary* Clone(CloneContext& ctx) override;
+
     /// @returns the value for the instruction
     Value* Val() { return operands_[kValueOperandOffset]; }
 
diff --git a/src/tint/lang/core/ir/unary_test.cc b/src/tint/lang/core/ir/unary_test.cc
index 95d30cf..ab90266 100644
--- a/src/tint/lang/core/ir/unary_test.cc
+++ b/src/tint/lang/core/ir/unary_test.cc
@@ -78,5 +78,20 @@
         "");
 }
 
+TEST_F(IR_UnaryTest, Clone) {
+    auto* inst = b.Complement(mod.Types().i32(), 4_i);
+    auto* new_inst = clone_ctx.Clone(inst);
+
+    EXPECT_NE(inst, new_inst);
+    EXPECT_NE(nullptr, new_inst->Result());
+    EXPECT_NE(inst->Result(), new_inst->Result());
+
+    EXPECT_EQ(Unary::Kind::kComplement, new_inst->Kind());
+
+    auto new_val = new_inst->Val()->As<Constant>()->Value();
+    ASSERT_TRUE(new_val->Is<core::constant::Scalar<i32>>());
+    EXPECT_EQ(4_i, new_val->As<core::constant::Scalar<i32>>()->ValueAs<i32>());
+}
+
 }  // namespace
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/unreachable.cc b/src/tint/lang/core/ir/unreachable.cc
index 0011530..da73d70 100644
--- a/src/tint/lang/core/ir/unreachable.cc
+++ b/src/tint/lang/core/ir/unreachable.cc
@@ -14,10 +14,17 @@
 
 #include "src/tint/lang/core/ir/unreachable.h"
 
+#include "src/tint/lang/core/ir/clone_context.h"
+#include "src/tint/lang/core/ir/module.h"
+
 TINT_INSTANTIATE_TYPEINFO(tint::core::ir::Unreachable);
 
 namespace tint::core::ir {
 
 Unreachable::~Unreachable() = default;
 
+Unreachable* Unreachable::Clone(CloneContext& ctx) {
+    return ctx.ir.instructions.Create<Unreachable>();
+}
+
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/unreachable.h b/src/tint/lang/core/ir/unreachable.h
index fd92538..9a9d264 100644
--- a/src/tint/lang/core/ir/unreachable.h
+++ b/src/tint/lang/core/ir/unreachable.h
@@ -22,10 +22,13 @@
 namespace tint::core::ir {
 
 /// An unreachable instruction in the IR.
-class Unreachable : public Castable<Unreachable, Terminator> {
+class Unreachable final : public Castable<Unreachable, Terminator> {
   public:
     ~Unreachable() override;
 
+    /// @copydoc Instruction::Clone()
+    Unreachable* Clone(CloneContext& ctx) override;
+
     /// @returns the friendly name for the instruction
     std::string FriendlyName() override { return "unreachable"; }
 };
diff --git a/src/tint/lang/core/ir/unreachable_test.cc b/src/tint/lang/core/ir/unreachable_test.cc
new file mode 100644
index 0000000..5e172bd
--- /dev/null
+++ b/src/tint/lang/core/ir/unreachable_test.cc
@@ -0,0 +1,46 @@
+// Copyright 2023 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "gtest/gtest-spi.h"
+#include "src/tint/lang/core/ir/builder.h"
+#include "src/tint/lang/core/ir/instruction.h"
+#include "src/tint/lang/core/ir/ir_helper_test.h"
+
+namespace tint::core::ir {
+namespace {
+
+using IR_UnreachableTest = IRTestHelper;
+
+TEST_F(IR_UnreachableTest, Unreachable) {
+    auto* inst = b.Unreachable();
+    ASSERT_TRUE(inst->Is<ir::Unreachable>());
+}
+
+TEST_F(IR_UnreachableTest, Result) {
+    auto* inst = b.Unreachable();
+
+    EXPECT_FALSE(inst->HasResults());
+    EXPECT_FALSE(inst->HasMultiResults());
+}
+
+TEST_F(IR_UnreachableTest, Clone) {
+    auto* d = b.Unreachable();
+    auto* new_d = clone_ctx.Clone(d);
+
+    EXPECT_NE(d, new_d);
+    EXPECT_NE(nullptr, new_d);
+}
+
+}  // namespace
+}  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/user_call.cc b/src/tint/lang/core/ir/user_call.cc
index 1a41472..0f20129 100644
--- a/src/tint/lang/core/ir/user_call.cc
+++ b/src/tint/lang/core/ir/user_call.cc
@@ -16,6 +16,9 @@
 
 #include <utility>
 
+#include "src/tint/lang/core/ir/clone_context.h"
+#include "src/tint/lang/core/ir/module.h"
+
 TINT_INSTANTIATE_TYPEINFO(tint::core::ir::UserCall);
 
 namespace tint::core::ir {
@@ -28,4 +31,11 @@
 
 UserCall::~UserCall() = default;
 
+UserCall* UserCall::Clone(CloneContext& ctx) {
+    auto* new_result = ctx.Clone(Result());
+    auto* new_target = ctx.Clone(Target());
+    auto new_args = ctx.Clone<UserCall::kDefaultNumOperands>(Args());
+    return ctx.ir.instructions.Create<UserCall>(new_result, new_target, new_args);
+}
+
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/user_call.h b/src/tint/lang/core/ir/user_call.h
index f6155ab..f6688e2 100644
--- a/src/tint/lang/core/ir/user_call.h
+++ b/src/tint/lang/core/ir/user_call.h
@@ -24,7 +24,7 @@
 namespace tint::core::ir {
 
 /// A user call instruction in the IR.
-class UserCall : public Castable<UserCall, Call> {
+class UserCall final : public Castable<UserCall, Call> {
   public:
     /// The offset in Operands() for the function being called
     static constexpr size_t kFunctionOperandOffset = 0;
@@ -39,6 +39,9 @@
     UserCall(InstructionResult* result, Function* func, VectorRef<Value*> args);
     ~UserCall() override;
 
+    /// @copydoc Instruction::Clone()
+    UserCall* Clone(CloneContext& ctx) override;
+
     /// @returns the call arguments
     tint::Slice<Value*> Args() override { return operands_.Slice().Offset(kArgsOperandOffset); }
 
diff --git a/src/tint/lang/core/ir/user_call_test.cc b/src/tint/lang/core/ir/user_call_test.cc
index 51f99eb..efbe64c 100644
--- a/src/tint/lang/core/ir/user_call_test.cc
+++ b/src/tint/lang/core/ir/user_call_test.cc
@@ -56,5 +56,39 @@
         "");
 }
 
+TEST_F(IR_UserCallTest, Clone) {
+    auto* func = b.Function("myfunc", mod.Types().void_());
+    auto* e = b.Call(mod.Types().void_(), func, Vector{b.Constant(1_u), b.Constant(2_u)});
+
+    auto* new_func = clone_ctx.Clone(func);
+    auto* new_e = clone_ctx.Clone(e);
+
+    EXPECT_NE(e, new_e);
+    EXPECT_NE(nullptr, new_e->Result());
+    EXPECT_NE(e->Result(), new_e->Result());
+
+    EXPECT_EQ(new_func, new_e->Target());
+
+    auto args = new_e->Args();
+    EXPECT_EQ(2u, args.Length());
+
+    auto new_arg1 = args[0]->As<Constant>()->Value();
+    ASSERT_TRUE(new_arg1->Is<core::constant::Scalar<u32>>());
+    EXPECT_EQ(1_u, new_arg1->As<core::constant::Scalar<u32>>()->ValueAs<u32>());
+
+    auto new_arg2 = args[1]->As<Constant>()->Value();
+    ASSERT_TRUE(new_arg2->Is<core::constant::Scalar<u32>>());
+    EXPECT_EQ(2_u, new_arg2->As<core::constant::Scalar<u32>>()->ValueAs<u32>());
+}
+
+TEST_F(IR_UserCallTest, CloneWithoutArgs) {
+    auto* func = b.Function("myfunc", mod.Types().void_());
+    auto* e = b.Call(mod.Types().void_(), func);
+
+    auto* new_e = clone_ctx.Clone(e);
+
+    EXPECT_EQ(0u, new_e->Args().Length());
+}
+
 }  // namespace
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/validator.cc b/src/tint/lang/core/ir/validator.cc
index e7cc0f2..055940c 100644
--- a/src/tint/lang/core/ir/validator.cc
+++ b/src/tint/lang/core/ir/validator.cc
@@ -35,7 +35,6 @@
 #include "src/tint/lang/core/ir/exit_switch.h"
 #include "src/tint/lang/core/ir/function.h"
 #include "src/tint/lang/core/ir/if.h"
-#include "src/tint/lang/core/ir/intrinsic_call.h"
 #include "src/tint/lang/core/ir/let.h"
 #include "src/tint/lang/core/ir/load.h"
 #include "src/tint/lang/core/ir/load_vector_element.h"
@@ -520,7 +519,6 @@
         call,                                          //
         [&](Bitcast*) {},                              //
         [&](BuiltinCall* c) { CheckBuiltinCall(c); },  //
-        [&](IntrinsicCall*) {},                        //
         [&](Construct*) {},                            //
         [&](Convert*) {},                              //
         [&](Discard*) {},                              //
diff --git a/src/tint/lang/core/ir/value.h b/src/tint/lang/core/ir/value.h
index 6393bc9..176ad0e 100644
--- a/src/tint/lang/core/ir/value.h
+++ b/src/tint/lang/core/ir/value.h
@@ -21,6 +21,7 @@
 
 // Forward declarations
 namespace tint::core::ir {
+class CloneContext;
 class Instruction;
 }  // namespace tint::core::ir
 
@@ -63,6 +64,10 @@
     /// The Value must not be in use by any instruction.
     virtual void Destroy();
 
+    /// @param ctx the CloneContext used to clone this value
+    /// @returns a clone of this value
+    virtual Value* Clone(CloneContext& ctx) = 0;
+
     /// @returns true if the Value has not been destroyed with Destroy()
     bool Alive() const { return !flags_.Contains(Flag::kDead); }
 
diff --git a/src/tint/lang/core/ir/var.cc b/src/tint/lang/core/ir/var.cc
index 501d6af..3ea21f9 100644
--- a/src/tint/lang/core/ir/var.cc
+++ b/src/tint/lang/core/ir/var.cc
@@ -14,6 +14,8 @@
 
 #include "src/tint/lang/core/ir/var.h"
 
+#include "src/tint/lang/core/ir/clone_context.h"
+#include "src/tint/lang/core/ir/module.h"
 #include "src/tint/lang/core/ir/store.h"
 #include "src/tint/lang/core/type/pointer.h"
 #include "src/tint/utils/ice/ice.h"
@@ -34,6 +36,20 @@
 
 Var::~Var() = default;
 
+Var* Var::Clone(CloneContext& ctx) {
+    auto* new_result = ctx.Clone(Result());
+    auto* new_var = ctx.ir.instructions.Create<Var>(new_result);
+
+    new_var->binding_point_ = binding_point_;
+    new_var->attributes_ = attributes_;
+
+    auto name = ctx.ir.NameOf(this);
+    if (name.IsValid()) {
+        ctx.ir.SetName(new_var, name.Name());
+    }
+    return new_var;
+}
+
 void Var::SetInitializer(Value* initializer) {
     SetOperand(Var::kInitializerOperandOffset, initializer);
 }
diff --git a/src/tint/lang/core/ir/var.h b/src/tint/lang/core/ir/var.h
index 384f7b1..01e8c38 100644
--- a/src/tint/lang/core/ir/var.h
+++ b/src/tint/lang/core/ir/var.h
@@ -40,7 +40,7 @@
 };
 
 /// A var instruction in the IR.
-class Var : public Castable<Var, OperandInstruction<1, 1>> {
+class Var final : public Castable<Var, OperandInstruction<1, 1>> {
   public:
     /// The offset in Operands() for the initializer
     static constexpr size_t kInitializerOperandOffset = 0;
@@ -50,6 +50,9 @@
     explicit Var(InstructionResult* result);
     ~Var() override;
 
+    /// @copydoc Instruction::Clone()
+    Var* Clone(CloneContext& ctx) override;
+
     /// Sets the var initializer
     /// @param initializer the initializer
     void SetInitializer(Value* initializer);
diff --git a/src/tint/lang/core/ir/var_test.cc b/src/tint/lang/core/ir/var_test.cc
index 24fb70e..c147dc3 100644
--- a/src/tint/lang/core/ir/var_test.cc
+++ b/src/tint/lang/core/ir/var_test.cc
@@ -58,5 +58,54 @@
     EXPECT_TRUE(init->Usages().IsEmpty());
 }
 
+TEST_F(IR_VarTest, Clone) {
+    auto* v = b.Var(mod.Types().ptr(core::AddressSpace::kFunction, mod.Types().f32()));
+    v->SetInitializer(b.Constant(4_f));
+    v->SetBindingPoint(1, 2);
+    v->SetAttributes(IOAttributes{
+        3, 4, core::BuiltinValue::kFragDepth,
+        Interpolation{core::InterpolationType::kFlat, core::InterpolationSampling::kCentroid},
+        true});
+
+    auto* new_v = clone_ctx.Clone(v);
+
+    EXPECT_NE(v, new_v);
+    ASSERT_NE(nullptr, new_v->Result());
+    EXPECT_NE(v->Result(), new_v->Result());
+    EXPECT_EQ(new_v->Result()->Type(),
+              mod.Types().ptr(core::AddressSpace::kFunction, mod.Types().f32()));
+
+    auto new_val = v->Initializer()->As<Constant>()->Value();
+    ASSERT_TRUE(new_val->Is<core::constant::Scalar<f32>>());
+    EXPECT_FLOAT_EQ(4_f, new_val->As<core::constant::Scalar<f32>>()->ValueAs<f32>());
+
+    EXPECT_TRUE(new_v->BindingPoint().has_value());
+    EXPECT_EQ(1u, new_v->BindingPoint()->group);
+    EXPECT_EQ(2u, new_v->BindingPoint()->binding);
+
+    auto& attrs = new_v->Attributes();
+    EXPECT_TRUE(attrs.location.has_value());
+    EXPECT_EQ(3u, attrs.location.value());
+
+    EXPECT_TRUE(attrs.index.has_value());
+    EXPECT_EQ(4u, attrs.index.value());
+
+    EXPECT_TRUE(attrs.builtin.has_value());
+    EXPECT_EQ(core::BuiltinValue::kFragDepth, attrs.builtin.value());
+
+    EXPECT_TRUE(attrs.interpolation.has_value());
+    EXPECT_EQ(core::InterpolationType::kFlat, attrs.interpolation->type);
+    EXPECT_EQ(core::InterpolationSampling::kCentroid, attrs.interpolation->sampling);
+
+    EXPECT_TRUE(attrs.invariant);
+}
+
+TEST_F(IR_VarTest, CloneWithName) {
+    auto* v = b.Var("v", mod.Types().ptr(core::AddressSpace::kFunction, mod.Types().f32()));
+    auto* new_v = clone_ctx.Clone(v);
+
+    EXPECT_EQ(std::string("v"), mod.NameOf(new_v).Name());
+}
+
 }  // namespace
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/texel_format.def b/src/tint/lang/core/texel_format.def
new file mode 100644
index 0000000..89e5f65
--- /dev/null
+++ b/src/tint/lang/core/texel_format.def
@@ -0,0 +1,20 @@
+// https://gpuweb.github.io/gpuweb/wgsl/#texel-formats
+enum texel_format {
+  bgra8unorm
+  rgba8unorm
+  rgba8snorm
+  rgba8uint
+  rgba8sint
+  rgba16uint
+  rgba16sint
+  rgba16float
+  r32uint
+  r32sint
+  r32float
+  rg32uint
+  rg32sint
+  rg32float
+  rgba32uint
+  rgba32sint
+  rgba32float
+}
diff --git a/src/tint/lang/core/type/builtin_structs.cc b/src/tint/lang/core/type/builtin_structs.cc
index 3d97986..2673bf2 100644
--- a/src/tint/lang/core/type/builtin_structs.cc
+++ b/src/tint/lang/core/type/builtin_structs.cc
@@ -18,7 +18,7 @@
 #include <string>
 #include <utility>
 
-#include "src/tint/lang/core/builtin.h"
+#include "src/tint/lang/core/builtin_type.h"
 #include "src/tint/lang/core/type/abstract_float.h"
 #include "src/tint/lang/core/type/abstract_int.h"
 #include "src/tint/lang/core/type/bool.h"
@@ -36,39 +36,39 @@
 
 /// An array of `modf()` return type names for an argument of `vecN<f32>`.
 constexpr std::array kModfVecF32Names{
-    core::Builtin::kModfResultVec2F32,  // return type of modf(vec2<f32>)
-    core::Builtin::kModfResultVec3F32,  // return type of modf(vec3<f32>)
-    core::Builtin::kModfResultVec4F32,  // return type of modf(vec4<f32>)
+    core::BuiltinType::kModfResultVec2F32,  // return type of modf(vec2<f32>)
+    core::BuiltinType::kModfResultVec3F32,  // return type of modf(vec3<f32>)
+    core::BuiltinType::kModfResultVec4F32,  // return type of modf(vec4<f32>)
 };
 
 /// An array of `modf()` return type names for an argument of `vecN<f16>`.
 constexpr std::array kModfVecF16Names{
-    core::Builtin::kModfResultVec2F16,  // return type of modf(vec2<f16>)
-    core::Builtin::kModfResultVec3F16,  // return type of modf(vec3<f16>)
-    core::Builtin::kModfResultVec4F16,  // return type of modf(vec4<f16>)
+    core::BuiltinType::kModfResultVec2F16,  // return type of modf(vec2<f16>)
+    core::BuiltinType::kModfResultVec3F16,  // return type of modf(vec3<f16>)
+    core::BuiltinType::kModfResultVec4F16,  // return type of modf(vec4<f16>)
 };
 
 /// An array of `modf()` return type names for an argument of `vecN<abstract-float>`.
 constexpr std::array kModfVecAbstractNames{
-    core::Builtin::kModfResultVec2Abstract,  // return type of modf(vec2<abstract-float>)
-    core::Builtin::kModfResultVec3Abstract,  // return type of modf(vec3<abstract-float>)
-    core::Builtin::kModfResultVec4Abstract,  // return type of modf(vec4<abstract-float>)
+    core::BuiltinType::kModfResultVec2Abstract,  // return type of modf(vec2<abstract-float>)
+    core::BuiltinType::kModfResultVec3Abstract,  // return type of modf(vec3<abstract-float>)
+    core::BuiltinType::kModfResultVec4Abstract,  // return type of modf(vec4<abstract-float>)
 };
 
 Struct* CreateModfResult(Manager& types, SymbolTable& symbols, const Type* ty) {
-    auto build = [&](core::Builtin name, const Type* t) {
+    auto build = [&](core::BuiltinType name, const Type* t) {
         return types.Struct(symbols.Register(tint::ToString(name)),
                             {{symbols.Register("fract"), t}, {symbols.Register("whole"), t}});
     };
     return Switch(
         ty,  //
-        [&](const F32*) { return build(core::Builtin::kModfResultF32, ty); },
-        [&](const F16*) { return build(core::Builtin::kModfResultF16, ty); },
+        [&](const F32*) { return build(core::BuiltinType::kModfResultF32, ty); },
+        [&](const F16*) { return build(core::BuiltinType::kModfResultF16, ty); },
         [&](const AbstractFloat*) {
-            auto* abstract = build(core::Builtin::kModfResultAbstract, ty);
+            auto* abstract = build(core::BuiltinType::kModfResultAbstract, ty);
             abstract->SetConcreteTypes(tint::Vector{
-                build(core::Builtin::kModfResultF32, types.f32()),
-                build(core::Builtin::kModfResultF16, types.f16()),
+                build(core::BuiltinType::kModfResultF32, types.f32()),
+                build(core::BuiltinType::kModfResultF16, types.f16()),
             });
             return abstract;
         },
@@ -99,40 +99,40 @@
 
 /// An array of `frexp()` return type names for an argument of `vecN<f32>`.
 constexpr std::array kFrexpVecF32Names{
-    core::Builtin::kFrexpResultVec2F32,  // return type of frexp(vec2<f32>)
-    core::Builtin::kFrexpResultVec3F32,  // return type of frexp(vec3<f32>)
-    core::Builtin::kFrexpResultVec4F32,  // return type of frexp(vec4<f32>)
+    core::BuiltinType::kFrexpResultVec2F32,  // return type of frexp(vec2<f32>)
+    core::BuiltinType::kFrexpResultVec3F32,  // return type of frexp(vec3<f32>)
+    core::BuiltinType::kFrexpResultVec4F32,  // return type of frexp(vec4<f32>)
 };
 
 /// An array of `frexp()` return type names for an argument of `vecN<f16>`.
 constexpr std::array kFrexpVecF16Names{
-    core::Builtin::kFrexpResultVec2F16,  // return type of frexp(vec2<f16>)
-    core::Builtin::kFrexpResultVec3F16,  // return type of frexp(vec3<f16>)
-    core::Builtin::kFrexpResultVec4F16,  // return type of frexp(vec4<f16>)
+    core::BuiltinType::kFrexpResultVec2F16,  // return type of frexp(vec2<f16>)
+    core::BuiltinType::kFrexpResultVec3F16,  // return type of frexp(vec3<f16>)
+    core::BuiltinType::kFrexpResultVec4F16,  // return type of frexp(vec4<f16>)
 };
 
 /// An array of `frexp()` return type names for an argument of `vecN<abstract-float>`.
 constexpr std::array kFrexpVecAbstractNames{
-    core::Builtin::kFrexpResultVec2Abstract,  // return type of frexp(vec2<abstract-float>)
-    core::Builtin::kFrexpResultVec3Abstract,  // return type of frexp(vec3<abstract-float>)
-    core::Builtin::kFrexpResultVec4Abstract,  // return type of frexp(vec4<abstract-float>)
+    core::BuiltinType::kFrexpResultVec2Abstract,  // return type of frexp(vec2<abstract-float>)
+    core::BuiltinType::kFrexpResultVec3Abstract,  // return type of frexp(vec3<abstract-float>)
+    core::BuiltinType::kFrexpResultVec4Abstract,  // return type of frexp(vec4<abstract-float>)
 };
 
 Struct* CreateFrexpResult(Manager& types, SymbolTable& symbols, const Type* ty) {
-    auto build = [&](core::Builtin name, const Type* fract_ty, const Type* exp_ty) {
+    auto build = [&](core::BuiltinType name, const Type* fract_ty, const Type* exp_ty) {
         return types.Struct(
             symbols.Register(tint::ToString(name)),
             {{symbols.Register("fract"), fract_ty}, {symbols.Register("exp"), exp_ty}});
     };
     return Switch(
         ty,  //
-        [&](const F32*) { return build(core::Builtin::kFrexpResultF32, ty, types.i32()); },
-        [&](const F16*) { return build(core::Builtin::kFrexpResultF16, ty, types.i32()); },
+        [&](const F32*) { return build(core::BuiltinType::kFrexpResultF32, ty, types.i32()); },
+        [&](const F16*) { return build(core::BuiltinType::kFrexpResultF16, ty, types.i32()); },
         [&](const AbstractFloat*) {
-            auto* abstract = build(core::Builtin::kFrexpResultAbstract, ty, types.AInt());
+            auto* abstract = build(core::BuiltinType::kFrexpResultAbstract, ty, types.AInt());
             abstract->SetConcreteTypes(tint::Vector{
-                build(core::Builtin::kFrexpResultF32, types.f32(), types.i32()),
-                build(core::Builtin::kFrexpResultF16, types.f16(), types.i32()),
+                build(core::BuiltinType::kFrexpResultF32, types.f32(), types.i32()),
+                build(core::BuiltinType::kFrexpResultF16, types.f16(), types.i32()),
             });
             return abstract;
         },
@@ -170,7 +170,7 @@
 }
 
 Struct* CreateAtomicCompareExchangeResult(Manager& types, SymbolTable& symbols, const Type* ty) {
-    auto build = [&](core::Builtin name) {
+    auto build = [&](core::BuiltinType name) {
         return types.Struct(symbols.Register(tint::ToString(name)),
                             {
                                 {symbols.Register("old_value"), ty},
@@ -179,8 +179,8 @@
     };
     return Switch(
         ty,  //
-        [&](const I32*) { return build(core::Builtin::kAtomicCompareExchangeResultI32); },
-        [&](const U32*) { return build(core::Builtin::kAtomicCompareExchangeResultU32); },
+        [&](const I32*) { return build(core::BuiltinType::kAtomicCompareExchangeResultI32); },
+        [&](const U32*) { return build(core::BuiltinType::kAtomicCompareExchangeResultU32); },
         [&](Default) {
             TINT_UNREACHABLE() << "unhandled atomic_compare_exchange type";
             return nullptr;
diff --git a/src/tint/lang/core/type/helper_test.h b/src/tint/lang/core/type/helper_test.h
index 7cb71bc..5babb08 100644
--- a/src/tint/lang/core/type/helper_test.h
+++ b/src/tint/lang/core/type/helper_test.h
@@ -30,9 +30,9 @@
     /// Builds and returns the program. Must only be called once per test
     /// @return the built program
     Program Build() {
-        [&] {
-            ASSERT_TRUE(IsValid()) << "Builder program is not valid\n" << Diagnostics().str();
-        }();
+        if (!IsValid()) {
+            ADD_FAILURE() << "ProgramBuilder is not valid: " << Diagnostics();
+        }
         return resolver::Resolve(*this);
     }
 };
diff --git a/src/tint/lang/core/type/storage_texture_test.cc b/src/tint/lang/core/type/storage_texture_test.cc
index 45f7776..c362090 100644
--- a/src/tint/lang/core/type/storage_texture_test.cc
+++ b/src/tint/lang/core/type/storage_texture_test.cc
@@ -101,7 +101,7 @@
 
     auto program = Build();
 
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
     ASSERT_TRUE(s->Is<Texture>());
     ASSERT_TRUE(s->Is<StorageTexture>());
     EXPECT_TRUE(s->As<StorageTexture>()->type()->Is<F32>());
@@ -114,7 +114,7 @@
 
     auto program = Build();
 
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
     ASSERT_TRUE(s->Is<Texture>());
     ASSERT_TRUE(s->Is<StorageTexture>());
     EXPECT_TRUE(s->As<StorageTexture>()->type()->Is<U32>());
@@ -127,7 +127,7 @@
 
     auto program = Build();
 
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
     ASSERT_TRUE(s->Is<Texture>());
     ASSERT_TRUE(s->Is<StorageTexture>());
     EXPECT_TRUE(s->As<StorageTexture>()->type()->Is<I32>());
diff --git a/src/tint/lang/core/type/struct_test.cc b/src/tint/lang/core/type/struct_test.cc
index 6a78e2a..ebb2209 100644
--- a/src/tint/lang/core/type/struct_test.cc
+++ b/src/tint/lang/core/type/struct_test.cc
@@ -65,7 +65,7 @@
                                         });
 
     auto p = Build();
-    ASSERT_TRUE(p.IsValid()) << p.Diagnostics().str();
+    ASSERT_TRUE(p.IsValid()) << p.Diagnostics();
 
     auto* sem_inner_st = p.Sem().Get(inner_st);
     auto* sem_outer_st = p.Sem().Get(outer_st);
@@ -96,7 +96,7 @@
                                });
 
     auto p = Build();
-    ASSERT_TRUE(p.IsValid()) << p.Diagnostics().str();
+    ASSERT_TRUE(p.IsValid()) << p.Diagnostics();
 
     auto* sem = p.Sem().Get(st);
     ASSERT_EQ(2u, sem->Members().Length());
@@ -127,7 +127,7 @@
                                                 Member("runtime_sized_array", ty.array<i32>()),
                                             });
     auto p = Build();
-    ASSERT_TRUE(p.IsValid()) << p.Diagnostics().str();
+    ASSERT_TRUE(p.IsValid()) << p.Diagnostics();
 
     auto* sem_inner = p.Sem().Get(inner);
     auto* sem_outer = p.Sem().Get(outer);
@@ -160,7 +160,7 @@
                                             });
 
     auto p = Build();
-    ASSERT_TRUE(p.IsValid()) << p.Diagnostics().str();
+    ASSERT_TRUE(p.IsValid()) << p.Diagnostics();
 
     auto* sem_inner = p.Sem().Get(inner);
     auto* sem_outer = p.Sem().Get(outer);
@@ -193,7 +193,7 @@
                                             });
 
     auto p = Build();
-    ASSERT_TRUE(p.IsValid()) << p.Diagnostics().str();
+    ASSERT_TRUE(p.IsValid()) << p.Diagnostics();
 
     auto* sem_inner = p.Sem().Get(inner);
     auto* sem_outer = p.Sem().Get(outer);
diff --git a/src/tint/lang/glsl/writer/ast_printer/ast_printer.cc b/src/tint/lang/glsl/writer/ast_printer/ast_printer.cc
index 839cdb1..89387ae 100644
--- a/src/tint/lang/glsl/writer/ast_printer/ast_printer.cc
+++ b/src/tint/lang/glsl/writer/ast_printer/ast_printer.cc
@@ -155,7 +155,7 @@
 SanitizedResult::~SanitizedResult() = default;
 SanitizedResult::SanitizedResult(SanitizedResult&&) = default;
 
-SanitizedResult Sanitize(const Program* in,
+SanitizedResult Sanitize(const Program& in,
                          const Options& options,
                          const std::string& entry_point) {
     ast::transform::Manager manager;
@@ -244,7 +244,9 @@
     manager.Add<CombineSamplers>();
 
     data.Add<ast::transform::BindingRemapper::Remappings>(
-        options.binding_points, options.access_controls, options.allow_collisions);
+        options.binding_remapper_options.binding_points,
+        options.binding_remapper_options.access_controls,
+        options.binding_remapper_options.allow_collisions);
     manager.Add<ast::transform::BindingRemapper>();
 
     manager.Add<ast::transform::PromoteInitializersToLet>();
@@ -271,7 +273,7 @@
     return result;
 }
 
-ASTPrinter::ASTPrinter(const Program* program, const Version& version)
+ASTPrinter::ASTPrinter(const Program& program, const Version& version)
     : builder_(ProgramBuilder::Wrap(program)), version_(version) {}
 
 ASTPrinter::~ASTPrinter() = default;
@@ -820,7 +822,7 @@
     Switch(
         call->Target(),  //
         [&](const sem::Function* fn) { EmitFunctionCall(out, call, fn); },
-        [&](const sem::Builtin* builtin) { EmitBuiltinCall(out, call, builtin); },
+        [&](const sem::BuiltinFn* builtin) { EmitBuiltinCall(out, call, builtin); },
         [&](const sem::ValueConversion* conv) { EmitValueConversion(out, call, conv); },
         [&](const sem::ValueConstructor* ctor) { EmitValueConstructor(out, call, ctor); },
         [&](Default) {
@@ -850,40 +852,39 @@
 
 void ASTPrinter::EmitBuiltinCall(StringStream& out,
                                  const sem::Call* call,
-                                 const sem::Builtin* builtin) {
+                                 const sem::BuiltinFn* builtin) {
     auto* expr = call->Declaration();
     if (builtin->IsTexture()) {
         EmitTextureCall(out, call, builtin);
-    } else if (builtin->Type() == core::Function::kCountOneBits) {
+    } else if (builtin->Fn() == core::BuiltinFn::kCountOneBits) {
         EmitCountOneBitsCall(out, expr);
-    } else if (builtin->Type() == core::Function::kSelect) {
+    } else if (builtin->Fn() == core::BuiltinFn::kSelect) {
         EmitSelectCall(out, expr, builtin);
-    } else if (builtin->Type() == core::Function::kDot) {
+    } else if (builtin->Fn() == core::BuiltinFn::kDot) {
         EmitDotCall(out, expr, builtin);
-    } else if (builtin->Type() == core::Function::kModf) {
+    } else if (builtin->Fn() == core::BuiltinFn::kModf) {
         EmitModfCall(out, expr, builtin);
-    } else if (builtin->Type() == core::Function::kFrexp) {
+    } else if (builtin->Fn() == core::BuiltinFn::kFrexp) {
         EmitFrexpCall(out, expr, builtin);
-    } else if (builtin->Type() == core::Function::kDegrees) {
+    } else if (builtin->Fn() == core::BuiltinFn::kDegrees) {
         EmitDegreesCall(out, expr, builtin);
-    } else if (builtin->Type() == core::Function::kRadians) {
+    } else if (builtin->Fn() == core::BuiltinFn::kRadians) {
         EmitRadiansCall(out, expr, builtin);
-    } else if (builtin->Type() == core::Function::kQuantizeToF16) {
+    } else if (builtin->Fn() == core::BuiltinFn::kQuantizeToF16) {
         EmitQuantizeToF16Call(out, expr, builtin);
-    } else if (builtin->Type() == core::Function::kArrayLength) {
+    } else if (builtin->Fn() == core::BuiltinFn::kArrayLength) {
         EmitArrayLength(out, expr);
-    } else if (builtin->Type() == core::Function::kExtractBits) {
+    } else if (builtin->Fn() == core::BuiltinFn::kExtractBits) {
         EmitExtractBits(out, expr);
-    } else if (builtin->Type() == core::Function::kInsertBits) {
+    } else if (builtin->Fn() == core::BuiltinFn::kInsertBits) {
         EmitInsertBits(out, expr);
-    } else if (builtin->Type() == core::Function::kFma && version_.IsES()) {
+    } else if (builtin->Fn() == core::BuiltinFn::kFma && version_.IsES()) {
         EmitEmulatedFMA(out, expr);
-    } else if (builtin->Type() == core::Function::kAbs &&
+    } else if (builtin->Fn() == core::BuiltinFn::kAbs &&
                TypeOf(expr->args[0])->UnwrapRef()->is_unsigned_integer_scalar_or_vector()) {
         // GLSL does not support abs() on unsigned arguments. However, it's a no-op.
         EmitExpression(out, expr->args[0]);
-    } else if ((builtin->Type() == core::Function::kAny ||
-                builtin->Type() == core::Function::kAll) &&
+    } else if ((builtin->Fn() == core::BuiltinFn::kAny || builtin->Fn() == core::BuiltinFn::kAll) &&
                TypeOf(expr->args[0])->UnwrapRef()->Is<core::type::Scalar>()) {
         // GLSL does not support any() or all() on scalar arguments. It's a no-op.
         EmitExpression(out, expr->args[0]);
@@ -948,7 +949,7 @@
 
 void ASTPrinter::EmitWorkgroupAtomicCall(StringStream& out,
                                          const ast::CallExpression* expr,
-                                         const sem::Builtin* builtin) {
+                                         const sem::BuiltinFn* builtin) {
     auto call = [&](const char* name) {
         out << name;
         {
@@ -964,8 +965,8 @@
         return;
     };
 
-    switch (builtin->Type()) {
-        case core::Function::kAtomicLoad: {
+    switch (builtin->Fn()) {
+        case core::BuiltinFn::kAtomicLoad: {
             // GLSL does not have an atomicLoad, so we emulate it with
             // atomicOr using 0 as the OR value
             out << "atomicOr";
@@ -979,7 +980,7 @@
             }
             return;
         }
-        case core::Function::kAtomicCompareExchangeWeak: {
+        case core::BuiltinFn::kAtomicCompareExchangeWeak: {
             EmitStructType(&helpers_, builtin->ReturnType()->As<core::type::Struct>());
 
             auto* dest = expr->args[0];
@@ -1018,33 +1019,33 @@
             return;
         }
 
-        case core::Function::kAtomicAdd:
-        case core::Function::kAtomicSub:
+        case core::BuiltinFn::kAtomicAdd:
+        case core::BuiltinFn::kAtomicSub:
             call("atomicAdd");
             return;
 
-        case core::Function::kAtomicMax:
+        case core::BuiltinFn::kAtomicMax:
             call("atomicMax");
             return;
 
-        case core::Function::kAtomicMin:
+        case core::BuiltinFn::kAtomicMin:
             call("atomicMin");
             return;
 
-        case core::Function::kAtomicAnd:
+        case core::BuiltinFn::kAtomicAnd:
             call("atomicAnd");
             return;
 
-        case core::Function::kAtomicOr:
+        case core::BuiltinFn::kAtomicOr:
             call("atomicOr");
             return;
 
-        case core::Function::kAtomicXor:
+        case core::BuiltinFn::kAtomicXor:
             call("atomicXor");
             return;
 
-        case core::Function::kAtomicExchange:
-        case core::Function::kAtomicStore:
+        case core::BuiltinFn::kAtomicExchange:
+        case core::BuiltinFn::kAtomicStore:
             // GLSL does not have an atomicStore, so we emulate it with
             // atomicExchange.
             call("atomicExchange");
@@ -1054,7 +1055,7 @@
             break;
     }
 
-    TINT_UNREACHABLE() << "unsupported atomic builtin: " << builtin->Type();
+    TINT_UNREACHABLE() << "unsupported atomic builtin: " << builtin->Fn();
 }
 
 void ASTPrinter::EmitArrayLength(StringStream& out, const ast::CallExpression* expr) {
@@ -1107,7 +1108,7 @@
 
 void ASTPrinter::EmitSelectCall(StringStream& out,
                                 const ast::CallExpression* expr,
-                                const sem::Builtin* builtin) {
+                                const sem::BuiltinFn* builtin) {
     // GLSL does not support ternary expressions with a bool vector conditional,
     // so polyfill with a helper.
     if (auto* vec = builtin->Parameters()[2]->Type()->As<core::type::Vector>()) {
@@ -1147,7 +1148,7 @@
 
 void ASTPrinter::EmitDotCall(StringStream& out,
                              const ast::CallExpression* expr,
-                             const sem::Builtin* builtin) {
+                             const sem::BuiltinFn* builtin) {
     auto* vec_ty = builtin->Parameters()[0]->Type()->As<core::type::Vector>();
     std::string fn = "dot";
     if (vec_ty->type()->is_integer_scalar()) {
@@ -1202,7 +1203,7 @@
 
 void ASTPrinter::EmitModfCall(StringStream& out,
                               const ast::CallExpression* expr,
-                              const sem::Builtin* builtin) {
+                              const sem::BuiltinFn* builtin) {
     TINT_ASSERT(expr->args.Length() == 1);
     CallBuiltinHelper(
         out, expr, builtin, [&](TextBuffer* b, const std::vector<std::string>& params) {
@@ -1223,7 +1224,7 @@
 
 void ASTPrinter::EmitFrexpCall(StringStream& out,
                                const ast::CallExpression* expr,
-                               const sem::Builtin* builtin) {
+                               const sem::BuiltinFn* builtin) {
     TINT_ASSERT(expr->args.Length() == 1);
     CallBuiltinHelper(
         out, expr, builtin, [&](TextBuffer* b, const std::vector<std::string>& params) {
@@ -1244,7 +1245,7 @@
 
 void ASTPrinter::EmitDegreesCall(StringStream& out,
                                  const ast::CallExpression* expr,
-                                 const sem::Builtin* builtin) {
+                                 const sem::BuiltinFn* builtin) {
     auto* return_elem_type = builtin->ReturnType()->DeepestElement();
     const std::string suffix = Is<core::type::F16>(return_elem_type) ? "hf" : "f";
     CallBuiltinHelper(out, expr, builtin,
@@ -1256,7 +1257,7 @@
 
 void ASTPrinter::EmitRadiansCall(StringStream& out,
                                  const ast::CallExpression* expr,
-                                 const sem::Builtin* builtin) {
+                                 const sem::BuiltinFn* builtin) {
     auto* return_elem_type = builtin->ReturnType()->DeepestElement();
     const std::string suffix = Is<core::type::F16>(return_elem_type) ? "hf" : "f";
     CallBuiltinHelper(out, expr, builtin,
@@ -1268,7 +1269,7 @@
 
 void ASTPrinter::EmitQuantizeToF16Call(StringStream& out,
                                        const ast::CallExpression* expr,
-                                       const sem::Builtin* builtin) {
+                                       const sem::BuiltinFn* builtin) {
     // Emulate by casting to f16 and back again.
     CallBuiltinHelper(
         out, expr, builtin, [&](TextBuffer* b, const std::vector<std::string>& params) {
@@ -1297,17 +1298,17 @@
         });
 }
 
-void ASTPrinter::EmitBarrierCall(StringStream& out, const sem::Builtin* builtin) {
+void ASTPrinter::EmitBarrierCall(StringStream& out, const sem::BuiltinFn* builtin) {
     // TODO(crbug.com/tint/661): Combine sequential barriers to a single
     // instruction.
-    if (builtin->Type() == core::Function::kWorkgroupBarrier) {
+    if (builtin->Fn() == core::BuiltinFn::kWorkgroupBarrier) {
         out << "barrier()";
-    } else if (builtin->Type() == core::Function::kStorageBarrier) {
+    } else if (builtin->Fn() == core::BuiltinFn::kStorageBarrier) {
         out << "{ barrier(); memoryBarrierBuffer(); }";
-    } else if (builtin->Type() == core::Function::kTextureBarrier) {
+    } else if (builtin->Fn() == core::BuiltinFn::kTextureBarrier) {
         out << "{ barrier(); memoryBarrierImage(); }";
     } else {
-        TINT_UNREACHABLE() << "unexpected barrier builtin type " << core::str(builtin->Type());
+        TINT_UNREACHABLE() << "unexpected barrier builtin type " << core::str(builtin->Fn());
     }
 }
 
@@ -1323,7 +1324,7 @@
 
 void ASTPrinter::EmitTextureCall(StringStream& out,
                                  const sem::Call* call,
-                                 const sem::Builtin* builtin) {
+                                 const sem::BuiltinFn* builtin) {
     using Usage = core::ParameterUsage;
 
     auto& signature = builtin->Signature();
@@ -1374,8 +1375,8 @@
         return;
     };
 
-    switch (builtin->Type()) {
-        case core::Function::kTextureDimensions: {
+    switch (builtin->Fn()) {
+        case core::BuiltinFn::kTextureDimensions: {
             // textureDimensions() returns an unsigned scalar / vector in WGSL.
             // textureSize() / imageSize() returns a signed scalar / vector in GLSL.
             // Cast.
@@ -1410,7 +1411,7 @@
             }
             return;
         }
-        case core::Function::kTextureNumLayers: {
+        case core::BuiltinFn::kTextureNumLayers: {
             // textureNumLayers() returns an unsigned scalar in WGSL.
             // textureSize() / imageSize() returns a signed scalar / vector in GLSL.
             // Cast.
@@ -1441,7 +1442,7 @@
             out << ").z";
             return;
         }
-        case core::Function::kTextureNumLevels: {
+        case core::BuiltinFn::kTextureNumLevels: {
             // textureNumLevels() returns an unsigned scalar in WGSL.
             // textureQueryLevels() returns a signed scalar in GLSL.
             // Cast.
@@ -1453,7 +1454,7 @@
             out << ")";
             return;
         }
-        case core::Function::kTextureNumSamples: {
+        case core::BuiltinFn::kTextureNumSamples: {
             // textureNumSamples() returns an unsigned scalar in WGSL.
             // textureSamples() returns a signed scalar in GLSL.
             // Cast.
@@ -1473,41 +1474,41 @@
     bool append_depth_ref_to_coords = true;
     bool is_depth = texture_type->Is<core::type::DepthTexture>();
 
-    switch (builtin->Type()) {
-        case core::Function::kTextureSample:
-        case core::Function::kTextureSampleBias:
+    switch (builtin->Fn()) {
+        case core::BuiltinFn::kTextureSample:
+        case core::BuiltinFn::kTextureSampleBias:
             out << "texture";
             if (is_depth) {
                 glsl_ret_width = 1u;
             }
             break;
-        case core::Function::kTextureSampleLevel:
+        case core::BuiltinFn::kTextureSampleLevel:
             out << "textureLod";
             if (is_depth) {
                 glsl_ret_width = 1u;
             }
             break;
-        case core::Function::kTextureGather:
-        case core::Function::kTextureGatherCompare:
+        case core::BuiltinFn::kTextureGather:
+        case core::BuiltinFn::kTextureGatherCompare:
             out << "textureGather";
             append_depth_ref_to_coords = false;
             break;
-        case core::Function::kTextureSampleGrad:
+        case core::BuiltinFn::kTextureSampleGrad:
             out << "textureGrad";
             break;
-        case core::Function::kTextureSampleCompare:
-        case core::Function::kTextureSampleCompareLevel:
+        case core::BuiltinFn::kTextureSampleCompare:
+        case core::BuiltinFn::kTextureSampleCompareLevel:
             out << "texture";
             glsl_ret_width = 1;
             break;
-        case core::Function::kTextureLoad:
+        case core::BuiltinFn::kTextureLoad:
             if (texture_type->Is<core::type::StorageTexture>()) {
                 out << "imageLoad";
             } else {
                 out << "texelFetch";
             }
             break;
-        case core::Function::kTextureStore:
+        case core::BuiltinFn::kTextureStore:
             out << "imageStore";
             break;
         default:
@@ -1578,7 +1579,7 @@
     }
 
     // GLSL's textureGather always requires a refZ parameter.
-    if (is_depth && builtin->Type() == core::Function::kTextureGather) {
+    if (is_depth && builtin->Fn() == core::BuiltinFn::kTextureGather) {
         out << ", 0.0";
     }
 
@@ -1587,7 +1588,7 @@
         if (auto* e = arg(Usage::kDepthRef)) {
             out << ", ";
             EmitExpression(out, e);
-        } else if (builtin->Type() == core::Function::kTextureSample) {
+        } else if (builtin->Fn() == core::BuiltinFn::kTextureSample) {
             out << ", 0.0f";
         }
     }
@@ -1620,121 +1621,121 @@
     if (TINT_UNLIKELY(wgsl_ret_width > glsl_ret_width)) {
         TINT_ICE() << "WGSL return width (" << wgsl_ret_width
                    << ") is wider than GLSL return width (" << glsl_ret_width << ") for "
-                   << builtin->Type();
+                   << builtin->Fn();
         return;
     }
 }
 
-std::string ASTPrinter::generate_builtin_name(const sem::Builtin* builtin) {
-    switch (builtin->Type()) {
-        case core::Function::kAbs:
-        case core::Function::kAcos:
-        case core::Function::kAcosh:
-        case core::Function::kAll:
-        case core::Function::kAny:
-        case core::Function::kAsin:
-        case core::Function::kAsinh:
-        case core::Function::kAtan:
-        case core::Function::kAtanh:
-        case core::Function::kCeil:
-        case core::Function::kClamp:
-        case core::Function::kCos:
-        case core::Function::kCosh:
-        case core::Function::kCross:
-        case core::Function::kDeterminant:
-        case core::Function::kDistance:
-        case core::Function::kDot:
-        case core::Function::kExp:
-        case core::Function::kExp2:
-        case core::Function::kFloor:
-        case core::Function::kFrexp:
-        case core::Function::kLdexp:
-        case core::Function::kLength:
-        case core::Function::kLog:
-        case core::Function::kLog2:
-        case core::Function::kMax:
-        case core::Function::kMin:
-        case core::Function::kModf:
-        case core::Function::kNormalize:
-        case core::Function::kPow:
-        case core::Function::kReflect:
-        case core::Function::kRefract:
-        case core::Function::kRound:
-        case core::Function::kSign:
-        case core::Function::kSin:
-        case core::Function::kSinh:
-        case core::Function::kSqrt:
-        case core::Function::kStep:
-        case core::Function::kTan:
-        case core::Function::kTanh:
-        case core::Function::kTranspose:
-        case core::Function::kTrunc:
+std::string ASTPrinter::generate_builtin_name(const sem::BuiltinFn* builtin) {
+    switch (builtin->Fn()) {
+        case core::BuiltinFn::kAbs:
+        case core::BuiltinFn::kAcos:
+        case core::BuiltinFn::kAcosh:
+        case core::BuiltinFn::kAll:
+        case core::BuiltinFn::kAny:
+        case core::BuiltinFn::kAsin:
+        case core::BuiltinFn::kAsinh:
+        case core::BuiltinFn::kAtan:
+        case core::BuiltinFn::kAtanh:
+        case core::BuiltinFn::kCeil:
+        case core::BuiltinFn::kClamp:
+        case core::BuiltinFn::kCos:
+        case core::BuiltinFn::kCosh:
+        case core::BuiltinFn::kCross:
+        case core::BuiltinFn::kDeterminant:
+        case core::BuiltinFn::kDistance:
+        case core::BuiltinFn::kDot:
+        case core::BuiltinFn::kExp:
+        case core::BuiltinFn::kExp2:
+        case core::BuiltinFn::kFloor:
+        case core::BuiltinFn::kFrexp:
+        case core::BuiltinFn::kLdexp:
+        case core::BuiltinFn::kLength:
+        case core::BuiltinFn::kLog:
+        case core::BuiltinFn::kLog2:
+        case core::BuiltinFn::kMax:
+        case core::BuiltinFn::kMin:
+        case core::BuiltinFn::kModf:
+        case core::BuiltinFn::kNormalize:
+        case core::BuiltinFn::kPow:
+        case core::BuiltinFn::kReflect:
+        case core::BuiltinFn::kRefract:
+        case core::BuiltinFn::kRound:
+        case core::BuiltinFn::kSign:
+        case core::BuiltinFn::kSin:
+        case core::BuiltinFn::kSinh:
+        case core::BuiltinFn::kSqrt:
+        case core::BuiltinFn::kStep:
+        case core::BuiltinFn::kTan:
+        case core::BuiltinFn::kTanh:
+        case core::BuiltinFn::kTranspose:
+        case core::BuiltinFn::kTrunc:
             return builtin->str();
-        case core::Function::kAtan2:
+        case core::BuiltinFn::kAtan2:
             return "atan";
-        case core::Function::kCountOneBits:
+        case core::BuiltinFn::kCountOneBits:
             return "bitCount";
-        case core::Function::kDpdx:
+        case core::BuiltinFn::kDpdx:
             return "dFdx";
-        case core::Function::kDpdxCoarse:
+        case core::BuiltinFn::kDpdxCoarse:
             if (version_.IsES()) {
                 return "dFdx";
             }
             return "dFdxCoarse";
-        case core::Function::kDpdxFine:
+        case core::BuiltinFn::kDpdxFine:
             if (version_.IsES()) {
                 return "dFdx";
             }
             return "dFdxFine";
-        case core::Function::kDpdy:
+        case core::BuiltinFn::kDpdy:
             return "dFdy";
-        case core::Function::kDpdyCoarse:
+        case core::BuiltinFn::kDpdyCoarse:
             if (version_.IsES()) {
                 return "dFdy";
             }
             return "dFdyCoarse";
-        case core::Function::kDpdyFine:
+        case core::BuiltinFn::kDpdyFine:
             if (version_.IsES()) {
                 return "dFdy";
             }
             return "dFdyFine";
-        case core::Function::kFaceForward:
+        case core::BuiltinFn::kFaceForward:
             return "faceforward";
-        case core::Function::kFract:
+        case core::BuiltinFn::kFract:
             return "fract";
-        case core::Function::kFma:
+        case core::BuiltinFn::kFma:
             return "fma";
-        case core::Function::kFwidth:
-        case core::Function::kFwidthCoarse:
-        case core::Function::kFwidthFine:
+        case core::BuiltinFn::kFwidth:
+        case core::BuiltinFn::kFwidthCoarse:
+        case core::BuiltinFn::kFwidthFine:
             return "fwidth";
-        case core::Function::kInverseSqrt:
+        case core::BuiltinFn::kInverseSqrt:
             return "inversesqrt";
-        case core::Function::kMix:
+        case core::BuiltinFn::kMix:
             return "mix";
-        case core::Function::kPack2X16Float:
+        case core::BuiltinFn::kPack2X16Float:
             return "packHalf2x16";
-        case core::Function::kPack2X16Snorm:
+        case core::BuiltinFn::kPack2X16Snorm:
             return "packSnorm2x16";
-        case core::Function::kPack2X16Unorm:
+        case core::BuiltinFn::kPack2X16Unorm:
             return "packUnorm2x16";
-        case core::Function::kPack4X8Snorm:
+        case core::BuiltinFn::kPack4X8Snorm:
             return "packSnorm4x8";
-        case core::Function::kPack4X8Unorm:
+        case core::BuiltinFn::kPack4X8Unorm:
             return "packUnorm4x8";
-        case core::Function::kReverseBits:
+        case core::BuiltinFn::kReverseBits:
             return "bitfieldReverse";
-        case core::Function::kSmoothstep:
+        case core::BuiltinFn::kSmoothstep:
             return "smoothstep";
-        case core::Function::kUnpack2X16Float:
+        case core::BuiltinFn::kUnpack2X16Float:
             return "unpackHalf2x16";
-        case core::Function::kUnpack2X16Snorm:
+        case core::BuiltinFn::kUnpack2X16Snorm:
             return "unpackSnorm2x16";
-        case core::Function::kUnpack2X16Unorm:
+        case core::BuiltinFn::kUnpack2X16Unorm:
             return "unpackUnorm2x16";
-        case core::Function::kUnpack4X8Snorm:
+        case core::BuiltinFn::kUnpack4X8Snorm:
             return "unpackSnorm4x8";
-        case core::Function::kUnpack4X8Unorm:
+        case core::BuiltinFn::kUnpack4X8Unorm:
             return "unpackUnorm4x8";
         default:
             diagnostics_.add_error(diag::System::Writer,
@@ -2956,14 +2957,14 @@
 template <typename F>
 void ASTPrinter::CallBuiltinHelper(StringStream& out,
                                    const ast::CallExpression* call,
-                                   const sem::Builtin* builtin,
+                                   const sem::BuiltinFn* builtin,
                                    F&& build) {
     // Generate the helper function if it hasn't been created already
     auto fn = tint::GetOrCreate(builtins_, builtin, [&]() -> std::string {
         TextBuffer b;
         TINT_DEFER(helpers_.Append(b));
 
-        auto fn_name = UniqueIdentifier(std::string("tint_") + core::str(builtin->Type()));
+        auto fn_name = UniqueIdentifier(std::string("tint_") + core::str(builtin->Fn()));
         std::vector<std::string> parameter_names;
         {
             auto decl = Line(&b);
diff --git a/src/tint/lang/glsl/writer/ast_printer/ast_printer.h b/src/tint/lang/glsl/writer/ast_printer/ast_printer.h
index ad44120..5d1fa1f 100644
--- a/src/tint/lang/glsl/writer/ast_printer/ast_printer.h
+++ b/src/tint/lang/glsl/writer/ast_printer/ast_printer.h
@@ -32,7 +32,7 @@
 
 // Forward declarations
 namespace tint::sem {
-class Builtin;
+class BuiltinFn;
 class Call;
 class ValueConstructor;
 class ValueConversion;
@@ -68,7 +68,7 @@
 /// @param options The HLSL generator options.
 /// @param entry_point the entry point to generate GLSL for
 /// @returns the sanitized program and any supplementary information
-SanitizedResult Sanitize(const Program* program,
+SanitizedResult Sanitize(const Program& program,
                          const Options& options,
                          const std::string& entry_point);
 
@@ -78,7 +78,7 @@
     /// Constructor
     /// @param program the program to generate
     /// @param version the GLSL version to use
-    ASTPrinter(const Program* program, const Version& version);
+    ASTPrinter(const Program& program, const Version& version);
     ~ASTPrinter() override;
 
     /// Generates the GLSL shader
@@ -143,7 +143,7 @@
     /// @param out the output of the expression stream
     /// @param call the call expression
     /// @param builtin the builtin being called
-    void EmitBuiltinCall(StringStream& out, const sem::Call* call, const sem::Builtin* builtin);
+    void EmitBuiltinCall(StringStream& out, const sem::Call* call, const sem::BuiltinFn* builtin);
     /// Handles generating a value conversion expression
     /// @param out the output of the expression stream
     /// @param call the call expression
@@ -161,14 +161,14 @@
     /// Handles generating a barrier builtin call
     /// @param out the output of the expression stream
     /// @param builtin the semantic information for the barrier builtin
-    void EmitBarrierCall(StringStream& out, const sem::Builtin* builtin);
+    void EmitBarrierCall(StringStream& out, const sem::BuiltinFn* builtin);
     /// Handles generating an atomic builtin call for a workgroup variable
     /// @param out the output of the expression stream
     /// @param expr the call expression
     /// @param builtin the semantic information for the atomic builtin
     void EmitWorkgroupAtomicCall(StringStream& out,
                                  const ast::CallExpression* expr,
-                                 const sem::Builtin* builtin);
+                                 const sem::BuiltinFn* builtin);
     /// Handles generating an array.length() call
     /// @param out the output of the expression stream
     /// @param expr the call expression
@@ -195,7 +195,7 @@
     /// @param out the output of the expression stream
     /// @param call the call expression
     /// @param builtin the semantic information for the texture builtin
-    void EmitTextureCall(StringStream& out, const sem::Call* call, const sem::Builtin* builtin);
+    void EmitTextureCall(StringStream& out, const sem::Call* call, const sem::BuiltinFn* builtin);
     /// Handles generating a call to the `select()` builtin
     /// @param out the output of the expression stream
     /// @param expr the call expression
@@ -206,49 +206,49 @@
     /// @param builtin the semantic information for the builtin
     void EmitSelectCall(StringStream& out,
                         const ast::CallExpression* expr,
-                        const sem::Builtin* builtin);
+                        const sem::BuiltinFn* builtin);
     /// Handles generating a call to the `dot()` builtin
     /// @param out the output of the expression stream
     /// @param expr the call expression
     /// @param builtin the semantic information for the builtin
     void EmitDotCall(StringStream& out,
                      const ast::CallExpression* expr,
-                     const sem::Builtin* builtin);
+                     const sem::BuiltinFn* builtin);
     /// Handles generating a call to the `modf()` builtin
     /// @param out the output of the expression stream
     /// @param expr the call expression
     /// @param builtin the semantic information for the builtin
     void EmitModfCall(StringStream& out,
                       const ast::CallExpression* expr,
-                      const sem::Builtin* builtin);
+                      const sem::BuiltinFn* builtin);
     /// Handles generating a call to the `frexp()` builtin
     /// @param out the output of the expression stream
     /// @param expr the call expression
     /// @param builtin the semantic information for the builtin
     void EmitFrexpCall(StringStream& out,
                        const ast::CallExpression* expr,
-                       const sem::Builtin* builtin);
+                       const sem::BuiltinFn* builtin);
     /// Handles generating a call to the `degrees()` builtin
     /// @param out the output of the expression stream
     /// @param expr the call expression
     /// @param builtin the semantic information for the builtin
     void EmitDegreesCall(StringStream& out,
                          const ast::CallExpression* expr,
-                         const sem::Builtin* builtin);
+                         const sem::BuiltinFn* builtin);
     /// Handles generating a call to the `radians()` builtin
     /// @param out the output of the expression stream
     /// @param expr the call expression
     /// @param builtin the semantic information for the builtin
     void EmitRadiansCall(StringStream& out,
                          const ast::CallExpression* expr,
-                         const sem::Builtin* builtin);
+                         const sem::BuiltinFn* builtin);
     /// Handles generating a call to the `quantizeToF16()` intrinsic
     /// @param out the output of the expression stream
     /// @param expr the call expression
     /// @param builtin the semantic information for the builtin
     void EmitQuantizeToF16Call(StringStream& out,
                                const ast::CallExpression* expr,
-                               const sem::Builtin* builtin);
+                               const sem::BuiltinFn* builtin);
     /// Handles a case statement
     /// @param stmt the statement
     void EmitCase(const ast::CaseStatement* stmt);
@@ -402,7 +402,7 @@
     /// Handles generating a builtin method name
     /// @param builtin the semantic info for the builtin
     /// @returns the name or "" if not valid
-    std::string generate_builtin_name(const sem::Builtin* builtin);
+    std::string generate_builtin_name(const sem::BuiltinFn* builtin);
     /// Converts a builtin to a gl_ string
     /// @param builtin the builtin to convert
     /// @param stage pipeline stage in which this builtin is used
@@ -440,7 +440,7 @@
     template <typename F>
     void CallBuiltinHelper(StringStream& out,
                            const ast::CallExpression* call,
-                           const sem::Builtin* builtin,
+                           const sem::BuiltinFn* builtin,
                            F&& build);
 
     /// Create a uint type corresponding to the given bool or bool vector type.
@@ -473,7 +473,7 @@
     /// Map of builtin structure to unique generated name
     std::unordered_map<const core::type::Struct*, std::string> builtin_struct_names_;
     std::function<void()> emit_continuing_;
-    std::unordered_map<const sem::Builtin*, std::string> builtins_;
+    std::unordered_map<const sem::BuiltinFn*, std::string> builtins_;
     std::unordered_map<const core::type::Vector*, std::string> dynamic_vector_write_;
     std::unordered_map<const core::type::Vector*, std::string> int_dot_funcs_;
     std::unordered_map<BinaryOperandType, std::string> float_modulo_funcs_;
diff --git a/src/tint/lang/glsl/writer/ast_printer/ast_printer_test.cc b/src/tint/lang/glsl/writer/ast_printer/ast_printer_test.cc
index d0445c3..fe63847 100644
--- a/src/tint/lang/glsl/writer/ast_printer/ast_printer_test.cc
+++ b/src/tint/lang/glsl/writer/ast_printer/ast_printer_test.cc
@@ -24,9 +24,9 @@
 TEST_F(GlslASTPrinterTest, InvalidProgram) {
     Diagnostics().add_error(diag::System::Writer, "make the program invalid");
     ASSERT_FALSE(IsValid());
-    auto program = std::make_unique<Program>(resolver::Resolve(*this));
-    ASSERT_FALSE(program->IsValid());
-    auto result = Generate(program.get(), Options{}, "");
+    auto program = resolver::Resolve(*this);
+    ASSERT_FALSE(program.IsValid());
+    auto result = Generate(program, Options{}, "");
     EXPECT_FALSE(result);
     EXPECT_EQ(result.Failure(), "input program is not valid");
 }
diff --git a/src/tint/lang/glsl/writer/ast_printer/builtin_test.cc b/src/tint/lang/glsl/writer/ast_printer/builtin_test.cc
index 2694b2c..c23b308 100644
--- a/src/tint/lang/glsl/writer/ast_printer/builtin_test.cc
+++ b/src/tint/lang/glsl/writer/ast_printer/builtin_test.cc
@@ -36,7 +36,7 @@
 };
 
 struct BuiltinData {
-    core::Function builtin;
+    core::BuiltinFn builtin;
     CallParamType type;
     const char* glsl_name;
 };
@@ -60,86 +60,86 @@
     return out;
 }
 
-const ast::CallExpression* GenerateCall(core::Function builtin,
+const ast::CallExpression* GenerateCall(core::BuiltinFn builtin,
                                         CallParamType type,
                                         ProgramBuilder* builder) {
     std::string name;
     StringStream str;
     str << name << builtin;
     switch (builtin) {
-        case core::Function::kAcos:
-        case core::Function::kAsin:
-        case core::Function::kAtan:
-        case core::Function::kCeil:
-        case core::Function::kCos:
-        case core::Function::kCosh:
-        case core::Function::kDpdx:
-        case core::Function::kDpdxCoarse:
-        case core::Function::kDpdxFine:
-        case core::Function::kDpdy:
-        case core::Function::kDpdyCoarse:
-        case core::Function::kDpdyFine:
-        case core::Function::kExp:
-        case core::Function::kExp2:
-        case core::Function::kFloor:
-        case core::Function::kFract:
-        case core::Function::kFwidth:
-        case core::Function::kFwidthCoarse:
-        case core::Function::kFwidthFine:
-        case core::Function::kInverseSqrt:
-        case core::Function::kLength:
-        case core::Function::kLog:
-        case core::Function::kLog2:
-        case core::Function::kNormalize:
-        case core::Function::kRound:
-        case core::Function::kSin:
-        case core::Function::kSinh:
-        case core::Function::kSqrt:
-        case core::Function::kTan:
-        case core::Function::kTanh:
-        case core::Function::kTrunc:
-        case core::Function::kSign:
+        case core::BuiltinFn::kAcos:
+        case core::BuiltinFn::kAsin:
+        case core::BuiltinFn::kAtan:
+        case core::BuiltinFn::kCeil:
+        case core::BuiltinFn::kCos:
+        case core::BuiltinFn::kCosh:
+        case core::BuiltinFn::kDpdx:
+        case core::BuiltinFn::kDpdxCoarse:
+        case core::BuiltinFn::kDpdxFine:
+        case core::BuiltinFn::kDpdy:
+        case core::BuiltinFn::kDpdyCoarse:
+        case core::BuiltinFn::kDpdyFine:
+        case core::BuiltinFn::kExp:
+        case core::BuiltinFn::kExp2:
+        case core::BuiltinFn::kFloor:
+        case core::BuiltinFn::kFract:
+        case core::BuiltinFn::kFwidth:
+        case core::BuiltinFn::kFwidthCoarse:
+        case core::BuiltinFn::kFwidthFine:
+        case core::BuiltinFn::kInverseSqrt:
+        case core::BuiltinFn::kLength:
+        case core::BuiltinFn::kLog:
+        case core::BuiltinFn::kLog2:
+        case core::BuiltinFn::kNormalize:
+        case core::BuiltinFn::kRound:
+        case core::BuiltinFn::kSin:
+        case core::BuiltinFn::kSinh:
+        case core::BuiltinFn::kSqrt:
+        case core::BuiltinFn::kTan:
+        case core::BuiltinFn::kTanh:
+        case core::BuiltinFn::kTrunc:
+        case core::BuiltinFn::kSign:
             if (type == CallParamType::kF16) {
                 return builder->Call(str.str(), "h2");
             } else {
                 return builder->Call(str.str(), "f2");
             }
-        case core::Function::kLdexp:
+        case core::BuiltinFn::kLdexp:
             if (type == CallParamType::kF16) {
                 return builder->Call(str.str(), "h2", "i2");
             } else {
                 return builder->Call(str.str(), "f2", "i2");
             }
-        case core::Function::kAtan2:
-        case core::Function::kDot:
-        case core::Function::kDistance:
-        case core::Function::kPow:
-        case core::Function::kReflect:
-        case core::Function::kStep:
+        case core::BuiltinFn::kAtan2:
+        case core::BuiltinFn::kDot:
+        case core::BuiltinFn::kDistance:
+        case core::BuiltinFn::kPow:
+        case core::BuiltinFn::kReflect:
+        case core::BuiltinFn::kStep:
             if (type == CallParamType::kF16) {
                 return builder->Call(str.str(), "h2", "h2");
             } else {
                 return builder->Call(str.str(), "f2", "f2");
             }
-        case core::Function::kCross:
+        case core::BuiltinFn::kCross:
             if (type == CallParamType::kF16) {
                 return builder->Call(str.str(), "h3", "h3");
             } else {
                 return builder->Call(str.str(), "f3", "f3");
             }
-        case core::Function::kFma:
-        case core::Function::kMix:
-        case core::Function::kFaceForward:
-        case core::Function::kSmoothstep:
+        case core::BuiltinFn::kFma:
+        case core::BuiltinFn::kMix:
+        case core::BuiltinFn::kFaceForward:
+        case core::BuiltinFn::kSmoothstep:
             if (type == CallParamType::kF16) {
                 return builder->Call(str.str(), "h2", "h2", "h2");
             } else {
                 return builder->Call(str.str(), "f2", "f2", "f2");
             }
-        case core::Function::kAll:
-        case core::Function::kAny:
+        case core::BuiltinFn::kAll:
+        case core::BuiltinFn::kAny:
             return builder->Call(str.str(), "b2");
-        case core::Function::kAbs:
+        case core::BuiltinFn::kAbs:
             if (type == CallParamType::kF32) {
                 return builder->Call(str.str(), "f2");
             } else if (type == CallParamType::kF16) {
@@ -147,11 +147,11 @@
             } else {
                 return builder->Call(str.str(), "u2");
             }
-        case core::Function::kCountOneBits:
-        case core::Function::kReverseBits:
+        case core::BuiltinFn::kCountOneBits:
+        case core::BuiltinFn::kReverseBits:
             return builder->Call(str.str(), "u2");
-        case core::Function::kMax:
-        case core::Function::kMin:
+        case core::BuiltinFn::kMax:
+        case core::BuiltinFn::kMin:
             if (type == CallParamType::kF32) {
                 return builder->Call(str.str(), "f2", "f2");
             } else if (type == CallParamType::kF16) {
@@ -159,7 +159,7 @@
             } else {
                 return builder->Call(str.str(), "u2", "u2");
             }
-        case core::Function::kClamp:
+        case core::BuiltinFn::kClamp:
             if (type == CallParamType::kF32) {
                 return builder->Call(str.str(), "f2", "f2", "f2");
             } else if (type == CallParamType::kF16) {
@@ -167,19 +167,19 @@
             } else {
                 return builder->Call(str.str(), "u2", "u2", "u2");
             }
-        case core::Function::kSelect:
+        case core::BuiltinFn::kSelect:
             if (type == CallParamType::kF16) {
                 return builder->Call(str.str(), "h2", "h2", "b2");
             } else {
                 return builder->Call(str.str(), "f2", "f2", "b2");
             }
-        case core::Function::kDeterminant:
+        case core::BuiltinFn::kDeterminant:
             if (type == CallParamType::kF16) {
                 return builder->Call(str.str(), "hm2x2");
             } else {
                 return builder->Call(str.str(), "m2x2");
             }
-        case core::Function::kTranspose:
+        case core::BuiltinFn::kTranspose:
             if (type == CallParamType::kF16) {
                 return builder->Call(str.str(), "hm3x2");
             } else {
@@ -225,7 +225,7 @@
     ASSERT_NE(sem, nullptr);
     auto* target = sem->Target();
     ASSERT_NE(target, nullptr);
-    auto* builtin = target->As<sem::Builtin>();
+    auto* builtin = target->As<sem::BuiltinFn>();
     ASSERT_NE(builtin, nullptr);
 
     EXPECT_EQ(gen.generate_builtin_name(builtin), param.glsl_name);
@@ -234,110 +234,110 @@
     GlslASTPrinterTest_Builtin,
     GlslBuiltinTest,
     testing::Values(/* Logical built-in */
-                    BuiltinData{core::Function::kAll, CallParamType::kBool, "all"},
-                    BuiltinData{core::Function::kAny, CallParamType::kBool, "any"},
+                    BuiltinData{core::BuiltinFn::kAll, CallParamType::kBool, "all"},
+                    BuiltinData{core::BuiltinFn::kAny, CallParamType::kBool, "any"},
                     /* Float built-in */
-                    BuiltinData{core::Function::kAbs, CallParamType::kF32, "abs"},
-                    BuiltinData{core::Function::kAbs, CallParamType::kF16, "abs"},
-                    BuiltinData{core::Function::kAcos, CallParamType::kF32, "acos"},
-                    BuiltinData{core::Function::kAcos, CallParamType::kF16, "acos"},
-                    BuiltinData{core::Function::kAsin, CallParamType::kF32, "asin"},
-                    BuiltinData{core::Function::kAsin, CallParamType::kF16, "asin"},
-                    BuiltinData{core::Function::kAtan, CallParamType::kF32, "atan"},
-                    BuiltinData{core::Function::kAtan, CallParamType::kF16, "atan"},
-                    BuiltinData{core::Function::kAtan2, CallParamType::kF32, "atan"},
-                    BuiltinData{core::Function::kAtan2, CallParamType::kF16, "atan"},
-                    BuiltinData{core::Function::kCeil, CallParamType::kF32, "ceil"},
-                    BuiltinData{core::Function::kCeil, CallParamType::kF16, "ceil"},
-                    BuiltinData{core::Function::kClamp, CallParamType::kF32, "clamp"},
-                    BuiltinData{core::Function::kClamp, CallParamType::kF16, "clamp"},
-                    BuiltinData{core::Function::kCos, CallParamType::kF32, "cos"},
-                    BuiltinData{core::Function::kCos, CallParamType::kF16, "cos"},
-                    BuiltinData{core::Function::kCosh, CallParamType::kF32, "cosh"},
-                    BuiltinData{core::Function::kCosh, CallParamType::kF16, "cosh"},
-                    BuiltinData{core::Function::kCross, CallParamType::kF32, "cross"},
-                    BuiltinData{core::Function::kCross, CallParamType::kF16, "cross"},
-                    BuiltinData{core::Function::kDistance, CallParamType::kF32, "distance"},
-                    BuiltinData{core::Function::kDistance, CallParamType::kF16, "distance"},
-                    BuiltinData{core::Function::kExp, CallParamType::kF32, "exp"},
-                    BuiltinData{core::Function::kExp, CallParamType::kF16, "exp"},
-                    BuiltinData{core::Function::kExp2, CallParamType::kF32, "exp2"},
-                    BuiltinData{core::Function::kExp2, CallParamType::kF16, "exp2"},
-                    BuiltinData{core::Function::kFaceForward, CallParamType::kF32, "faceforward"},
-                    BuiltinData{core::Function::kFaceForward, CallParamType::kF16, "faceforward"},
-                    BuiltinData{core::Function::kFloor, CallParamType::kF32, "floor"},
-                    BuiltinData{core::Function::kFloor, CallParamType::kF16, "floor"},
-                    BuiltinData{core::Function::kFma, CallParamType::kF32, "fma"},
-                    BuiltinData{core::Function::kFma, CallParamType::kF16, "fma"},
-                    BuiltinData{core::Function::kFract, CallParamType::kF32, "fract"},
-                    BuiltinData{core::Function::kFract, CallParamType::kF16, "fract"},
-                    BuiltinData{core::Function::kInverseSqrt, CallParamType::kF32, "inversesqrt"},
-                    BuiltinData{core::Function::kInverseSqrt, CallParamType::kF16, "inversesqrt"},
-                    BuiltinData{core::Function::kLdexp, CallParamType::kF32, "ldexp"},
-                    BuiltinData{core::Function::kLdexp, CallParamType::kF16, "ldexp"},
-                    BuiltinData{core::Function::kLength, CallParamType::kF32, "length"},
-                    BuiltinData{core::Function::kLength, CallParamType::kF16, "length"},
-                    BuiltinData{core::Function::kLog, CallParamType::kF32, "log"},
-                    BuiltinData{core::Function::kLog, CallParamType::kF16, "log"},
-                    BuiltinData{core::Function::kLog2, CallParamType::kF32, "log2"},
-                    BuiltinData{core::Function::kLog2, CallParamType::kF16, "log2"},
-                    BuiltinData{core::Function::kMax, CallParamType::kF32, "max"},
-                    BuiltinData{core::Function::kMax, CallParamType::kF16, "max"},
-                    BuiltinData{core::Function::kMin, CallParamType::kF32, "min"},
-                    BuiltinData{core::Function::kMin, CallParamType::kF16, "min"},
-                    BuiltinData{core::Function::kMix, CallParamType::kF32, "mix"},
-                    BuiltinData{core::Function::kMix, CallParamType::kF16, "mix"},
-                    BuiltinData{core::Function::kNormalize, CallParamType::kF32, "normalize"},
-                    BuiltinData{core::Function::kNormalize, CallParamType::kF16, "normalize"},
-                    BuiltinData{core::Function::kPow, CallParamType::kF32, "pow"},
-                    BuiltinData{core::Function::kPow, CallParamType::kF16, "pow"},
-                    BuiltinData{core::Function::kReflect, CallParamType::kF32, "reflect"},
-                    BuiltinData{core::Function::kReflect, CallParamType::kF16, "reflect"},
-                    BuiltinData{core::Function::kSign, CallParamType::kF32, "sign"},
-                    BuiltinData{core::Function::kSign, CallParamType::kF16, "sign"},
-                    BuiltinData{core::Function::kSin, CallParamType::kF32, "sin"},
-                    BuiltinData{core::Function::kSin, CallParamType::kF16, "sin"},
-                    BuiltinData{core::Function::kSinh, CallParamType::kF32, "sinh"},
-                    BuiltinData{core::Function::kSinh, CallParamType::kF16, "sinh"},
-                    BuiltinData{core::Function::kSmoothstep, CallParamType::kF32, "smoothstep"},
-                    BuiltinData{core::Function::kSmoothstep, CallParamType::kF16, "smoothstep"},
-                    BuiltinData{core::Function::kSqrt, CallParamType::kF32, "sqrt"},
-                    BuiltinData{core::Function::kSqrt, CallParamType::kF16, "sqrt"},
-                    BuiltinData{core::Function::kStep, CallParamType::kF32, "step"},
-                    BuiltinData{core::Function::kStep, CallParamType::kF16, "step"},
-                    BuiltinData{core::Function::kTan, CallParamType::kF32, "tan"},
-                    BuiltinData{core::Function::kTan, CallParamType::kF16, "tan"},
-                    BuiltinData{core::Function::kTanh, CallParamType::kF32, "tanh"},
-                    BuiltinData{core::Function::kTanh, CallParamType::kF16, "tanh"},
-                    BuiltinData{core::Function::kTrunc, CallParamType::kF32, "trunc"},
-                    BuiltinData{core::Function::kTrunc, CallParamType::kF16, "trunc"},
+                    BuiltinData{core::BuiltinFn::kAbs, CallParamType::kF32, "abs"},
+                    BuiltinData{core::BuiltinFn::kAbs, CallParamType::kF16, "abs"},
+                    BuiltinData{core::BuiltinFn::kAcos, CallParamType::kF32, "acos"},
+                    BuiltinData{core::BuiltinFn::kAcos, CallParamType::kF16, "acos"},
+                    BuiltinData{core::BuiltinFn::kAsin, CallParamType::kF32, "asin"},
+                    BuiltinData{core::BuiltinFn::kAsin, CallParamType::kF16, "asin"},
+                    BuiltinData{core::BuiltinFn::kAtan, CallParamType::kF32, "atan"},
+                    BuiltinData{core::BuiltinFn::kAtan, CallParamType::kF16, "atan"},
+                    BuiltinData{core::BuiltinFn::kAtan2, CallParamType::kF32, "atan"},
+                    BuiltinData{core::BuiltinFn::kAtan2, CallParamType::kF16, "atan"},
+                    BuiltinData{core::BuiltinFn::kCeil, CallParamType::kF32, "ceil"},
+                    BuiltinData{core::BuiltinFn::kCeil, CallParamType::kF16, "ceil"},
+                    BuiltinData{core::BuiltinFn::kClamp, CallParamType::kF32, "clamp"},
+                    BuiltinData{core::BuiltinFn::kClamp, CallParamType::kF16, "clamp"},
+                    BuiltinData{core::BuiltinFn::kCos, CallParamType::kF32, "cos"},
+                    BuiltinData{core::BuiltinFn::kCos, CallParamType::kF16, "cos"},
+                    BuiltinData{core::BuiltinFn::kCosh, CallParamType::kF32, "cosh"},
+                    BuiltinData{core::BuiltinFn::kCosh, CallParamType::kF16, "cosh"},
+                    BuiltinData{core::BuiltinFn::kCross, CallParamType::kF32, "cross"},
+                    BuiltinData{core::BuiltinFn::kCross, CallParamType::kF16, "cross"},
+                    BuiltinData{core::BuiltinFn::kDistance, CallParamType::kF32, "distance"},
+                    BuiltinData{core::BuiltinFn::kDistance, CallParamType::kF16, "distance"},
+                    BuiltinData{core::BuiltinFn::kExp, CallParamType::kF32, "exp"},
+                    BuiltinData{core::BuiltinFn::kExp, CallParamType::kF16, "exp"},
+                    BuiltinData{core::BuiltinFn::kExp2, CallParamType::kF32, "exp2"},
+                    BuiltinData{core::BuiltinFn::kExp2, CallParamType::kF16, "exp2"},
+                    BuiltinData{core::BuiltinFn::kFaceForward, CallParamType::kF32, "faceforward"},
+                    BuiltinData{core::BuiltinFn::kFaceForward, CallParamType::kF16, "faceforward"},
+                    BuiltinData{core::BuiltinFn::kFloor, CallParamType::kF32, "floor"},
+                    BuiltinData{core::BuiltinFn::kFloor, CallParamType::kF16, "floor"},
+                    BuiltinData{core::BuiltinFn::kFma, CallParamType::kF32, "fma"},
+                    BuiltinData{core::BuiltinFn::kFma, CallParamType::kF16, "fma"},
+                    BuiltinData{core::BuiltinFn::kFract, CallParamType::kF32, "fract"},
+                    BuiltinData{core::BuiltinFn::kFract, CallParamType::kF16, "fract"},
+                    BuiltinData{core::BuiltinFn::kInverseSqrt, CallParamType::kF32, "inversesqrt"},
+                    BuiltinData{core::BuiltinFn::kInverseSqrt, CallParamType::kF16, "inversesqrt"},
+                    BuiltinData{core::BuiltinFn::kLdexp, CallParamType::kF32, "ldexp"},
+                    BuiltinData{core::BuiltinFn::kLdexp, CallParamType::kF16, "ldexp"},
+                    BuiltinData{core::BuiltinFn::kLength, CallParamType::kF32, "length"},
+                    BuiltinData{core::BuiltinFn::kLength, CallParamType::kF16, "length"},
+                    BuiltinData{core::BuiltinFn::kLog, CallParamType::kF32, "log"},
+                    BuiltinData{core::BuiltinFn::kLog, CallParamType::kF16, "log"},
+                    BuiltinData{core::BuiltinFn::kLog2, CallParamType::kF32, "log2"},
+                    BuiltinData{core::BuiltinFn::kLog2, CallParamType::kF16, "log2"},
+                    BuiltinData{core::BuiltinFn::kMax, CallParamType::kF32, "max"},
+                    BuiltinData{core::BuiltinFn::kMax, CallParamType::kF16, "max"},
+                    BuiltinData{core::BuiltinFn::kMin, CallParamType::kF32, "min"},
+                    BuiltinData{core::BuiltinFn::kMin, CallParamType::kF16, "min"},
+                    BuiltinData{core::BuiltinFn::kMix, CallParamType::kF32, "mix"},
+                    BuiltinData{core::BuiltinFn::kMix, CallParamType::kF16, "mix"},
+                    BuiltinData{core::BuiltinFn::kNormalize, CallParamType::kF32, "normalize"},
+                    BuiltinData{core::BuiltinFn::kNormalize, CallParamType::kF16, "normalize"},
+                    BuiltinData{core::BuiltinFn::kPow, CallParamType::kF32, "pow"},
+                    BuiltinData{core::BuiltinFn::kPow, CallParamType::kF16, "pow"},
+                    BuiltinData{core::BuiltinFn::kReflect, CallParamType::kF32, "reflect"},
+                    BuiltinData{core::BuiltinFn::kReflect, CallParamType::kF16, "reflect"},
+                    BuiltinData{core::BuiltinFn::kSign, CallParamType::kF32, "sign"},
+                    BuiltinData{core::BuiltinFn::kSign, CallParamType::kF16, "sign"},
+                    BuiltinData{core::BuiltinFn::kSin, CallParamType::kF32, "sin"},
+                    BuiltinData{core::BuiltinFn::kSin, CallParamType::kF16, "sin"},
+                    BuiltinData{core::BuiltinFn::kSinh, CallParamType::kF32, "sinh"},
+                    BuiltinData{core::BuiltinFn::kSinh, CallParamType::kF16, "sinh"},
+                    BuiltinData{core::BuiltinFn::kSmoothstep, CallParamType::kF32, "smoothstep"},
+                    BuiltinData{core::BuiltinFn::kSmoothstep, CallParamType::kF16, "smoothstep"},
+                    BuiltinData{core::BuiltinFn::kSqrt, CallParamType::kF32, "sqrt"},
+                    BuiltinData{core::BuiltinFn::kSqrt, CallParamType::kF16, "sqrt"},
+                    BuiltinData{core::BuiltinFn::kStep, CallParamType::kF32, "step"},
+                    BuiltinData{core::BuiltinFn::kStep, CallParamType::kF16, "step"},
+                    BuiltinData{core::BuiltinFn::kTan, CallParamType::kF32, "tan"},
+                    BuiltinData{core::BuiltinFn::kTan, CallParamType::kF16, "tan"},
+                    BuiltinData{core::BuiltinFn::kTanh, CallParamType::kF32, "tanh"},
+                    BuiltinData{core::BuiltinFn::kTanh, CallParamType::kF16, "tanh"},
+                    BuiltinData{core::BuiltinFn::kTrunc, CallParamType::kF32, "trunc"},
+                    BuiltinData{core::BuiltinFn::kTrunc, CallParamType::kF16, "trunc"},
                     /* Integer built-in */
-                    BuiltinData{core::Function::kAbs, CallParamType::kU32, "abs"},
-                    BuiltinData{core::Function::kClamp, CallParamType::kU32, "clamp"},
-                    BuiltinData{core::Function::kCountOneBits, CallParamType::kU32, "bitCount"},
-                    BuiltinData{core::Function::kMax, CallParamType::kU32, "max"},
-                    BuiltinData{core::Function::kMin, CallParamType::kU32, "min"},
-                    BuiltinData{core::Function::kReverseBits, CallParamType::kU32,
+                    BuiltinData{core::BuiltinFn::kAbs, CallParamType::kU32, "abs"},
+                    BuiltinData{core::BuiltinFn::kClamp, CallParamType::kU32, "clamp"},
+                    BuiltinData{core::BuiltinFn::kCountOneBits, CallParamType::kU32, "bitCount"},
+                    BuiltinData{core::BuiltinFn::kMax, CallParamType::kU32, "max"},
+                    BuiltinData{core::BuiltinFn::kMin, CallParamType::kU32, "min"},
+                    BuiltinData{core::BuiltinFn::kReverseBits, CallParamType::kU32,
                                 "bitfieldReverse"},
-                    BuiltinData{core::Function::kRound, CallParamType::kU32, "round"},
+                    BuiltinData{core::BuiltinFn::kRound, CallParamType::kU32, "round"},
                     /* Matrix built-in */
-                    BuiltinData{core::Function::kDeterminant, CallParamType::kF32, "determinant"},
-                    BuiltinData{core::Function::kDeterminant, CallParamType::kF16, "determinant"},
-                    BuiltinData{core::Function::kTranspose, CallParamType::kF32, "transpose"},
-                    BuiltinData{core::Function::kTranspose, CallParamType::kF16, "transpose"},
+                    BuiltinData{core::BuiltinFn::kDeterminant, CallParamType::kF32, "determinant"},
+                    BuiltinData{core::BuiltinFn::kDeterminant, CallParamType::kF16, "determinant"},
+                    BuiltinData{core::BuiltinFn::kTranspose, CallParamType::kF32, "transpose"},
+                    BuiltinData{core::BuiltinFn::kTranspose, CallParamType::kF16, "transpose"},
                     /* Vector built-in */
-                    BuiltinData{core::Function::kDot, CallParamType::kF32, "dot"},
-                    BuiltinData{core::Function::kDot, CallParamType::kF16, "dot"},
+                    BuiltinData{core::BuiltinFn::kDot, CallParamType::kF32, "dot"},
+                    BuiltinData{core::BuiltinFn::kDot, CallParamType::kF16, "dot"},
                     /* Derivate built-in */
-                    BuiltinData{core::Function::kDpdx, CallParamType::kF32, "dFdx"},
-                    BuiltinData{core::Function::kDpdxCoarse, CallParamType::kF32, "dFdx"},
-                    BuiltinData{core::Function::kDpdxFine, CallParamType::kF32, "dFdx"},
-                    BuiltinData{core::Function::kDpdy, CallParamType::kF32, "dFdy"},
-                    BuiltinData{core::Function::kDpdyCoarse, CallParamType::kF32, "dFdy"},
-                    BuiltinData{core::Function::kDpdyFine, CallParamType::kF32, "dFdy"},
-                    BuiltinData{core::Function::kFwidth, CallParamType::kF32, "fwidth"},
-                    BuiltinData{core::Function::kFwidthCoarse, CallParamType::kF32, "fwidth"},
-                    BuiltinData{core::Function::kFwidthFine, CallParamType::kF32, "fwidth"}));
+                    BuiltinData{core::BuiltinFn::kDpdx, CallParamType::kF32, "dFdx"},
+                    BuiltinData{core::BuiltinFn::kDpdxCoarse, CallParamType::kF32, "dFdx"},
+                    BuiltinData{core::BuiltinFn::kDpdxFine, CallParamType::kF32, "dFdx"},
+                    BuiltinData{core::BuiltinFn::kDpdy, CallParamType::kF32, "dFdy"},
+                    BuiltinData{core::BuiltinFn::kDpdyCoarse, CallParamType::kF32, "dFdy"},
+                    BuiltinData{core::BuiltinFn::kDpdyFine, CallParamType::kF32, "dFdy"},
+                    BuiltinData{core::BuiltinFn::kFwidth, CallParamType::kF32, "fwidth"},
+                    BuiltinData{core::BuiltinFn::kFwidthCoarse, CallParamType::kF32, "fwidth"},
+                    BuiltinData{core::BuiltinFn::kFwidthFine, CallParamType::kF32, "fwidth"}));
 
 TEST_F(GlslASTPrinterTest_Builtin, Builtin_Call) {
     auto* call = Call("dot", "param1", "param2");
diff --git a/src/tint/lang/glsl/writer/ast_printer/helper_test.h b/src/tint/lang/glsl/writer/ast_printer/helper_test.h
index d2f150a..5c5e018 100644
--- a/src/tint/lang/glsl/writer/ast_printer/helper_test.h
+++ b/src/tint/lang/glsl/writer/ast_printer/helper_test.h
@@ -52,12 +52,14 @@
         if (gen_) {
             return *gen_;
         }
-        [&] {
-            ASSERT_TRUE(IsValid()) << "Builder program is not valid\n" << Diagnostics().str();
-        }();
+        if (!IsValid()) {
+            ADD_FAILURE() << "ProgramBuilder is not valid: " << Diagnostics();
+        }
         program = std::make_unique<Program>(resolver::Resolve(*this));
-        [&] { ASSERT_TRUE(program->IsValid()) << program->Diagnostics().str(); }();
-        gen_ = std::make_unique<ASTPrinter>(program.get(), version);
+        if (!program->IsValid()) {
+            ADD_FAILURE() << program->Diagnostics();
+        }
+        gen_ = std::make_unique<ASTPrinter>(*program, version);
         return *gen_;
     }
 
@@ -73,20 +75,21 @@
         if (gen_) {
             return *gen_;
         }
-        [&] {
-            ASSERT_TRUE(IsValid()) << "Builder program is not valid\n" << Diagnostics().str();
-        }();
+        if (!IsValid()) {
+            ADD_FAILURE() << "ProgramBuilder is not valid: " << Diagnostics();
+        }
         program = std::make_unique<Program>(resolver::Resolve(*this));
-        [&] { ASSERT_TRUE(program->IsValid()) << program->Diagnostics().str(); }();
+        if (!program->IsValid()) {
+            ADD_FAILURE() << program->Diagnostics();
+        }
 
-        auto sanitized_result = Sanitize(program.get(), options, /* entry_point */ "");
-        [&] {
-            ASSERT_TRUE(sanitized_result.program.IsValid())
-                << sanitized_result.program.Diagnostics().str();
-        }();
+        auto sanitized_result = Sanitize(*program, options, /* entry_point */ "");
+        if (!sanitized_result.program.IsValid()) {
+            ADD_FAILURE() << sanitized_result.program.Diagnostics();
+        }
 
         *program = std::move(sanitized_result.program);
-        gen_ = std::make_unique<ASTPrinter>(program.get(), version);
+        gen_ = std::make_unique<ASTPrinter>(*program, version);
         return *gen_;
     }
 
diff --git a/src/tint/lang/glsl/writer/ast_raise/combine_samplers.cc b/src/tint/lang/glsl/writer/ast_raise/combine_samplers.cc
index 84647d0..0908a70 100644
--- a/src/tint/lang/glsl/writer/ast_raise/combine_samplers.cc
+++ b/src/tint/lang/glsl/writer/ast_raise/combine_samplers.cc
@@ -51,11 +51,11 @@
 /// PIMPL state for the transform
 struct CombineSamplers::State {
     /// The source program
-    const Program* const src;
+    const Program& src;
     /// The target program builder
     ProgramBuilder b;
     /// The clone context
-    program::CloneContext ctx = {&b, src, /* auto_clone_symbols */ true};
+    program::CloneContext ctx = {&b, &src, /* auto_clone_symbols */ true};
 
     /// The binding info
     const BindingInfo* binding_info;
@@ -91,7 +91,7 @@
     /// Constructor
     /// @param program the source program
     /// @param info the binding map information
-    State(const Program* program, const BindingInfo* info) : src(program), binding_info(info) {}
+    State(const Program& program, const BindingInfo* info) : src(program), binding_info(info) {}
 
     /// Creates a combined sampler global variables.
     /// (Note this is actually a Texture node at the AST level, but it will be
@@ -226,7 +226,7 @@
             if (auto* call = sem.Get(expr)->UnwrapMaterialize()->As<sem::Call>()) {
                 Vector<const ast::Expression*, 8> args;
                 // Replace all texture builtin calls.
-                if (auto* builtin = call->Target()->As<sem::Builtin>()) {
+                if (auto* builtin = call->Target()->As<sem::BuiltinFn>()) {
                     const auto& signature = builtin->Signature();
                     auto sampler_index = signature.IndexOf(core::ParameterUsage::kSampler);
                     auto texture_index = signature.IndexOf(core::ParameterUsage::kTexture);
@@ -270,7 +270,7 @@
                         }
                     }
                     const ast::Expression* value = ctx.dst->Call(ctx.Clone(expr->target), args);
-                    if (builtin->Type() == core::Function::kTextureLoad &&
+                    if (builtin->Fn() == core::BuiltinFn::kTextureLoad &&
                         texture_var->Type()->UnwrapRef()->Is<core::type::DepthTexture>() &&
                         !call->Stmt()->Declaration()->Is<ast::CallStatement>()) {
                         value = ctx.dst->MemberAccessor(value, "x");
@@ -336,7 +336,7 @@
 
 CombineSamplers::~CombineSamplers() = default;
 
-ast::transform::Transform::ApplyResult CombineSamplers::Apply(const Program* src,
+ast::transform::Transform::ApplyResult CombineSamplers::Apply(const Program& src,
                                                               const ast::transform::DataMap& inputs,
                                                               ast::transform::DataMap&) const {
     auto* binding_info = inputs.Get<BindingInfo>();
diff --git a/src/tint/lang/glsl/writer/ast_raise/combine_samplers.h b/src/tint/lang/glsl/writer/ast_raise/combine_samplers.h
index 725e66d..e8c1b80 100644
--- a/src/tint/lang/glsl/writer/ast_raise/combine_samplers.h
+++ b/src/tint/lang/glsl/writer/ast_raise/combine_samplers.h
@@ -89,7 +89,7 @@
     ~CombineSamplers() override;
 
     /// @copydoc ast::transform::Transform::Apply
-    ApplyResult Apply(const Program* program,
+    ApplyResult Apply(const Program& program,
                       const ast::transform::DataMap& inputs,
                       ast::transform::DataMap& outputs) const override;
 
diff --git a/src/tint/lang/glsl/writer/ast_raise/pad_structs.cc b/src/tint/lang/glsl/writer/ast_raise/pad_structs.cc
index aa35141..8057eee 100644
--- a/src/tint/lang/glsl/writer/ast_raise/pad_structs.cc
+++ b/src/tint/lang/glsl/writer/ast_raise/pad_structs.cc
@@ -56,12 +56,12 @@
 
 PadStructs::~PadStructs() = default;
 
-ast::transform::Transform::ApplyResult PadStructs::Apply(const Program* src,
+ast::transform::Transform::ApplyResult PadStructs::Apply(const Program& src,
                                                          const ast::transform::DataMap&,
                                                          ast::transform::DataMap&) const {
     ProgramBuilder b;
-    program::CloneContext ctx{&b, src, /* auto_clone_symbols */ true};
-    auto& sem = src->Sem();
+    program::CloneContext ctx{&b, &src, /* auto_clone_symbols */ true};
+    auto& sem = src.Sem();
 
     std::unordered_map<const ast::Struct*, const ast::Struct*> replaced_structs;
     Hashset<const ast::StructMember*, 8> padding_members;
diff --git a/src/tint/lang/glsl/writer/ast_raise/pad_structs.h b/src/tint/lang/glsl/writer/ast_raise/pad_structs.h
index 33fe20d..1529b87 100644
--- a/src/tint/lang/glsl/writer/ast_raise/pad_structs.h
+++ b/src/tint/lang/glsl/writer/ast_raise/pad_structs.h
@@ -33,7 +33,7 @@
     ~PadStructs() override;
 
     /// @copydoc ast::transform::Transform::Apply
-    ApplyResult Apply(const Program* program,
+    ApplyResult Apply(const Program& program,
                       const ast::transform::DataMap& inputs,
                       ast::transform::DataMap& outputs) const override;
 };
diff --git a/src/tint/lang/glsl/writer/ast_raise/texture_1d_to_2d.cc b/src/tint/lang/glsl/writer/ast_raise/texture_1d_to_2d.cc
index aa23d0b..3306a9a 100644
--- a/src/tint/lang/glsl/writer/ast_raise/texture_1d_to_2d.cc
+++ b/src/tint/lang/glsl/writer/ast_raise/texture_1d_to_2d.cc
@@ -33,9 +33,9 @@
 
 namespace {
 
-bool ShouldRun(const Program* program) {
-    for (auto* fn : program->AST().Functions()) {
-        if (auto* sem_fn = program->Sem().Get(fn)) {
+bool ShouldRun(const Program& program) {
+    for (auto* fn : program.AST().Functions()) {
+        if (auto* sem_fn = program.Sem().Get(fn)) {
             for (auto* builtin : sem_fn->DirectlyCalledBuiltins()) {
                 const auto& signature = builtin->Signature();
                 auto texture = signature.Parameter(core::ParameterUsage::kTexture);
@@ -48,9 +48,9 @@
             }
         }
     }
-    for (auto* var : program->AST().GlobalVariables()) {
+    for (auto* var : program.AST().GlobalVariables()) {
         if (Switch(
-                program->Sem().Get(var)->Type()->UnwrapRef(),
+                program.Sem().Get(var)->Type()->UnwrapRef(),
                 [&](const core::type::SampledTexture* tex) {
                     return tex->dim() == core::type::TextureDimension::k1d;
                 },
@@ -68,22 +68,22 @@
 /// PIMPL state for the transform
 struct Texture1DTo2D::State {
     /// The source program
-    const Program* const src;
+    const Program& src;
     /// The target program builder
     ProgramBuilder b;
     /// The clone context
-    program::CloneContext ctx = {&b, src, /* auto_clone_symbols */ true};
+    program::CloneContext ctx = {&b, &src, /* auto_clone_symbols */ true};
 
     /// Constructor
     /// @param program the source program
-    explicit State(const Program* program) : src(program) {}
+    explicit State(const Program& program) : src(program) {}
 
     /// Runs the transform
     /// @returns the new program or SkipTransform if the transform is not required
     ApplyResult Run() {
-        auto& sem = src->Sem();
+        auto& sem = src.Sem();
 
-        if (!ShouldRun(ctx.src)) {
+        if (!ShouldRun(src)) {
             return SkipTransform;
         }
 
@@ -126,7 +126,7 @@
             if (!call) {
                 return nullptr;
             }
-            auto* builtin = call->Target()->As<sem::Builtin>();
+            auto* builtin = call->Target()->As<sem::BuiltinFn>();
             if (!builtin) {
                 return nullptr;
             }
@@ -140,7 +140,7 @@
                 return nullptr;
             }
 
-            if (builtin->Type() == core::Function::kTextureDimensions) {
+            if (builtin->Fn() == core::BuiltinFn::kTextureDimensions) {
                 // If this textureDimensions() call is in a CallStatement, we can leave it
                 // unmodified since the return value will be dropped on the floor anyway.
                 if (call->Stmt()->Declaration()->Is<ast::CallStatement>()) {
@@ -187,7 +187,7 @@
 
 Texture1DTo2D::~Texture1DTo2D() = default;
 
-ast::transform::Transform::ApplyResult Texture1DTo2D::Apply(const Program* src,
+ast::transform::Transform::ApplyResult Texture1DTo2D::Apply(const Program& src,
                                                             const ast::transform::DataMap&,
                                                             ast::transform::DataMap&) const {
     return State(src).Run();
diff --git a/src/tint/lang/glsl/writer/ast_raise/texture_1d_to_2d.h b/src/tint/lang/glsl/writer/ast_raise/texture_1d_to_2d.h
index bd43aea..7f8ef20 100644
--- a/src/tint/lang/glsl/writer/ast_raise/texture_1d_to_2d.h
+++ b/src/tint/lang/glsl/writer/ast_raise/texture_1d_to_2d.h
@@ -30,7 +30,7 @@
     ~Texture1DTo2D() override;
 
     /// @copydoc ast::transform::Transform::Apply
-    ApplyResult Apply(const Program* program,
+    ApplyResult Apply(const Program& program,
                       const ast::transform::DataMap& inputs,
                       ast::transform::DataMap& outputs) const override;
 
diff --git a/src/tint/lang/glsl/writer/ast_raise/texture_builtins_from_uniform.cc b/src/tint/lang/glsl/writer/ast_raise/texture_builtins_from_uniform.cc
index 02c056d..61851e4 100644
--- a/src/tint/lang/glsl/writer/ast_raise/texture_builtins_from_uniform.cc
+++ b/src/tint/lang/glsl/writer/ast_raise/texture_builtins_from_uniform.cc
@@ -45,16 +45,16 @@
 /// The member name of the texture builtin values.
 constexpr std::string_view kTextureBuiltinValuesMemberNamePrefix = "texture_builtin_value_";
 
-bool ShouldRun(const Program* program) {
-    for (auto* fn : program->AST().Functions()) {
-        if (auto* sem_fn = program->Sem().Get(fn)) {
+bool ShouldRun(const Program& program) {
+    for (auto* fn : program.AST().Functions()) {
+        if (auto* sem_fn = program.Sem().Get(fn)) {
             for (auto* builtin : sem_fn->DirectlyCalledBuiltins()) {
                 // GLSL ES  has no native support for the counterpart of
                 // textureNumLevels (textureQueryLevels) and textureNumSamples (textureSamples)
-                if (builtin->Type() == core::Function::kTextureNumLevels) {
+                if (builtin->Fn() == core::BuiltinFn::kTextureNumLevels) {
                     return true;
                 }
-                if (builtin->Type() == core::Function::kTextureNumSamples) {
+                if (builtin->Fn() == core::BuiltinFn::kTextureNumSamples) {
                     return true;
                 }
             }
@@ -74,7 +74,7 @@
     /// @param program the source program
     /// @param in the input transform data
     /// @param out the output transform data
-    explicit State(const Program* program,
+    explicit State(const Program& program,
                    const ast::transform::DataMap& in,
                    ast::transform::DataMap& out)
         : src(program), inputs(in), outputs(out) {}
@@ -91,7 +91,7 @@
             return resolver::Resolve(b);
         }
 
-        if (!ShouldRun(ctx.src)) {
+        if (!ShouldRun(src)) {
             return SkipTransform;
         }
 
@@ -110,9 +110,9 @@
 
                     tint::Switch(
                         call->Target(),
-                        [&](const sem::Builtin* builtin) {
-                            if (builtin->Type() != core::Function::kTextureNumLevels &&
-                                builtin->Type() != core::Function::kTextureNumSamples) {
+                        [&](const sem::BuiltinFn* builtin) {
+                            if (builtin->Fn() != core::BuiltinFn::kTextureNumLevels &&
+                                builtin->Fn() != core::BuiltinFn::kTextureNumSamples) {
                                 return;
                             }
                             if (auto* call_stmt =
@@ -131,7 +131,7 @@
                             TINT_ASSERT(texture_sem);
 
                             TextureBuiltinsFromUniformOptions::Field dataType =
-                                GetFieldFromBuiltinFunctionType(builtin->Type());
+                                GetFieldFromBuiltinFunctionType(builtin->Fn());
 
                             tint::Switch(
                                 texture_sem,
@@ -262,7 +262,7 @@
 
   private:
     /// The source program
-    const Program* const src;
+    const Program& src;
     /// The transform inputs
     const ast::transform::DataMap& inputs;
     /// The transform outputs
@@ -270,9 +270,9 @@
     /// The target program builder
     ProgramBuilder b;
     /// The clone context
-    program::CloneContext ctx = {&b, src, /* auto_clone_symbols */ true};
+    program::CloneContext ctx = {&b, &src, /* auto_clone_symbols */ true};
     /// Alias to the semantic info in ctx.src
-    const sem::Info& sem = ctx.src->Sem();
+    const sem::Info& sem = src.Sem();
 
     /// The bindpoint to byte offset and field to pass out in transform result.
     /// For one texture type, it could only be passed into one of the
@@ -352,7 +352,7 @@
         }
 
         // Find if there's any existing global variable using the same cfg->ubo_binding
-        for (auto* var : src->AST().Globals<ast::Var>()) {
+        for (auto* var : src.AST().Globals<ast::Var>()) {
             if (var->HasBindingPoint()) {
                 auto* global_sem = sem.Get<sem::GlobalVariable>(var);
 
@@ -462,11 +462,11 @@
     /// @param type of the builtin function
     /// @returns corresponding TextureBuiltinsFromUniformOptions::Field for the builtin
     static TextureBuiltinsFromUniformOptions::Field GetFieldFromBuiltinFunctionType(
-        core::Function type) {
+        core::BuiltinFn type) {
         switch (type) {
-            case core::Function::kTextureNumLevels:
+            case core::BuiltinFn::kTextureNumLevels:
                 return TextureBuiltinsFromUniformOptions::Field::TextureNumLevels;
-            case core::Function::kTextureNumSamples:
+            case core::BuiltinFn::kTextureNumSamples:
                 return TextureBuiltinsFromUniformOptions::Field::TextureNumSamples;
             default:
                 TINT_UNREACHABLE() << "unsupported builtin function type " << type;
@@ -476,7 +476,7 @@
 };
 
 ast::transform::Transform::ApplyResult TextureBuiltinsFromUniform::Apply(
-    const Program* src,
+    const Program& src,
     const ast::transform::DataMap& inputs,
     ast::transform::DataMap& outputs) const {
     return State{src, inputs, outputs}.Run();
diff --git a/src/tint/lang/glsl/writer/ast_raise/texture_builtins_from_uniform.h b/src/tint/lang/glsl/writer/ast_raise/texture_builtins_from_uniform.h
index 53ba1e3..81c6dce 100644
--- a/src/tint/lang/glsl/writer/ast_raise/texture_builtins_from_uniform.h
+++ b/src/tint/lang/glsl/writer/ast_raise/texture_builtins_from_uniform.h
@@ -107,7 +107,7 @@
     };
 
     /// @copydoc ast::transform::Transform::Apply
-    ApplyResult Apply(const Program* program,
+    ApplyResult Apply(const Program& program,
                       const ast::transform::DataMap& inputs,
                       ast::transform::DataMap& outputs) const override;
 
diff --git a/src/tint/lang/glsl/writer/common/options.h b/src/tint/lang/glsl/writer/common/options.h
index 59d3208..1fe8e43 100644
--- a/src/tint/lang/glsl/writer/common/options.h
+++ b/src/tint/lang/glsl/writer/common/options.h
@@ -19,6 +19,7 @@
 #include <string>
 #include <unordered_map>
 
+#include "src/tint/api/options/binding_remapper.h"
 #include "src/tint/api/options/external_texture.h"
 #include "src/tint/api/options/texture_builtins_from_uniform.h"
 #include "src/tint/lang/core/access.h"
@@ -47,24 +48,15 @@
     /// The binding point to use for placeholder samplers.
     BindingPoint placeholder_binding_point;
 
-    /// A map of old binding point to new binding point for the BindingRemapper
-    /// transform
-    std::unordered_map<BindingPoint, BindingPoint> binding_points;
-
-    /// A map of old binding point to new access control for the BindingRemapper
-    /// transform
-    std::unordered_map<BindingPoint, core::Access> access_controls;
-
     /// Set to `true` to disable software robustness that prevents out-of-bounds accesses.
     bool disable_robustness = false;
 
-    /// If true, then validation will be disabled for binding point collisions
-    /// generated by the BindingRemapper transform
-    bool allow_collisions = false;
-
     /// Set to `true` to disable workgroup memory zero initialization
     bool disable_workgroup_init = false;
 
+    /// Options used in the bindings remapper
+    BindingRemapperOptions binding_remapper_options = {};
+
     /// Options used in the binding mappings for external textures
     ExternalTextureOptions external_texture_options = {};
 
@@ -79,11 +71,9 @@
     /// Reflect the fields of this class so that it can be used by tint::ForeachField()
     TINT_REFLECT(binding_map,
                  placeholder_binding_point,
-                 binding_points,
-                 access_controls,
                  disable_robustness,
-                 allow_collisions,
                  disable_workgroup_init,
+                 binding_remapper_options,
                  external_texture_options,
                  texture_builtins_from_uniform,
                  version);
diff --git a/src/tint/lang/glsl/writer/writer.cc b/src/tint/lang/glsl/writer/writer.cc
index 2fd23d8..5931ad5 100644
--- a/src/tint/lang/glsl/writer/writer.cc
+++ b/src/tint/lang/glsl/writer/writer.cc
@@ -22,10 +22,10 @@
 
 namespace tint::glsl::writer {
 
-Result<Output, std::string> Generate(const Program* program,
+Result<Output, std::string> Generate(const Program& program,
                                      const Options& options,
                                      const std::string& entry_point) {
-    if (!program->IsValid()) {
+    if (!program.IsValid()) {
         return std::string("input program is not valid");
     }
 
@@ -36,7 +36,7 @@
     }
 
     // Generate the GLSL code.
-    auto impl = std::make_unique<ASTPrinter>(&sanitized_result.program, options.version);
+    auto impl = std::make_unique<ASTPrinter>(sanitized_result.program, options.version);
     if (!impl->Generate()) {
         return impl->Diagnostics().str();
     }
diff --git a/src/tint/lang/glsl/writer/writer.h b/src/tint/lang/glsl/writer/writer.h
index 7ced20e..7b6be46 100644
--- a/src/tint/lang/glsl/writer/writer.h
+++ b/src/tint/lang/glsl/writer/writer.h
@@ -35,7 +35,7 @@
 /// @param options the configuration options to use when generating GLSL
 /// @param entry_point the entry point to generate GLSL for
 /// @returns the resulting GLSL and supplementary information, or an error string
-Result<Output, std::string> Generate(const Program* program,
+Result<Output, std::string> Generate(const Program& program,
                                      const Options& options,
                                      const std::string& entry_point);
 
diff --git a/src/tint/lang/glsl/writer/writer_bench.cc b/src/tint/lang/glsl/writer/writer_bench.cc
index 6d433f7..1c58f4b 100644
--- a/src/tint/lang/glsl/writer/writer_bench.cc
+++ b/src/tint/lang/glsl/writer/writer_bench.cc
@@ -38,7 +38,7 @@
 
     for (auto _ : state) {
         for (auto& ep : entry_points) {
-            auto res = Generate(&program, {}, ep);
+            auto res = Generate(program, {}, ep);
             if (!res) {
                 state.SkipWithError(res.Failure().c_str());
             }
diff --git a/src/tint/lang/hlsl/writer/ast_printer/ast_printer.cc b/src/tint/lang/hlsl/writer/ast_printer/ast_printer.cc
index 352c4d8..a22ac90 100644
--- a/src/tint/lang/hlsl/writer/ast_printer/ast_printer.cc
+++ b/src/tint/lang/hlsl/writer/ast_printer/ast_printer.cc
@@ -168,7 +168,7 @@
 SanitizedResult::~SanitizedResult() = default;
 SanitizedResult::SanitizedResult(SanitizedResult&&) = default;
 
-SanitizedResult Sanitize(const Program* in, const Options& options) {
+SanitizedResult Sanitize(const Program& in, const Options& options) {
     ast::transform::Manager manager;
     ast::transform::DataMap data;
 
@@ -329,7 +329,7 @@
     return result;
 }
 
-ASTPrinter::ASTPrinter(const Program* program) : builder_(ProgramBuilder::Wrap(program)) {}
+ASTPrinter::ASTPrinter(const Program& program) : builder_(ProgramBuilder::Wrap(program)) {}
 
 ASTPrinter::~ASTPrinter() = default;
 
@@ -1087,7 +1087,7 @@
     return Switch(
         target,  //
         [&](const sem::Function* func) { return EmitFunctionCall(out, call, func); },
-        [&](const sem::Builtin* builtin) { return EmitBuiltinCall(out, call, builtin); },
+        [&](const sem::BuiltinFn* builtin) { return EmitBuiltinCall(out, call, builtin); },
         [&](const sem::ValueConversion* conv) { return EmitValueConversion(out, call, conv); },
         [&](const sem::ValueConstructor* ctor) { return EmitValueConstructor(out, call, ctor); },
         [&](Default) {
@@ -1166,35 +1166,35 @@
 
 bool ASTPrinter::EmitBuiltinCall(StringStream& out,
                                  const sem::Call* call,
-                                 const sem::Builtin* builtin) {
-    const auto type = builtin->Type();
+                                 const sem::BuiltinFn* builtin) {
+    const auto type = builtin->Fn();
 
     auto* expr = call->Declaration();
     if (builtin->IsTexture()) {
         return EmitTextureCall(out, call, builtin);
     }
-    if (type == core::Function::kSelect) {
+    if (type == core::BuiltinFn::kSelect) {
         return EmitSelectCall(out, expr);
     }
-    if (type == core::Function::kModf) {
+    if (type == core::BuiltinFn::kModf) {
         return EmitModfCall(out, expr, builtin);
     }
-    if (type == core::Function::kFrexp) {
+    if (type == core::BuiltinFn::kFrexp) {
         return EmitFrexpCall(out, expr, builtin);
     }
-    if (type == core::Function::kDegrees) {
+    if (type == core::BuiltinFn::kDegrees) {
         return EmitDegreesCall(out, expr, builtin);
     }
-    if (type == core::Function::kRadians) {
+    if (type == core::BuiltinFn::kRadians) {
         return EmitRadiansCall(out, expr, builtin);
     }
-    if (type == core::Function::kSign) {
+    if (type == core::BuiltinFn::kSign) {
         return EmitSignCall(out, call, builtin);
     }
-    if (type == core::Function::kQuantizeToF16) {
+    if (type == core::BuiltinFn::kQuantizeToF16) {
         return EmitQuantizeToF16Call(out, expr, builtin);
     }
-    if (type == core::Function::kTrunc) {
+    if (type == core::BuiltinFn::kTrunc) {
         return EmitTruncCall(out, expr, builtin);
     }
     if (builtin->IsDataPacking()) {
@@ -1213,7 +1213,11 @@
         return EmitDP4aCall(out, expr, builtin);
     }
     if (builtin->IsSubgroup()) {
-        return EmitSubgroupCall(out, expr, builtin);
+        if (builtin->Fn() == core::BuiltinFn::kSubgroupBroadcast) {
+            // Fall through the regular path.
+        } else {
+            return EmitSubgroupCall(out, expr, builtin);
+        }
     }
 
     auto name = generate_builtin_name(builtin);
@@ -1224,7 +1228,7 @@
     // Handle single argument builtins that only accept and return uint (not int overload). We need
     // to explicitly cast the return value (we also cast the arg for good measure). See
     // crbug.com/tint/1550
-    if (type == core::Function::kCountOneBits || type == core::Function::kReverseBits) {
+    if (type == core::BuiltinFn::kCountOneBits || type == core::BuiltinFn::kReverseBits) {
         auto* arg = call->Arguments()[0];
         if (arg->Type()->UnwrapRef()->is_signed_integer_scalar_or_vector()) {
             out << "asint(" << name << "(asuint(";
@@ -1987,7 +1991,7 @@
 
 bool ASTPrinter::EmitWorkgroupAtomicCall(StringStream& out,
                                          const ast::CallExpression* expr,
-                                         const sem::Builtin* builtin) {
+                                         const sem::BuiltinFn* builtin) {
     std::string result = UniqueIdentifier("atomic_result");
 
     if (!builtin->ReturnType()->Is<core::type::Void>()) {
@@ -2014,7 +2018,7 @@
                 if (i > 0) {
                     pre << ", ";
                 }
-                if (i == 1 && builtin->Type() == core::Function::kAtomicSub) {
+                if (i == 1 && builtin->Fn() == core::BuiltinFn::kAtomicSub) {
                     // Sub uses InterlockedAdd with the operand negated.
                     pre << "-";
                 }
@@ -2032,8 +2036,8 @@
         return true;
     };
 
-    switch (builtin->Type()) {
-        case core::Function::kAtomicLoad: {
+    switch (builtin->Fn()) {
+        case core::BuiltinFn::kAtomicLoad: {
             // HLSL does not have an InterlockedLoad, so we emulate it with
             // InterlockedOr using 0 as the OR value
             auto pre = Line();
@@ -2050,7 +2054,7 @@
             out << result;
             return true;
         }
-        case core::Function::kAtomicStore: {
+        case core::BuiltinFn::kAtomicStore: {
             // HLSL does not have an InterlockedStore, so we emulate it with
             // InterlockedExchange and discard the returned value
             {  // T result = 0;
@@ -2081,7 +2085,7 @@
             }
             return true;
         }
-        case core::Function::kAtomicCompareExchangeWeak: {
+        case core::BuiltinFn::kAtomicCompareExchangeWeak: {
             if (!EmitStructType(&helpers_, builtin->ReturnType()->As<core::type::Struct>())) {
                 return false;
             }
@@ -2130,33 +2134,33 @@
             return true;
         }
 
-        case core::Function::kAtomicAdd:
-        case core::Function::kAtomicSub:
+        case core::BuiltinFn::kAtomicAdd:
+        case core::BuiltinFn::kAtomicSub:
             return call("InterlockedAdd");
 
-        case core::Function::kAtomicMax:
+        case core::BuiltinFn::kAtomicMax:
             return call("InterlockedMax");
 
-        case core::Function::kAtomicMin:
+        case core::BuiltinFn::kAtomicMin:
             return call("InterlockedMin");
 
-        case core::Function::kAtomicAnd:
+        case core::BuiltinFn::kAtomicAnd:
             return call("InterlockedAnd");
 
-        case core::Function::kAtomicOr:
+        case core::BuiltinFn::kAtomicOr:
             return call("InterlockedOr");
 
-        case core::Function::kAtomicXor:
+        case core::BuiltinFn::kAtomicXor:
             return call("InterlockedXor");
 
-        case core::Function::kAtomicExchange:
+        case core::BuiltinFn::kAtomicExchange:
             return call("InterlockedExchange");
 
         default:
             break;
     }
 
-    TINT_UNREACHABLE() << "unsupported atomic builtin: " << builtin->Type();
+    TINT_UNREACHABLE() << "unsupported atomic builtin: " << builtin->Fn();
     return false;
 }
 
@@ -2186,7 +2190,7 @@
 
 bool ASTPrinter::EmitModfCall(StringStream& out,
                               const ast::CallExpression* expr,
-                              const sem::Builtin* builtin) {
+                              const sem::BuiltinFn* builtin) {
     return CallBuiltinHelper(
         out, expr, builtin, [&](TextBuffer* b, const std::vector<std::string>& params) {
             auto* ty = builtin->Parameters()[0]->Type();
@@ -2219,7 +2223,7 @@
 
 bool ASTPrinter::EmitFrexpCall(StringStream& out,
                                const ast::CallExpression* expr,
-                               const sem::Builtin* builtin) {
+                               const sem::BuiltinFn* builtin) {
     return CallBuiltinHelper(
         out, expr, builtin, [&](TextBuffer* b, const std::vector<std::string>& params) {
             auto* ty = builtin->Parameters()[0]->Type();
@@ -2260,7 +2264,7 @@
 
 bool ASTPrinter::EmitDegreesCall(StringStream& out,
                                  const ast::CallExpression* expr,
-                                 const sem::Builtin* builtin) {
+                                 const sem::BuiltinFn* builtin) {
     return CallBuiltinHelper(out, expr, builtin,
                              [&](TextBuffer* b, const std::vector<std::string>& params) {
                                  Line(b) << "return " << params[0] << " * " << std::setprecision(20)
@@ -2271,7 +2275,7 @@
 
 bool ASTPrinter::EmitRadiansCall(StringStream& out,
                                  const ast::CallExpression* expr,
-                                 const sem::Builtin* builtin) {
+                                 const sem::BuiltinFn* builtin) {
     return CallBuiltinHelper(out, expr, builtin,
                              [&](TextBuffer* b, const std::vector<std::string>& params) {
                                  Line(b) << "return " << params[0] << " * " << std::setprecision(20)
@@ -2283,7 +2287,7 @@
 // The HLSL `sign` method always returns an `int` result (scalar or vector). In WGSL the result is
 // expected to be the same type as the argument. This injects a cast to the expected WGSL result
 // type after the call to `sign`.
-bool ASTPrinter::EmitSignCall(StringStream& out, const sem::Call* call, const sem::Builtin*) {
+bool ASTPrinter::EmitSignCall(StringStream& out, const sem::Call* call, const sem::BuiltinFn*) {
     auto* arg = call->Arguments()[0];
     if (!EmitType(out, arg->Type(), core::AddressSpace::kUndefined, core::Access::kReadWrite, "")) {
         return false;
@@ -2298,7 +2302,7 @@
 
 bool ASTPrinter::EmitQuantizeToF16Call(StringStream& out,
                                        const ast::CallExpression* expr,
-                                       const sem::Builtin* builtin) {
+                                       const sem::BuiltinFn* builtin) {
     // Cast to f16 and back
     std::string width;
     if (auto* vec = builtin->ReturnType()->As<core::type::Vector>()) {
@@ -2315,7 +2319,7 @@
 
 bool ASTPrinter::EmitTruncCall(StringStream& out,
                                const ast::CallExpression* expr,
-                               const sem::Builtin* builtin) {
+                               const sem::BuiltinFn* builtin) {
     // HLSL's trunc is broken for very large/small float values.
     // See crbug.com/tint/1883
     return CallBuiltinHelper(  //
@@ -2329,27 +2333,27 @@
 
 bool ASTPrinter::EmitDataPackingCall(StringStream& out,
                                      const ast::CallExpression* expr,
-                                     const sem::Builtin* builtin) {
+                                     const sem::BuiltinFn* builtin) {
     return CallBuiltinHelper(
         out, expr, builtin, [&](TextBuffer* b, const std::vector<std::string>& params) {
             uint32_t dims = 2;
             bool is_signed = false;
             uint32_t scale = 65535;
-            if (builtin->Type() == core::Function::kPack4X8Snorm ||
-                builtin->Type() == core::Function::kPack4X8Unorm) {
+            if (builtin->Fn() == core::BuiltinFn::kPack4X8Snorm ||
+                builtin->Fn() == core::BuiltinFn::kPack4X8Unorm) {
                 dims = 4;
                 scale = 255;
             }
-            if (builtin->Type() == core::Function::kPack4X8Snorm ||
-                builtin->Type() == core::Function::kPack2X16Snorm) {
+            if (builtin->Fn() == core::BuiltinFn::kPack4X8Snorm ||
+                builtin->Fn() == core::BuiltinFn::kPack2X16Snorm) {
                 is_signed = true;
                 scale = (scale - 1) / 2;
             }
-            switch (builtin->Type()) {
-                case core::Function::kPack4X8Snorm:
-                case core::Function::kPack4X8Unorm:
-                case core::Function::kPack2X16Snorm:
-                case core::Function::kPack2X16Unorm: {
+            switch (builtin->Fn()) {
+                case core::BuiltinFn::kPack4X8Snorm:
+                case core::BuiltinFn::kPack4X8Unorm:
+                case core::BuiltinFn::kPack2X16Snorm:
+                case core::BuiltinFn::kPack2X16Unorm: {
                     {
                         auto l = Line(b);
                         l << (is_signed ? "" : "u") << "int" << dims
@@ -2375,7 +2379,7 @@
                     }
                     break;
                 }
-                case core::Function::kPack2X16Float: {
+                case core::BuiltinFn::kPack2X16Float: {
                     Line(b) << "uint2 i = f32tof16(" << params[0] << ");";
                     Line(b) << "return i.x | (i.y << 16);";
                     break;
@@ -2392,25 +2396,25 @@
 
 bool ASTPrinter::EmitDataUnpackingCall(StringStream& out,
                                        const ast::CallExpression* expr,
-                                       const sem::Builtin* builtin) {
+                                       const sem::BuiltinFn* builtin) {
     return CallBuiltinHelper(
         out, expr, builtin, [&](TextBuffer* b, const std::vector<std::string>& params) {
             uint32_t dims = 2;
             bool is_signed = false;
             uint32_t scale = 65535;
-            if (builtin->Type() == core::Function::kUnpack4X8Snorm ||
-                builtin->Type() == core::Function::kUnpack4X8Unorm) {
+            if (builtin->Fn() == core::BuiltinFn::kUnpack4X8Snorm ||
+                builtin->Fn() == core::BuiltinFn::kUnpack4X8Unorm) {
                 dims = 4;
                 scale = 255;
             }
-            if (builtin->Type() == core::Function::kUnpack4X8Snorm ||
-                builtin->Type() == core::Function::kUnpack2X16Snorm) {
+            if (builtin->Fn() == core::BuiltinFn::kUnpack4X8Snorm ||
+                builtin->Fn() == core::BuiltinFn::kUnpack2X16Snorm) {
                 is_signed = true;
                 scale = (scale - 1) / 2;
             }
-            switch (builtin->Type()) {
-                case core::Function::kUnpack4X8Snorm:
-                case core::Function::kUnpack2X16Snorm: {
+            switch (builtin->Fn()) {
+                case core::BuiltinFn::kUnpack4X8Snorm:
+                case core::BuiltinFn::kUnpack2X16Snorm: {
                     Line(b) << "int j = int(" << params[0] << ");";
                     {  // Perform sign extension on the converted values.
                         auto l = Line(b);
@@ -2426,8 +2430,8 @@
                             << (is_signed ? "-1.0" : "0.0") << ", 1.0);";
                     break;
                 }
-                case core::Function::kUnpack4X8Unorm:
-                case core::Function::kUnpack2X16Unorm: {
+                case core::BuiltinFn::kUnpack4X8Unorm:
+                case core::BuiltinFn::kUnpack2X16Unorm: {
                     Line(b) << "uint j = " << params[0] << ";";
                     {
                         auto l = Line(b);
@@ -2443,7 +2447,7 @@
                     Line(b) << "return float" << dims << "(i) / " << scale << ".0;";
                     break;
                 }
-                case core::Function::kUnpack2X16Float:
+                case core::BuiltinFn::kUnpack2X16Float:
                     Line(b) << "uint i = " << params[0] << ";";
                     Line(b) << "return f16tof32(uint2(i & 0xffff, i >> 16));";
                     break;
@@ -2459,17 +2463,17 @@
 
 bool ASTPrinter::EmitDP4aCall(StringStream& out,
                               const ast::CallExpression* expr,
-                              const sem::Builtin* builtin) {
+                              const sem::BuiltinFn* builtin) {
     // TODO(crbug.com/tint/1497): support the polyfill version of DP4a functions.
     return CallBuiltinHelper(
         out, expr, builtin, [&](TextBuffer* b, const std::vector<std::string>& params) {
             std::string functionName;
-            switch (builtin->Type()) {
-                case core::Function::kDot4I8Packed:
+            switch (builtin->Fn()) {
+                case core::BuiltinFn::kDot4I8Packed:
                     Line(b) << "int accumulator = 0;";
                     functionName = "dot4add_i8packed";
                     break;
-                case core::Function::kDot4U8Packed:
+                case core::BuiltinFn::kDot4U8Packed:
                     Line(b) << "uint accumulator = 0u;";
                     functionName = "dot4add_u8packed";
                     break;
@@ -2485,17 +2489,17 @@
         });
 }
 
-bool ASTPrinter::EmitBarrierCall(StringStream& out, const sem::Builtin* builtin) {
+bool ASTPrinter::EmitBarrierCall(StringStream& out, const sem::BuiltinFn* builtin) {
     // TODO(crbug.com/tint/661): Combine sequential barriers to a single
     // instruction.
-    if (builtin->Type() == core::Function::kWorkgroupBarrier) {
+    if (builtin->Fn() == core::BuiltinFn::kWorkgroupBarrier) {
         out << "GroupMemoryBarrierWithGroupSync()";
-    } else if (builtin->Type() == core::Function::kStorageBarrier) {
+    } else if (builtin->Fn() == core::BuiltinFn::kStorageBarrier) {
         out << "DeviceMemoryBarrierWithGroupSync()";
-    } else if (builtin->Type() == core::Function::kTextureBarrier) {
+    } else if (builtin->Fn() == core::BuiltinFn::kTextureBarrier) {
         out << "DeviceMemoryBarrierWithGroupSync()";
     } else {
-        TINT_UNREACHABLE() << "unexpected barrier builtin type " << core::str(builtin->Type());
+        TINT_UNREACHABLE() << "unexpected barrier builtin type " << core::str(builtin->Fn());
         return false;
     }
     return true;
@@ -2503,11 +2507,12 @@
 
 bool ASTPrinter::EmitSubgroupCall(StringStream& out,
                                   [[maybe_unused]] const ast::CallExpression* expr,
-                                  const sem::Builtin* builtin) {
-    if (builtin->Type() == core::Function::kSubgroupBallot) {
+                                  const sem::BuiltinFn* builtin) {
+    if (builtin->Fn() == core::BuiltinFn::kSubgroupBallot) {
         out << "WaveActiveBallot(true)";
     } else {
-        TINT_UNREACHABLE() << "unexpected subgroup builtin type " << core::str(builtin->Type());
+        // subgroupBroadcast is already handled in the regular builtin flow.
+        TINT_UNREACHABLE() << "unexpected subgroup builtin type " << core::str(builtin->Fn());
         return false;
     }
     return true;
@@ -2546,7 +2551,7 @@
 
 bool ASTPrinter::EmitTextureCall(StringStream& out,
                                  const sem::Call* call,
-                                 const sem::Builtin* builtin) {
+                                 const sem::BuiltinFn* builtin) {
     using Usage = core::ParameterUsage;
 
     auto& signature = builtin->Signature();
@@ -2567,19 +2572,19 @@
 
     auto* texture_type = TypeOf(texture)->UnwrapRef()->As<core::type::Texture>();
 
-    switch (builtin->Type()) {
-        case core::Function::kTextureDimensions:
-        case core::Function::kTextureNumLayers:
-        case core::Function::kTextureNumLevels:
-        case core::Function::kTextureNumSamples: {
+    switch (builtin->Fn()) {
+        case core::BuiltinFn::kTextureDimensions:
+        case core::BuiltinFn::kTextureNumLayers:
+        case core::BuiltinFn::kTextureNumLevels:
+        case core::BuiltinFn::kTextureNumSamples: {
             // All of these builtins use the GetDimensions() method on the texture
             bool is_ms = texture_type->IsAnyOf<core::type::MultisampledTexture,
                                                core::type::DepthMultisampledTexture>();
             int num_dimensions = 0;
             std::string swizzle;
 
-            switch (builtin->Type()) {
-                case core::Function::kTextureDimensions:
+            switch (builtin->Fn()) {
+                case core::BuiltinFn::kTextureDimensions:
                     switch (texture_type->dim()) {
                         case core::type::TextureDimension::kNone:
                             TINT_ICE() << "texture dimension is kNone";
@@ -2607,7 +2612,7 @@
                             break;
                     }
                     break;
-                case core::Function::kTextureNumLayers:
+                case core::BuiltinFn::kTextureNumLayers:
                     switch (texture_type->dim()) {
                         default:
                             TINT_ICE() << "texture dimension is not arrayed";
@@ -2622,7 +2627,7 @@
                             break;
                     }
                     break;
-                case core::Function::kTextureNumLevels:
+                case core::BuiltinFn::kTextureNumLevels:
                     switch (texture_type->dim()) {
                         default:
                             TINT_ICE() << "texture dimension does not support mips";
@@ -2644,7 +2649,7 @@
                             break;
                     }
                     break;
-                case core::Function::kTextureNumSamples:
+                case core::BuiltinFn::kTextureNumSamples:
                     switch (texture_type->dim()) {
                         default:
                             TINT_ICE() << "texture dimension does not support multisampling";
@@ -2707,7 +2712,7 @@
                         return false;
                     }
                     pre << ", ";
-                } else if (builtin->Type() == core::Function::kTextureNumLevels) {
+                } else if (builtin->Fn() == core::BuiltinFn::kTextureNumLevels) {
                     pre << "0, ";
                 }
 
@@ -2752,35 +2757,35 @@
 
     uint32_t hlsl_ret_width = 4u;
 
-    switch (builtin->Type()) {
-        case core::Function::kTextureSample:
+    switch (builtin->Fn()) {
+        case core::BuiltinFn::kTextureSample:
             out << ".Sample(";
             break;
-        case core::Function::kTextureSampleBias:
+        case core::BuiltinFn::kTextureSampleBias:
             out << ".SampleBias(";
             break;
-        case core::Function::kTextureSampleLevel:
+        case core::BuiltinFn::kTextureSampleLevel:
             out << ".SampleLevel(";
             break;
-        case core::Function::kTextureSampleGrad:
+        case core::BuiltinFn::kTextureSampleGrad:
             out << ".SampleGrad(";
             break;
-        case core::Function::kTextureSampleCompare:
+        case core::BuiltinFn::kTextureSampleCompare:
             out << ".SampleCmp(";
             hlsl_ret_width = 1;
             break;
-        case core::Function::kTextureSampleCompareLevel:
+        case core::BuiltinFn::kTextureSampleCompareLevel:
             out << ".SampleCmpLevelZero(";
             hlsl_ret_width = 1;
             break;
-        case core::Function::kTextureLoad:
+        case core::BuiltinFn::kTextureLoad:
             out << ".Load(";
             // Multisampled textures do not support mip-levels.
             if (!texture_type->Is<core::type::MultisampledTexture>()) {
                 pack_level_in_coords = true;
             }
             break;
-        case core::Function::kTextureGather:
+        case core::BuiltinFn::kTextureGather:
             out << ".Gather";
             if (builtin->Parameters()[0]->Usage() == core::ParameterUsage::kComponent) {
                 switch (call->Arguments()[0]->ConstantValue()->ValueAs<AInt>()) {
@@ -2800,10 +2805,10 @@
             }
             out << "(";
             break;
-        case core::Function::kTextureGatherCompare:
+        case core::BuiltinFn::kTextureGatherCompare:
             out << ".GatherCmp(";
             break;
-        case core::Function::kTextureStore:
+        case core::BuiltinFn::kTextureStore:
             out << "[";
             break;
         default:
@@ -2864,7 +2869,7 @@
         if (!emit_vector_appended_with_level(param_coords)) {
             return false;
         }
-    } else if (builtin->Type() == core::Function::kTextureStore) {
+    } else if (builtin->Fn() == core::BuiltinFn::kTextureStore) {
         // param_coords is an index expression, not a function arg
         if (!EmitExpression(out, param_coords)) {
             return false;
@@ -2886,7 +2891,7 @@
         }
     }
 
-    if (builtin->Type() == core::Function::kTextureStore) {
+    if (builtin->Fn() == core::BuiltinFn::kTextureStore) {
         out << "] = ";
         if (!EmitExpression(out, arg(Usage::kValue))) {
             return false;
@@ -2910,7 +2915,7 @@
         if (TINT_UNLIKELY(wgsl_ret_width > hlsl_ret_width)) {
             TINT_ICE() << "WGSL return width (" << wgsl_ret_width
                        << ") is wider than HLSL return width (" << hlsl_ret_width << ") for "
-                       << builtin->Type();
+                       << builtin->Fn();
             return false;
         }
     }
@@ -2918,80 +2923,82 @@
     return true;
 }
 
-std::string ASTPrinter::generate_builtin_name(const sem::Builtin* builtin) {
-    switch (builtin->Type()) {
-        case core::Function::kAbs:
-        case core::Function::kAcos:
-        case core::Function::kAll:
-        case core::Function::kAny:
-        case core::Function::kAsin:
-        case core::Function::kAtan:
-        case core::Function::kAtan2:
-        case core::Function::kCeil:
-        case core::Function::kClamp:
-        case core::Function::kCos:
-        case core::Function::kCosh:
-        case core::Function::kCross:
-        case core::Function::kDeterminant:
-        case core::Function::kDistance:
-        case core::Function::kDot:
-        case core::Function::kExp:
-        case core::Function::kExp2:
-        case core::Function::kFloor:
-        case core::Function::kFrexp:
-        case core::Function::kLdexp:
-        case core::Function::kLength:
-        case core::Function::kLog:
-        case core::Function::kLog2:
-        case core::Function::kMax:
-        case core::Function::kMin:
-        case core::Function::kModf:
-        case core::Function::kNormalize:
-        case core::Function::kPow:
-        case core::Function::kReflect:
-        case core::Function::kRefract:
-        case core::Function::kRound:
-        case core::Function::kSaturate:
-        case core::Function::kSin:
-        case core::Function::kSinh:
-        case core::Function::kSqrt:
-        case core::Function::kStep:
-        case core::Function::kTan:
-        case core::Function::kTanh:
-        case core::Function::kTranspose:
+std::string ASTPrinter::generate_builtin_name(const sem::BuiltinFn* builtin) {
+    switch (builtin->Fn()) {
+        case core::BuiltinFn::kAbs:
+        case core::BuiltinFn::kAcos:
+        case core::BuiltinFn::kAll:
+        case core::BuiltinFn::kAny:
+        case core::BuiltinFn::kAsin:
+        case core::BuiltinFn::kAtan:
+        case core::BuiltinFn::kAtan2:
+        case core::BuiltinFn::kCeil:
+        case core::BuiltinFn::kClamp:
+        case core::BuiltinFn::kCos:
+        case core::BuiltinFn::kCosh:
+        case core::BuiltinFn::kCross:
+        case core::BuiltinFn::kDeterminant:
+        case core::BuiltinFn::kDistance:
+        case core::BuiltinFn::kDot:
+        case core::BuiltinFn::kExp:
+        case core::BuiltinFn::kExp2:
+        case core::BuiltinFn::kFloor:
+        case core::BuiltinFn::kFrexp:
+        case core::BuiltinFn::kLdexp:
+        case core::BuiltinFn::kLength:
+        case core::BuiltinFn::kLog:
+        case core::BuiltinFn::kLog2:
+        case core::BuiltinFn::kMax:
+        case core::BuiltinFn::kMin:
+        case core::BuiltinFn::kModf:
+        case core::BuiltinFn::kNormalize:
+        case core::BuiltinFn::kPow:
+        case core::BuiltinFn::kReflect:
+        case core::BuiltinFn::kRefract:
+        case core::BuiltinFn::kRound:
+        case core::BuiltinFn::kSaturate:
+        case core::BuiltinFn::kSin:
+        case core::BuiltinFn::kSinh:
+        case core::BuiltinFn::kSqrt:
+        case core::BuiltinFn::kStep:
+        case core::BuiltinFn::kTan:
+        case core::BuiltinFn::kTanh:
+        case core::BuiltinFn::kTranspose:
             return builtin->str();
-        case core::Function::kCountOneBits:  // uint
+        case core::BuiltinFn::kCountOneBits:  // uint
             return "countbits";
-        case core::Function::kDpdx:
+        case core::BuiltinFn::kDpdx:
             return "ddx";
-        case core::Function::kDpdxCoarse:
+        case core::BuiltinFn::kDpdxCoarse:
             return "ddx_coarse";
-        case core::Function::kDpdxFine:
+        case core::BuiltinFn::kDpdxFine:
             return "ddx_fine";
-        case core::Function::kDpdy:
+        case core::BuiltinFn::kDpdy:
             return "ddy";
-        case core::Function::kDpdyCoarse:
+        case core::BuiltinFn::kDpdyCoarse:
             return "ddy_coarse";
-        case core::Function::kDpdyFine:
+        case core::BuiltinFn::kDpdyFine:
             return "ddy_fine";
-        case core::Function::kFaceForward:
+        case core::BuiltinFn::kFaceForward:
             return "faceforward";
-        case core::Function::kFract:
+        case core::BuiltinFn::kFract:
             return "frac";
-        case core::Function::kFma:
+        case core::BuiltinFn::kFma:
             return "mad";
-        case core::Function::kFwidth:
-        case core::Function::kFwidthCoarse:
-        case core::Function::kFwidthFine:
+        case core::BuiltinFn::kFwidth:
+        case core::BuiltinFn::kFwidthCoarse:
+        case core::BuiltinFn::kFwidthFine:
             return "fwidth";
-        case core::Function::kInverseSqrt:
+        case core::BuiltinFn::kInverseSqrt:
             return "rsqrt";
-        case core::Function::kMix:
+        case core::BuiltinFn::kMix:
             return "lerp";
-        case core::Function::kReverseBits:  // uint
+        case core::BuiltinFn::kReverseBits:  // uint
             return "reversebits";
-        case core::Function::kSmoothstep:
+        case core::BuiltinFn::kSmoothstep:
             return "smoothstep";
+        case core::BuiltinFn::kSubgroupBroadcast:
+            return "WaveReadLaneAt";
         default:
             diagnostics_.add_error(diag::System::Writer,
                                    "Unknown builtin method: " + std::string(builtin->str()));
@@ -4577,14 +4584,14 @@
 template <typename F>
 bool ASTPrinter::CallBuiltinHelper(StringStream& out,
                                    const ast::CallExpression* call,
-                                   const sem::Builtin* builtin,
+                                   const sem::BuiltinFn* builtin,
                                    F&& build) {
     // Generate the helper function if it hasn't been created already
     auto fn = tint::GetOrCreate(builtins_, builtin, [&]() -> std::string {
         TextBuffer b;
         TINT_DEFER(helpers_.Append(b));
 
-        auto fn_name = UniqueIdentifier(std::string("tint_") + core::str(builtin->Type()));
+        auto fn_name = UniqueIdentifier(std::string("tint_") + core::str(builtin->Fn()));
         std::vector<std::string> parameter_names;
         {
             auto decl = Line(&b);
diff --git a/src/tint/lang/hlsl/writer/ast_printer/ast_printer.h b/src/tint/lang/hlsl/writer/ast_printer/ast_printer.h
index 7b7966b..b3a5002 100644
--- a/src/tint/lang/hlsl/writer/ast_printer/ast_printer.h
+++ b/src/tint/lang/hlsl/writer/ast_printer/ast_printer.h
@@ -33,7 +33,7 @@
 
 // Forward declarations
 namespace tint::sem {
-class Builtin;
+class BuiltinFn;
 class Call;
 class ValueConstructor;
 class ValueConversion;
@@ -61,14 +61,14 @@
 /// @param program the input program
 /// @param options The HLSL generator options.
 /// @returns the sanitized program and any supplementary information
-SanitizedResult Sanitize(const Program* program, const Options& options);
+SanitizedResult Sanitize(const Program& program, const Options& options);
 
 /// Implementation class for HLSL generator
 class ASTPrinter : public tint::TextGenerator {
   public:
     /// Constructor
     /// @param program the program to generate
-    explicit ASTPrinter(const Program* program);
+    explicit ASTPrinter(const Program& program);
     ~ASTPrinter() override;
 
     /// @returns true on successful generation; false otherwise
@@ -129,7 +129,7 @@
     /// @param call the call expression
     /// @param builtin the builtin being called
     /// @returns true if the expression is emitted
-    bool EmitBuiltinCall(StringStream& out, const sem::Call* call, const sem::Builtin* builtin);
+    bool EmitBuiltinCall(StringStream& out, const sem::Call* call, const sem::BuiltinFn* builtin);
     /// Handles generating a value conversion expression
     /// @param out the output stream
     /// @param call the call expression
@@ -168,7 +168,7 @@
     /// @param out the output stream
     /// @param builtin the semantic information for the barrier builtin
     /// @returns true if the call expression is emitted
-    bool EmitBarrierCall(StringStream& out, const sem::Builtin* builtin);
+    bool EmitBarrierCall(StringStream& out, const sem::BuiltinFn* builtin);
     /// Handles generating an atomic intrinsic call for a storage buffer variable
     /// @param out the output stream
     /// @param expr the call expression
@@ -190,14 +190,14 @@
     /// @returns true if the call expression is emitted
     bool EmitWorkgroupAtomicCall(StringStream& out,
                                  const ast::CallExpression* expr,
-                                 const sem::Builtin* builtin);
+                                 const sem::BuiltinFn* builtin);
     /// Handles generating a call to a texture function (`textureSample`,
     /// `textureSampleGrad`, etc)
     /// @param out the output stream
     /// @param call the call expression
     /// @param builtin the semantic information for the texture builtin
     /// @returns true if the call expression is emitted
-    bool EmitTextureCall(StringStream& out, const sem::Call* call, const sem::Builtin* builtin);
+    bool EmitTextureCall(StringStream& out, const sem::Call* call, const sem::BuiltinFn* builtin);
     /// Handles generating a call to the `select()` builtin
     /// @param out the output stream
     /// @param expr the call expression
@@ -210,7 +210,7 @@
     /// @returns true if the call expression is emitted
     bool EmitModfCall(StringStream& out,
                       const ast::CallExpression* expr,
-                      const sem::Builtin* builtin);
+                      const sem::BuiltinFn* builtin);
     /// Handles generating a call to the `frexp()` builtin
     /// @param out the output stream
     /// @param expr the call expression
@@ -218,7 +218,7 @@
     /// @returns true if the call expression is emitted
     bool EmitFrexpCall(StringStream& out,
                        const ast::CallExpression* expr,
-                       const sem::Builtin* builtin);
+                       const sem::BuiltinFn* builtin);
     /// Handles generating a call to the `degrees()` builtin
     /// @param out the output stream
     /// @param expr the call expression
@@ -226,7 +226,7 @@
     /// @returns true if the call expression is emitted
     bool EmitDegreesCall(StringStream& out,
                          const ast::CallExpression* expr,
-                         const sem::Builtin* builtin);
+                         const sem::BuiltinFn* builtin);
     /// Handles generating a call to the `radians()` builtin
     /// @param out the output stream
     /// @param expr the call expression
@@ -234,13 +234,13 @@
     /// @returns true if the call expression is emitted
     bool EmitRadiansCall(StringStream& out,
                          const ast::CallExpression* expr,
-                         const sem::Builtin* builtin);
+                         const sem::BuiltinFn* builtin);
     /// Handles generating a call to the `sign()` builtin
     /// @param out the output stream
     /// @param call the call semantic node
     /// @param builtin the semantic information for the builtin
     /// @returns true if the call expression is emitted
-    bool EmitSignCall(StringStream& out, const sem::Call* call, const sem::Builtin* builtin);
+    bool EmitSignCall(StringStream& out, const sem::Call* call, const sem::BuiltinFn* builtin);
     /// Handles generating a call to data packing builtin
     /// @param out the output stream
     /// @param expr the call expression
@@ -248,7 +248,7 @@
     /// @returns true if the call expression is emitted
     bool EmitDataPackingCall(StringStream& out,
                              const ast::CallExpression* expr,
-                             const sem::Builtin* builtin);
+                             const sem::BuiltinFn* builtin);
     /// Handles generating a call to data unpacking builtin
     /// @param out the output stream
     /// @param expr the call expression
@@ -256,7 +256,7 @@
     /// @returns true if the call expression is emitted
     bool EmitDataUnpackingCall(StringStream& out,
                                const ast::CallExpression* expr,
-                               const sem::Builtin* builtin);
+                               const sem::BuiltinFn* builtin);
     /// Handles generating a call to the `quantizeToF16()` intrinsic
     /// @param out the output stream
     /// @param expr the call expression
@@ -264,7 +264,7 @@
     /// @returns true if the call expression is emitted
     bool EmitQuantizeToF16Call(StringStream& out,
                                const ast::CallExpression* expr,
-                               const sem::Builtin* builtin);
+                               const sem::BuiltinFn* builtin);
     /// Handles generating a call to the `trunc()` intrinsic
     /// @param out the output stream
     /// @param expr the call expression
@@ -272,7 +272,7 @@
     /// @returns true if the call expression is emitted
     bool EmitTruncCall(StringStream& out,
                        const ast::CallExpression* expr,
-                       const sem::Builtin* builtin);
+                       const sem::BuiltinFn* builtin);
     /// Handles generating a call to DP4a builtins (dot4I8Packed and dot4U8Packed)
     /// @param out the output stream
     /// @param expr the call expression
@@ -280,7 +280,7 @@
     /// @returns true if the call expression is emitted
     bool EmitDP4aCall(StringStream& out,
                       const ast::CallExpression* expr,
-                      const sem::Builtin* builtin);
+                      const sem::BuiltinFn* builtin);
     /// Handles generating a call to subgroup builtins.
     /// @param out the output stream
     /// @param expr the call expression
@@ -288,7 +288,7 @@
     /// @returns true if the call expression is emitted
     bool EmitSubgroupCall(StringStream& out,
                           const ast::CallExpression* expr,
-                          const sem::Builtin* builtin);
+                          const sem::BuiltinFn* builtin);
     /// Handles a case statement
     /// @param s the switch statement
     /// @param case_idx the index of the switch case in the switch statement
@@ -507,7 +507,7 @@
     /// Handles generating a builtin method name
     /// @param builtin the semantic info for the builtin
     /// @returns the name or "" if not valid
-    std::string generate_builtin_name(const sem::Builtin* builtin);
+    std::string generate_builtin_name(const sem::BuiltinFn* builtin);
     /// Converts a builtin to an attribute name
     /// @param builtin the builtin to convert
     /// @returns the string name of the builtin or blank on error
@@ -562,7 +562,7 @@
     template <typename F>
     bool CallBuiltinHelper(StringStream& out,
                            const ast::CallExpression* call,
-                           const sem::Builtin* builtin,
+                           const sem::BuiltinFn* builtin,
                            F&& build);
 
     /// @param s the structure
@@ -591,7 +591,7 @@
     std::unordered_map<const core::type::Struct*, std::string> builtin_struct_names_;
     std::function<bool()> emit_continuing_;
     std::unordered_map<const core::type::Matrix*, std::string> matrix_scalar_inits_;
-    std::unordered_map<const sem::Builtin*, std::string> builtins_;
+    std::unordered_map<const sem::BuiltinFn*, std::string> builtins_;
     // Polyfill functions for bitcast expression, BinaryType indicates the source type and the
     // destination type.
     std::unordered_map<BinaryType, std::string> bitcast_funcs_;
diff --git a/src/tint/lang/hlsl/writer/ast_printer/ast_printer_test.cc b/src/tint/lang/hlsl/writer/ast_printer/ast_printer_test.cc
index cb81bf6..d6cdd1b 100644
--- a/src/tint/lang/hlsl/writer/ast_printer/ast_printer_test.cc
+++ b/src/tint/lang/hlsl/writer/ast_printer/ast_printer_test.cc
@@ -24,9 +24,9 @@
 TEST_F(HlslASTPrinterTest, InvalidProgram) {
     Diagnostics().add_error(diag::System::Writer, "make the program invalid");
     ASSERT_FALSE(IsValid());
-    auto program = std::make_unique<Program>(resolver::Resolve(*this));
-    ASSERT_FALSE(program->IsValid());
-    auto result = Generate(program.get(), Options{});
+    auto program = resolver::Resolve(*this);
+    ASSERT_FALSE(program.IsValid());
+    auto result = Generate(program, Options{});
     EXPECT_FALSE(result);
     EXPECT_EQ(result.Failure(), "input program is not valid");
 }
diff --git a/src/tint/lang/hlsl/writer/ast_printer/builtin_test.cc b/src/tint/lang/hlsl/writer/ast_printer/builtin_test.cc
index a7bbede..9445273 100644
--- a/src/tint/lang/hlsl/writer/ast_printer/builtin_test.cc
+++ b/src/tint/lang/hlsl/writer/ast_printer/builtin_test.cc
@@ -36,7 +36,7 @@
 };
 
 struct BuiltinData {
-    core::Function builtin;
+    core::BuiltinFn builtin;
     CallParamType type;
     const char* hlsl_name;
 };
@@ -60,84 +60,84 @@
     return out;
 }
 
-const ast::CallExpression* GenerateCall(core::Function builtin,
+const ast::CallExpression* GenerateCall(core::BuiltinFn builtin,
                                         CallParamType type,
                                         ProgramBuilder* builder) {
     std::string name;
     StringStream str;
     str << name << builtin;
     switch (builtin) {
-        case core::Function::kAcos:
-        case core::Function::kAsin:
-        case core::Function::kAtan:
-        case core::Function::kCeil:
-        case core::Function::kCos:
-        case core::Function::kCosh:
-        case core::Function::kDpdx:
-        case core::Function::kDpdxCoarse:
-        case core::Function::kDpdxFine:
-        case core::Function::kDpdy:
-        case core::Function::kDpdyCoarse:
-        case core::Function::kDpdyFine:
-        case core::Function::kExp:
-        case core::Function::kExp2:
-        case core::Function::kFloor:
-        case core::Function::kFract:
-        case core::Function::kFwidth:
-        case core::Function::kFwidthCoarse:
-        case core::Function::kFwidthFine:
-        case core::Function::kInverseSqrt:
-        case core::Function::kLength:
-        case core::Function::kLog:
-        case core::Function::kLog2:
-        case core::Function::kNormalize:
-        case core::Function::kRound:
-        case core::Function::kSin:
-        case core::Function::kSinh:
-        case core::Function::kSqrt:
-        case core::Function::kTan:
-        case core::Function::kTanh:
+        case core::BuiltinFn::kAcos:
+        case core::BuiltinFn::kAsin:
+        case core::BuiltinFn::kAtan:
+        case core::BuiltinFn::kCeil:
+        case core::BuiltinFn::kCos:
+        case core::BuiltinFn::kCosh:
+        case core::BuiltinFn::kDpdx:
+        case core::BuiltinFn::kDpdxCoarse:
+        case core::BuiltinFn::kDpdxFine:
+        case core::BuiltinFn::kDpdy:
+        case core::BuiltinFn::kDpdyCoarse:
+        case core::BuiltinFn::kDpdyFine:
+        case core::BuiltinFn::kExp:
+        case core::BuiltinFn::kExp2:
+        case core::BuiltinFn::kFloor:
+        case core::BuiltinFn::kFract:
+        case core::BuiltinFn::kFwidth:
+        case core::BuiltinFn::kFwidthCoarse:
+        case core::BuiltinFn::kFwidthFine:
+        case core::BuiltinFn::kInverseSqrt:
+        case core::BuiltinFn::kLength:
+        case core::BuiltinFn::kLog:
+        case core::BuiltinFn::kLog2:
+        case core::BuiltinFn::kNormalize:
+        case core::BuiltinFn::kRound:
+        case core::BuiltinFn::kSin:
+        case core::BuiltinFn::kSinh:
+        case core::BuiltinFn::kSqrt:
+        case core::BuiltinFn::kTan:
+        case core::BuiltinFn::kTanh:
             if (type == CallParamType::kF16) {
                 return builder->Call(str.str(), "h2");
             } else {
                 return builder->Call(str.str(), "f2");
             }
-        case core::Function::kLdexp:
+        case core::BuiltinFn::kLdexp:
             if (type == CallParamType::kF16) {
                 return builder->Call(str.str(), "h2", "i2");
             } else {
                 return builder->Call(str.str(), "f2", "i2");
             }
-        case core::Function::kAtan2:
-        case core::Function::kDot:
-        case core::Function::kDistance:
-        case core::Function::kPow:
-        case core::Function::kReflect:
-        case core::Function::kStep:
+        case core::BuiltinFn::kAtan2:
+        case core::BuiltinFn::kDot:
+        case core::BuiltinFn::kDistance:
+        case core::BuiltinFn::kPow:
+        case core::BuiltinFn::kReflect:
+        case core::BuiltinFn::kStep:
             if (type == CallParamType::kF16) {
                 return builder->Call(str.str(), "h2", "h2");
             } else {
                 return builder->Call(str.str(), "f2", "f2");
             }
-        case core::Function::kCross:
+        case core::BuiltinFn::kCross:
             if (type == CallParamType::kF16) {
                 return builder->Call(str.str(), "h3", "h3");
             } else {
                 return builder->Call(str.str(), "f3", "f3");
             }
-        case core::Function::kFma:
-        case core::Function::kMix:
-        case core::Function::kFaceForward:
-        case core::Function::kSmoothstep:
+        case core::BuiltinFn::kFma:
+        case core::BuiltinFn::kMix:
+        case core::BuiltinFn::kFaceForward:
+        case core::BuiltinFn::kSmoothstep:
             if (type == CallParamType::kF16) {
                 return builder->Call(str.str(), "h2", "h2", "h2");
             } else {
                 return builder->Call(str.str(), "f2", "f2", "f2");
             }
-        case core::Function::kAll:
-        case core::Function::kAny:
+        case core::BuiltinFn::kAll:
+        case core::BuiltinFn::kAny:
             return builder->Call(str.str(), "b2");
-        case core::Function::kAbs:
+        case core::BuiltinFn::kAbs:
             if (type == CallParamType::kF32) {
                 return builder->Call(str.str(), "f2");
             } else if (type == CallParamType::kF16) {
@@ -145,11 +145,11 @@
             } else {
                 return builder->Call(str.str(), "u2");
             }
-        case core::Function::kCountOneBits:
-        case core::Function::kReverseBits:
+        case core::BuiltinFn::kCountOneBits:
+        case core::BuiltinFn::kReverseBits:
             return builder->Call(str.str(), "u2");
-        case core::Function::kMax:
-        case core::Function::kMin:
+        case core::BuiltinFn::kMax:
+        case core::BuiltinFn::kMin:
             if (type == CallParamType::kF32) {
                 return builder->Call(str.str(), "f2", "f2");
             } else if (type == CallParamType::kF16) {
@@ -157,7 +157,7 @@
             } else {
                 return builder->Call(str.str(), "u2", "u2");
             }
-        case core::Function::kClamp:
+        case core::BuiltinFn::kClamp:
             if (type == CallParamType::kF32) {
                 return builder->Call(str.str(), "f2", "f2", "f2");
             } else if (type == CallParamType::kF16) {
@@ -165,19 +165,19 @@
             } else {
                 return builder->Call(str.str(), "u2", "u2", "u2");
             }
-        case core::Function::kSelect:
+        case core::BuiltinFn::kSelect:
             if (type == CallParamType::kF16) {
                 return builder->Call(str.str(), "h2", "h2", "b2");
             } else {
                 return builder->Call(str.str(), "f2", "f2", "b2");
             }
-        case core::Function::kDeterminant:
+        case core::BuiltinFn::kDeterminant:
             if (type == CallParamType::kF16) {
                 return builder->Call(str.str(), "hm2x2");
             } else {
                 return builder->Call(str.str(), "m2x2");
             }
-        case core::Function::kTranspose:
+        case core::BuiltinFn::kTranspose:
             if (type == CallParamType::kF16) {
                 return builder->Call(str.str(), "hm3x2");
             } else {
@@ -224,7 +224,7 @@
     ASSERT_NE(sem, nullptr);
     auto* target = sem->Target();
     ASSERT_NE(target, nullptr);
-    auto* builtin = target->As<sem::Builtin>();
+    auto* builtin = target->As<sem::BuiltinFn>();
     ASSERT_NE(builtin, nullptr);
 
     EXPECT_EQ(gen.generate_builtin_name(builtin), param.hlsl_name);
@@ -233,105 +233,105 @@
     HlslASTPrinterTest_Builtin,
     HlslBuiltinTest,
     testing::Values(/* Logical built-in */
-                    BuiltinData{core::Function::kAll, CallParamType::kBool, "all"},
-                    BuiltinData{core::Function::kAny, CallParamType::kBool, "any"},
+                    BuiltinData{core::BuiltinFn::kAll, CallParamType::kBool, "all"},
+                    BuiltinData{core::BuiltinFn::kAny, CallParamType::kBool, "any"},
                     /* Float built-in */
-                    BuiltinData{core::Function::kAbs, CallParamType::kF32, "abs"},
-                    BuiltinData{core::Function::kAbs, CallParamType::kF16, "abs"},
-                    BuiltinData{core::Function::kAcos, CallParamType::kF32, "acos"},
-                    BuiltinData{core::Function::kAcos, CallParamType::kF16, "acos"},
-                    BuiltinData{core::Function::kAsin, CallParamType::kF32, "asin"},
-                    BuiltinData{core::Function::kAsin, CallParamType::kF16, "asin"},
-                    BuiltinData{core::Function::kAtan, CallParamType::kF32, "atan"},
-                    BuiltinData{core::Function::kAtan, CallParamType::kF16, "atan"},
-                    BuiltinData{core::Function::kAtan2, CallParamType::kF32, "atan2"},
-                    BuiltinData{core::Function::kAtan2, CallParamType::kF16, "atan2"},
-                    BuiltinData{core::Function::kCeil, CallParamType::kF32, "ceil"},
-                    BuiltinData{core::Function::kCeil, CallParamType::kF16, "ceil"},
-                    BuiltinData{core::Function::kClamp, CallParamType::kF32, "clamp"},
-                    BuiltinData{core::Function::kClamp, CallParamType::kF16, "clamp"},
-                    BuiltinData{core::Function::kCos, CallParamType::kF32, "cos"},
-                    BuiltinData{core::Function::kCos, CallParamType::kF16, "cos"},
-                    BuiltinData{core::Function::kCosh, CallParamType::kF32, "cosh"},
-                    BuiltinData{core::Function::kCosh, CallParamType::kF16, "cosh"},
-                    BuiltinData{core::Function::kCross, CallParamType::kF32, "cross"},
-                    BuiltinData{core::Function::kCross, CallParamType::kF16, "cross"},
-                    BuiltinData{core::Function::kDistance, CallParamType::kF32, "distance"},
-                    BuiltinData{core::Function::kDistance, CallParamType::kF16, "distance"},
-                    BuiltinData{core::Function::kExp, CallParamType::kF32, "exp"},
-                    BuiltinData{core::Function::kExp, CallParamType::kF16, "exp"},
-                    BuiltinData{core::Function::kExp2, CallParamType::kF32, "exp2"},
-                    BuiltinData{core::Function::kExp2, CallParamType::kF16, "exp2"},
-                    BuiltinData{core::Function::kFaceForward, CallParamType::kF32, "faceforward"},
-                    BuiltinData{core::Function::kFaceForward, CallParamType::kF16, "faceforward"},
-                    BuiltinData{core::Function::kFloor, CallParamType::kF32, "floor"},
-                    BuiltinData{core::Function::kFloor, CallParamType::kF16, "floor"},
-                    BuiltinData{core::Function::kFma, CallParamType::kF32, "mad"},
-                    BuiltinData{core::Function::kFma, CallParamType::kF16, "mad"},
-                    BuiltinData{core::Function::kFract, CallParamType::kF32, "frac"},
-                    BuiltinData{core::Function::kFract, CallParamType::kF16, "frac"},
-                    BuiltinData{core::Function::kInverseSqrt, CallParamType::kF32, "rsqrt"},
-                    BuiltinData{core::Function::kInverseSqrt, CallParamType::kF16, "rsqrt"},
-                    BuiltinData{core::Function::kLdexp, CallParamType::kF32, "ldexp"},
-                    BuiltinData{core::Function::kLdexp, CallParamType::kF16, "ldexp"},
-                    BuiltinData{core::Function::kLength, CallParamType::kF32, "length"},
-                    BuiltinData{core::Function::kLength, CallParamType::kF16, "length"},
-                    BuiltinData{core::Function::kLog, CallParamType::kF32, "log"},
-                    BuiltinData{core::Function::kLog, CallParamType::kF16, "log"},
-                    BuiltinData{core::Function::kLog2, CallParamType::kF32, "log2"},
-                    BuiltinData{core::Function::kLog2, CallParamType::kF16, "log2"},
-                    BuiltinData{core::Function::kMax, CallParamType::kF32, "max"},
-                    BuiltinData{core::Function::kMax, CallParamType::kF16, "max"},
-                    BuiltinData{core::Function::kMin, CallParamType::kF32, "min"},
-                    BuiltinData{core::Function::kMin, CallParamType::kF16, "min"},
-                    BuiltinData{core::Function::kMix, CallParamType::kF32, "lerp"},
-                    BuiltinData{core::Function::kMix, CallParamType::kF16, "lerp"},
-                    BuiltinData{core::Function::kNormalize, CallParamType::kF32, "normalize"},
-                    BuiltinData{core::Function::kNormalize, CallParamType::kF16, "normalize"},
-                    BuiltinData{core::Function::kPow, CallParamType::kF32, "pow"},
-                    BuiltinData{core::Function::kPow, CallParamType::kF16, "pow"},
-                    BuiltinData{core::Function::kReflect, CallParamType::kF32, "reflect"},
-                    BuiltinData{core::Function::kReflect, CallParamType::kF16, "reflect"},
-                    BuiltinData{core::Function::kSin, CallParamType::kF32, "sin"},
-                    BuiltinData{core::Function::kSin, CallParamType::kF16, "sin"},
-                    BuiltinData{core::Function::kSinh, CallParamType::kF32, "sinh"},
-                    BuiltinData{core::Function::kSinh, CallParamType::kF16, "sinh"},
-                    BuiltinData{core::Function::kSmoothstep, CallParamType::kF32, "smoothstep"},
-                    BuiltinData{core::Function::kSmoothstep, CallParamType::kF16, "smoothstep"},
-                    BuiltinData{core::Function::kSqrt, CallParamType::kF32, "sqrt"},
-                    BuiltinData{core::Function::kSqrt, CallParamType::kF16, "sqrt"},
-                    BuiltinData{core::Function::kStep, CallParamType::kF32, "step"},
-                    BuiltinData{core::Function::kStep, CallParamType::kF16, "step"},
-                    BuiltinData{core::Function::kTan, CallParamType::kF32, "tan"},
-                    BuiltinData{core::Function::kTan, CallParamType::kF16, "tan"},
-                    BuiltinData{core::Function::kTanh, CallParamType::kF32, "tanh"},
-                    BuiltinData{core::Function::kTanh, CallParamType::kF16, "tanh"},
+                    BuiltinData{core::BuiltinFn::kAbs, CallParamType::kF32, "abs"},
+                    BuiltinData{core::BuiltinFn::kAbs, CallParamType::kF16, "abs"},
+                    BuiltinData{core::BuiltinFn::kAcos, CallParamType::kF32, "acos"},
+                    BuiltinData{core::BuiltinFn::kAcos, CallParamType::kF16, "acos"},
+                    BuiltinData{core::BuiltinFn::kAsin, CallParamType::kF32, "asin"},
+                    BuiltinData{core::BuiltinFn::kAsin, CallParamType::kF16, "asin"},
+                    BuiltinData{core::BuiltinFn::kAtan, CallParamType::kF32, "atan"},
+                    BuiltinData{core::BuiltinFn::kAtan, CallParamType::kF16, "atan"},
+                    BuiltinData{core::BuiltinFn::kAtan2, CallParamType::kF32, "atan2"},
+                    BuiltinData{core::BuiltinFn::kAtan2, CallParamType::kF16, "atan2"},
+                    BuiltinData{core::BuiltinFn::kCeil, CallParamType::kF32, "ceil"},
+                    BuiltinData{core::BuiltinFn::kCeil, CallParamType::kF16, "ceil"},
+                    BuiltinData{core::BuiltinFn::kClamp, CallParamType::kF32, "clamp"},
+                    BuiltinData{core::BuiltinFn::kClamp, CallParamType::kF16, "clamp"},
+                    BuiltinData{core::BuiltinFn::kCos, CallParamType::kF32, "cos"},
+                    BuiltinData{core::BuiltinFn::kCos, CallParamType::kF16, "cos"},
+                    BuiltinData{core::BuiltinFn::kCosh, CallParamType::kF32, "cosh"},
+                    BuiltinData{core::BuiltinFn::kCosh, CallParamType::kF16, "cosh"},
+                    BuiltinData{core::BuiltinFn::kCross, CallParamType::kF32, "cross"},
+                    BuiltinData{core::BuiltinFn::kCross, CallParamType::kF16, "cross"},
+                    BuiltinData{core::BuiltinFn::kDistance, CallParamType::kF32, "distance"},
+                    BuiltinData{core::BuiltinFn::kDistance, CallParamType::kF16, "distance"},
+                    BuiltinData{core::BuiltinFn::kExp, CallParamType::kF32, "exp"},
+                    BuiltinData{core::BuiltinFn::kExp, CallParamType::kF16, "exp"},
+                    BuiltinData{core::BuiltinFn::kExp2, CallParamType::kF32, "exp2"},
+                    BuiltinData{core::BuiltinFn::kExp2, CallParamType::kF16, "exp2"},
+                    BuiltinData{core::BuiltinFn::kFaceForward, CallParamType::kF32, "faceforward"},
+                    BuiltinData{core::BuiltinFn::kFaceForward, CallParamType::kF16, "faceforward"},
+                    BuiltinData{core::BuiltinFn::kFloor, CallParamType::kF32, "floor"},
+                    BuiltinData{core::BuiltinFn::kFloor, CallParamType::kF16, "floor"},
+                    BuiltinData{core::BuiltinFn::kFma, CallParamType::kF32, "mad"},
+                    BuiltinData{core::BuiltinFn::kFma, CallParamType::kF16, "mad"},
+                    BuiltinData{core::BuiltinFn::kFract, CallParamType::kF32, "frac"},
+                    BuiltinData{core::BuiltinFn::kFract, CallParamType::kF16, "frac"},
+                    BuiltinData{core::BuiltinFn::kInverseSqrt, CallParamType::kF32, "rsqrt"},
+                    BuiltinData{core::BuiltinFn::kInverseSqrt, CallParamType::kF16, "rsqrt"},
+                    BuiltinData{core::BuiltinFn::kLdexp, CallParamType::kF32, "ldexp"},
+                    BuiltinData{core::BuiltinFn::kLdexp, CallParamType::kF16, "ldexp"},
+                    BuiltinData{core::BuiltinFn::kLength, CallParamType::kF32, "length"},
+                    BuiltinData{core::BuiltinFn::kLength, CallParamType::kF16, "length"},
+                    BuiltinData{core::BuiltinFn::kLog, CallParamType::kF32, "log"},
+                    BuiltinData{core::BuiltinFn::kLog, CallParamType::kF16, "log"},
+                    BuiltinData{core::BuiltinFn::kLog2, CallParamType::kF32, "log2"},
+                    BuiltinData{core::BuiltinFn::kLog2, CallParamType::kF16, "log2"},
+                    BuiltinData{core::BuiltinFn::kMax, CallParamType::kF32, "max"},
+                    BuiltinData{core::BuiltinFn::kMax, CallParamType::kF16, "max"},
+                    BuiltinData{core::BuiltinFn::kMin, CallParamType::kF32, "min"},
+                    BuiltinData{core::BuiltinFn::kMin, CallParamType::kF16, "min"},
+                    BuiltinData{core::BuiltinFn::kMix, CallParamType::kF32, "lerp"},
+                    BuiltinData{core::BuiltinFn::kMix, CallParamType::kF16, "lerp"},
+                    BuiltinData{core::BuiltinFn::kNormalize, CallParamType::kF32, "normalize"},
+                    BuiltinData{core::BuiltinFn::kNormalize, CallParamType::kF16, "normalize"},
+                    BuiltinData{core::BuiltinFn::kPow, CallParamType::kF32, "pow"},
+                    BuiltinData{core::BuiltinFn::kPow, CallParamType::kF16, "pow"},
+                    BuiltinData{core::BuiltinFn::kReflect, CallParamType::kF32, "reflect"},
+                    BuiltinData{core::BuiltinFn::kReflect, CallParamType::kF16, "reflect"},
+                    BuiltinData{core::BuiltinFn::kSin, CallParamType::kF32, "sin"},
+                    BuiltinData{core::BuiltinFn::kSin, CallParamType::kF16, "sin"},
+                    BuiltinData{core::BuiltinFn::kSinh, CallParamType::kF32, "sinh"},
+                    BuiltinData{core::BuiltinFn::kSinh, CallParamType::kF16, "sinh"},
+                    BuiltinData{core::BuiltinFn::kSmoothstep, CallParamType::kF32, "smoothstep"},
+                    BuiltinData{core::BuiltinFn::kSmoothstep, CallParamType::kF16, "smoothstep"},
+                    BuiltinData{core::BuiltinFn::kSqrt, CallParamType::kF32, "sqrt"},
+                    BuiltinData{core::BuiltinFn::kSqrt, CallParamType::kF16, "sqrt"},
+                    BuiltinData{core::BuiltinFn::kStep, CallParamType::kF32, "step"},
+                    BuiltinData{core::BuiltinFn::kStep, CallParamType::kF16, "step"},
+                    BuiltinData{core::BuiltinFn::kTan, CallParamType::kF32, "tan"},
+                    BuiltinData{core::BuiltinFn::kTan, CallParamType::kF16, "tan"},
+                    BuiltinData{core::BuiltinFn::kTanh, CallParamType::kF32, "tanh"},
+                    BuiltinData{core::BuiltinFn::kTanh, CallParamType::kF16, "tanh"},
                     /* Integer built-in */
-                    BuiltinData{core::Function::kAbs, CallParamType::kU32, "abs"},
-                    BuiltinData{core::Function::kClamp, CallParamType::kU32, "clamp"},
-                    BuiltinData{core::Function::kCountOneBits, CallParamType::kU32, "countbits"},
-                    BuiltinData{core::Function::kMax, CallParamType::kU32, "max"},
-                    BuiltinData{core::Function::kMin, CallParamType::kU32, "min"},
-                    BuiltinData{core::Function::kReverseBits, CallParamType::kU32, "reversebits"},
-                    BuiltinData{core::Function::kRound, CallParamType::kU32, "round"},
+                    BuiltinData{core::BuiltinFn::kAbs, CallParamType::kU32, "abs"},
+                    BuiltinData{core::BuiltinFn::kClamp, CallParamType::kU32, "clamp"},
+                    BuiltinData{core::BuiltinFn::kCountOneBits, CallParamType::kU32, "countbits"},
+                    BuiltinData{core::BuiltinFn::kMax, CallParamType::kU32, "max"},
+                    BuiltinData{core::BuiltinFn::kMin, CallParamType::kU32, "min"},
+                    BuiltinData{core::BuiltinFn::kReverseBits, CallParamType::kU32, "reversebits"},
+                    BuiltinData{core::BuiltinFn::kRound, CallParamType::kU32, "round"},
                     /* Matrix built-in */
-                    BuiltinData{core::Function::kDeterminant, CallParamType::kF32, "determinant"},
-                    BuiltinData{core::Function::kDeterminant, CallParamType::kF16, "determinant"},
-                    BuiltinData{core::Function::kTranspose, CallParamType::kF32, "transpose"},
-                    BuiltinData{core::Function::kTranspose, CallParamType::kF16, "transpose"},
+                    BuiltinData{core::BuiltinFn::kDeterminant, CallParamType::kF32, "determinant"},
+                    BuiltinData{core::BuiltinFn::kDeterminant, CallParamType::kF16, "determinant"},
+                    BuiltinData{core::BuiltinFn::kTranspose, CallParamType::kF32, "transpose"},
+                    BuiltinData{core::BuiltinFn::kTranspose, CallParamType::kF16, "transpose"},
                     /* Vector built-in */
-                    BuiltinData{core::Function::kDot, CallParamType::kF32, "dot"},
-                    BuiltinData{core::Function::kDot, CallParamType::kF16, "dot"},
+                    BuiltinData{core::BuiltinFn::kDot, CallParamType::kF32, "dot"},
+                    BuiltinData{core::BuiltinFn::kDot, CallParamType::kF16, "dot"},
                     /* Derivate built-in */
-                    BuiltinData{core::Function::kDpdx, CallParamType::kF32, "ddx"},
-                    BuiltinData{core::Function::kDpdxCoarse, CallParamType::kF32, "ddx_coarse"},
-                    BuiltinData{core::Function::kDpdxFine, CallParamType::kF32, "ddx_fine"},
-                    BuiltinData{core::Function::kDpdy, CallParamType::kF32, "ddy"},
-                    BuiltinData{core::Function::kDpdyCoarse, CallParamType::kF32, "ddy_coarse"},
-                    BuiltinData{core::Function::kDpdyFine, CallParamType::kF32, "ddy_fine"},
-                    BuiltinData{core::Function::kFwidth, CallParamType::kF32, "fwidth"},
-                    BuiltinData{core::Function::kFwidthCoarse, CallParamType::kF32, "fwidth"},
-                    BuiltinData{core::Function::kFwidthFine, CallParamType::kF32, "fwidth"}));
+                    BuiltinData{core::BuiltinFn::kDpdx, CallParamType::kF32, "ddx"},
+                    BuiltinData{core::BuiltinFn::kDpdxCoarse, CallParamType::kF32, "ddx_coarse"},
+                    BuiltinData{core::BuiltinFn::kDpdxFine, CallParamType::kF32, "ddx_fine"},
+                    BuiltinData{core::BuiltinFn::kDpdy, CallParamType::kF32, "ddy"},
+                    BuiltinData{core::BuiltinFn::kDpdyCoarse, CallParamType::kF32, "ddy_coarse"},
+                    BuiltinData{core::BuiltinFn::kDpdyFine, CallParamType::kF32, "ddy_fine"},
+                    BuiltinData{core::BuiltinFn::kFwidth, CallParamType::kF32, "fwidth"},
+                    BuiltinData{core::BuiltinFn::kFwidthCoarse, CallParamType::kF32, "fwidth"},
+                    BuiltinData{core::BuiltinFn::kFwidthFine, CallParamType::kF32, "fwidth"}));
 
 TEST_F(HlslASTPrinterTest_Builtin, Builtin_Call) {
     auto* call = Call("dot", "param1", "param2");
diff --git a/src/tint/lang/hlsl/writer/ast_printer/helper_test.h b/src/tint/lang/hlsl/writer/ast_printer/helper_test.h
index a75aaed..18de924 100644
--- a/src/tint/lang/hlsl/writer/ast_printer/helper_test.h
+++ b/src/tint/lang/hlsl/writer/ast_printer/helper_test.h
@@ -51,12 +51,14 @@
         if (gen_) {
             return *gen_;
         }
-        [&] {
-            ASSERT_TRUE(IsValid()) << "Builder program is not valid\n" << Diagnostics().str();
-        }();
+        if (!IsValid()) {
+            ADD_FAILURE() << "ProgramBuilder is not valid: " << Diagnostics();
+        }
         program = std::make_unique<Program>(resolver::Resolve(*this));
-        [&] { ASSERT_TRUE(program->IsValid()) << program->Diagnostics().str(); }();
-        gen_ = std::make_unique<ASTPrinter>(program.get());
+        if (!program->IsValid()) {
+            ADD_FAILURE() << program->Diagnostics();
+        }
+        gen_ = std::make_unique<ASTPrinter>(*program);
         return *gen_;
     }
 
@@ -70,17 +72,18 @@
         if (gen_) {
             return *gen_;
         }
-        [&] {
-            ASSERT_TRUE(IsValid()) << "Builder program is not valid\n" << Diagnostics().str();
-        }();
+        if (!IsValid()) {
+            ADD_FAILURE() << "ProgramBuilder is not valid: " << Diagnostics();
+        }
         program = std::make_unique<Program>(resolver::Resolve(*this));
-        [&] { ASSERT_TRUE(program->IsValid()) << program->Diagnostics().str(); }();
+        if (!program->IsValid()) {
+            ADD_FAILURE() << program->Diagnostics();
+        }
 
-        auto sanitized_result = Sanitize(program.get(), options);
-        [&] {
-            ASSERT_TRUE(sanitized_result.program.IsValid())
-                << sanitized_result.program.Diagnostics().str();
-        }();
+        auto sanitized_result = Sanitize(*program, options);
+        if (!sanitized_result.program.IsValid()) {
+            ADD_FAILURE() << sanitized_result.program.Diagnostics();
+        }
 
         ast::transform::Manager transform_manager;
         ast::transform::DataMap transform_data;
@@ -89,10 +92,12 @@
             ast::transform::Renamer::Target::kHlslKeywords,
             /* preserve_unicode */ true);
         transform_manager.Add<tint::ast::transform::Renamer>();
-        auto result = transform_manager.Run(&sanitized_result.program, transform_data, outputs);
-        [&] { ASSERT_TRUE(result.IsValid()) << result.Diagnostics().str(); }();
+        auto result = transform_manager.Run(sanitized_result.program, transform_data, outputs);
+        if (!result.IsValid()) {
+            ADD_FAILURE() << result.Diagnostics();
+        }
         *program = std::move(result);
-        gen_ = std::make_unique<ASTPrinter>(program.get());
+        gen_ = std::make_unique<ASTPrinter>(*program);
         return *gen_;
     }
 
diff --git a/src/tint/lang/hlsl/writer/ast_raise/calculate_array_length.cc b/src/tint/lang/hlsl/writer/ast_raise/calculate_array_length.cc
index 604700f..5aa810e 100644
--- a/src/tint/lang/hlsl/writer/ast_raise/calculate_array_length.cc
+++ b/src/tint/lang/hlsl/writer/ast_raise/calculate_array_length.cc
@@ -43,11 +43,11 @@
 using namespace tint::core::fluent_types;     // NOLINT
 using namespace tint::core::number_suffixes;  // NOLINT
 
-bool ShouldRun(const Program* program) {
-    for (auto* fn : program->AST().Functions()) {
-        if (auto* sem_fn = program->Sem().Get(fn)) {
+bool ShouldRun(const Program& program) {
+    for (auto* fn : program.AST().Functions()) {
+        if (auto* sem_fn = program.Sem().Get(fn)) {
             for (auto* builtin : sem_fn->DirectlyCalledBuiltins()) {
-                if (builtin->Type() == core::Function::kArrayLength) {
+                if (builtin->Fn() == core::BuiltinFn::kArrayLength) {
                     return true;
                 }
             }
@@ -87,7 +87,7 @@
 CalculateArrayLength::CalculateArrayLength() = default;
 CalculateArrayLength::~CalculateArrayLength() = default;
 
-ast::transform::Transform::ApplyResult CalculateArrayLength::Apply(const Program* src,
+ast::transform::Transform::ApplyResult CalculateArrayLength::Apply(const Program& src,
                                                                    const ast::transform::DataMap&,
                                                                    ast::transform::DataMap&) const {
     if (!ShouldRun(src)) {
@@ -95,8 +95,8 @@
     }
 
     ProgramBuilder b;
-    program::CloneContext ctx{&b, src, /* auto_clone_symbols */ true};
-    auto& sem = src->Sem();
+    program::CloneContext ctx{&b, &src, /* auto_clone_symbols */ true};
+    auto& sem = src.Sem();
 
     // get_buffer_size_intrinsic() emits the function decorated with
     // BufferSizeIntrinsic that is transformed by the HLSL writer into a call to
@@ -126,11 +126,11 @@
     std::unordered_map<ArrayUsage, Symbol, ArrayUsage::Hasher> array_length_by_usage;
 
     // Find all the arrayLength() calls...
-    for (auto* node : src->ASTNodes().Objects()) {
+    for (auto* node : src.ASTNodes().Objects()) {
         if (auto* call_expr = node->As<ast::CallExpression>()) {
             auto* call = sem.Get(call_expr)->UnwrapMaterialize()->As<sem::Call>();
-            if (auto* builtin = call->Target()->As<sem::Builtin>()) {
-                if (builtin->Type() == core::Function::kArrayLength) {
+            if (auto* builtin = call->Target()->As<sem::BuiltinFn>()) {
+                if (builtin->Fn() == core::BuiltinFn::kArrayLength) {
                     // We're dealing with an arrayLength() call
 
                     if (auto* call_stmt = call->Stmt()->Declaration()->As<ast::CallStatement>()) {
diff --git a/src/tint/lang/hlsl/writer/ast_raise/calculate_array_length.h b/src/tint/lang/hlsl/writer/ast_raise/calculate_array_length.h
index e798a77..8d52ae7 100644
--- a/src/tint/lang/hlsl/writer/ast_raise/calculate_array_length.h
+++ b/src/tint/lang/hlsl/writer/ast_raise/calculate_array_length.h
@@ -56,7 +56,7 @@
     ~CalculateArrayLength() override;
 
     /// @copydoc ast::transform::Transform::Apply
-    ApplyResult Apply(const Program* program,
+    ApplyResult Apply(const Program& program,
                       const ast::transform::DataMap& inputs,
                       ast::transform::DataMap& outputs) const override;
 };
diff --git a/src/tint/lang/hlsl/writer/ast_raise/decompose_memory_access.cc b/src/tint/lang/hlsl/writer/ast_raise/decompose_memory_access.cc
index 9cff4ce..ab0bfca 100644
--- a/src/tint/lang/hlsl/writer/ast_raise/decompose_memory_access.cc
+++ b/src/tint/lang/hlsl/writer/ast_raise/decompose_memory_access.cc
@@ -52,9 +52,9 @@
 
 namespace {
 
-bool ShouldRun(const Program* program) {
-    for (auto* decl : program->AST().GlobalDeclarations()) {
-        if (auto* var = program->Sem().Get<sem::Variable>(decl)) {
+bool ShouldRun(const Program& program) {
+    for (auto* decl : program.AST().GlobalDeclarations()) {
+        if (auto* var = program.Sem().Get<sem::Variable>(decl)) {
             if (var->AddressSpace() == core::AddressSpace::kStorage ||
                 var->AddressSpace() == core::AddressSpace::kUniform) {
                 return true;
@@ -129,7 +129,7 @@
 /// AtomicKey is the unordered map key to an atomic intrinsic.
 struct AtomicKey {
     core::type::Type const* el_ty = nullptr;  // element type
-    core::Function const op;                  // atomic op
+    core::BuiltinFn const op;                 // atomic op
     Symbol const buffer;                      // buffer name
     bool operator==(const AtomicKey& rhs) const {
         return el_ty == rhs.el_ty && op == rhs.op && buffer == rhs.buffer;
@@ -254,42 +254,42 @@
 /// @returns a DecomposeMemoryAccess::Intrinsic attribute that can be applied to a stub function for
 /// the atomic op and the type @p ty.
 DecomposeMemoryAccess::Intrinsic* IntrinsicAtomicFor(ast::Builder* builder,
-                                                     core::Function ity,
+                                                     core::BuiltinFn ity,
                                                      const core::type::Type* ty,
                                                      const Symbol& buffer) {
     auto op = DecomposeMemoryAccess::Intrinsic::Op::kAtomicLoad;
     switch (ity) {
-        case core::Function::kAtomicLoad:
+        case core::BuiltinFn::kAtomicLoad:
             op = DecomposeMemoryAccess::Intrinsic::Op::kAtomicLoad;
             break;
-        case core::Function::kAtomicStore:
+        case core::BuiltinFn::kAtomicStore:
             op = DecomposeMemoryAccess::Intrinsic::Op::kAtomicStore;
             break;
-        case core::Function::kAtomicAdd:
+        case core::BuiltinFn::kAtomicAdd:
             op = DecomposeMemoryAccess::Intrinsic::Op::kAtomicAdd;
             break;
-        case core::Function::kAtomicSub:
+        case core::BuiltinFn::kAtomicSub:
             op = DecomposeMemoryAccess::Intrinsic::Op::kAtomicSub;
             break;
-        case core::Function::kAtomicMax:
+        case core::BuiltinFn::kAtomicMax:
             op = DecomposeMemoryAccess::Intrinsic::Op::kAtomicMax;
             break;
-        case core::Function::kAtomicMin:
+        case core::BuiltinFn::kAtomicMin:
             op = DecomposeMemoryAccess::Intrinsic::Op::kAtomicMin;
             break;
-        case core::Function::kAtomicAnd:
+        case core::BuiltinFn::kAtomicAnd:
             op = DecomposeMemoryAccess::Intrinsic::Op::kAtomicAnd;
             break;
-        case core::Function::kAtomicOr:
+        case core::BuiltinFn::kAtomicOr:
             op = DecomposeMemoryAccess::Intrinsic::Op::kAtomicOr;
             break;
-        case core::Function::kAtomicXor:
+        case core::BuiltinFn::kAtomicXor:
             op = DecomposeMemoryAccess::Intrinsic::Op::kAtomicXor;
             break;
-        case core::Function::kAtomicExchange:
+        case core::BuiltinFn::kAtomicExchange:
             op = DecomposeMemoryAccess::Intrinsic::Op::kAtomicExchange;
             break;
-        case core::Function::kAtomicCompareExchangeWeak:
+        case core::BuiltinFn::kAtomicCompareExchangeWeak:
             op = DecomposeMemoryAccess::Intrinsic::Op::kAtomicCompareExchangeWeak;
             break;
         default:
@@ -629,38 +629,38 @@
         });
     }
 
-    /// AtomicFunc() returns a symbol to an intrinsic function that performs an  atomic operation on
+    /// AtomicFunc() returns a symbol to an builtin function that performs an  atomic operation on
     /// the storage buffer @p buffer. The function has the signature:
     // `fn atomic_op(offset : u32, ...) -> T`
     /// @param el_ty the storage buffer element type
-    /// @param intrinsic the atomic intrinsic
+    /// @param builtin the atomic builtin
     /// @param buffer the symbol of the storage buffer variable, owned by the target ProgramBuilder.
     /// @return the name of the function that performs the load
     Symbol AtomicFunc(const core::type::Type* el_ty,
-                      const sem::Builtin* intrinsic,
+                      const sem::BuiltinFn* builtin,
                       const Symbol& buffer) {
-        auto op = intrinsic->Type();
-        return tint::GetOrCreate(atomic_funcs, AtomicKey{el_ty, op, buffer}, [&] {
+        auto fn = builtin->Fn();
+        return tint::GetOrCreate(atomic_funcs, AtomicKey{el_ty, fn, buffer}, [&] {
             // The first parameter to all WGSL atomics is the expression to the
             // atomic. This is replaced with two parameters: the buffer and offset.
             Vector params{b.Param("offset", b.ty.u32())};
 
             // Other parameters are copied as-is:
-            for (size_t i = 1; i < intrinsic->Parameters().Length(); i++) {
-                auto* param = intrinsic->Parameters()[i];
+            for (size_t i = 1; i < builtin->Parameters().Length(); i++) {
+                auto* param = builtin->Parameters()[i];
                 auto ty = CreateASTTypeFor(ctx, param->Type());
                 params.Push(b.Param("param_" + std::to_string(i), ty));
             }
 
-            auto* atomic = IntrinsicAtomicFor(ctx.dst, op, el_ty, buffer);
+            auto* atomic = IntrinsicAtomicFor(ctx.dst, fn, el_ty, buffer);
             if (TINT_UNLIKELY(!atomic)) {
-                TINT_ICE() << "IntrinsicAtomicFor() returned nullptr for op " << op << " and type "
+                TINT_ICE() << "IntrinsicAtomicFor() returned nullptr for fn " << fn << " and type "
                            << el_ty->TypeInfo().name;
             }
 
-            ast::Type ret_ty = CreateASTTypeFor(ctx, intrinsic->ReturnType());
+            ast::Type ret_ty = CreateASTTypeFor(ctx, builtin->ReturnType());
 
-            auto name = b.Symbols().New(buffer.Name() + intrinsic->str());
+            auto name = b.Symbols().New(buffer.Name() + builtin->str());
             b.Func(name, std::move(params), ret_ty, nullptr,
                    Vector{
                        atomic,
@@ -795,16 +795,16 @@
 DecomposeMemoryAccess::~DecomposeMemoryAccess() = default;
 
 ast::transform::Transform::ApplyResult DecomposeMemoryAccess::Apply(
-    const Program* src,
+    const Program& src,
     const ast::transform::DataMap&,
     ast::transform::DataMap&) const {
     if (!ShouldRun(src)) {
         return SkipTransform;
     }
 
-    auto& sem = src->Sem();
+    auto& sem = src.Sem();
     ProgramBuilder b;
-    program::CloneContext ctx{&b, src, /* auto_clone_symbols */ true};
+    program::CloneContext ctx{&b, &src, /* auto_clone_symbols */ true};
     State state(ctx);
 
     // Scan the AST nodes for storage and uniform buffer accesses. Complex
@@ -815,7 +815,7 @@
     // Inner-most expression nodes are guaranteed to be visited first because AST
     // nodes are fully immutable and require their children to be constructed
     // first so their pointer can be passed to the parent's initializer.
-    for (auto* node : src->ASTNodes().Objects()) {
+    for (auto* node : src.ASTNodes().Objects()) {
         if (auto* ident = node->As<ast::IdentifierExpression>()) {
             // X
             if (auto* sem_ident = sem.GetVal(ident)) {
@@ -921,8 +921,8 @@
 
         if (auto* call_expr = node->As<ast::CallExpression>()) {
             auto* call = sem.Get(call_expr)->UnwrapMaterialize()->As<sem::Call>();
-            if (auto* builtin = call->Target()->As<sem::Builtin>()) {
-                if (builtin->Type() == core::Function::kArrayLength) {
+            if (auto* builtin = call->Target()->As<sem::BuiltinFn>()) {
+                if (builtin->Fn() == core::BuiltinFn::kArrayLength) {
                     // arrayLength(X)
                     // Don't convert X into a load, this builtin actually requires the real pointer.
                     state.TakeAccess(call_expr->args[0]);
diff --git a/src/tint/lang/hlsl/writer/ast_raise/decompose_memory_access.h b/src/tint/lang/hlsl/writer/ast_raise/decompose_memory_access.h
index cea9dd9..92e5ec2 100644
--- a/src/tint/lang/hlsl/writer/ast_raise/decompose_memory_access.h
+++ b/src/tint/lang/hlsl/writer/ast_raise/decompose_memory_access.h
@@ -117,7 +117,7 @@
     ~DecomposeMemoryAccess() override;
 
     /// @copydoc ast::transform::Transform::Apply
-    ApplyResult Apply(const Program* program,
+    ApplyResult Apply(const Program& program,
                       const ast::transform::DataMap& inputs,
                       ast::transform::DataMap& outputs) const override;
 
diff --git a/src/tint/lang/hlsl/writer/ast_raise/localize_struct_array_assignment.cc b/src/tint/lang/hlsl/writer/ast_raise/localize_struct_array_assignment.cc
index 883c911..3494440 100644
--- a/src/tint/lang/hlsl/writer/ast_raise/localize_struct_array_assignment.cc
+++ b/src/tint/lang/hlsl/writer/ast_raise/localize_struct_array_assignment.cc
@@ -38,7 +38,7 @@
 struct LocalizeStructArrayAssignment::State {
     /// Constructor
     /// @param program the source program
-    explicit State(const Program* program) : src(program) {}
+    explicit State(const Program& program) : src(program) {}
 
     /// Runs the transform
     /// @returns the new program or SkipTransform if the transform is not required
@@ -51,7 +51,7 @@
 
         bool made_changes = false;
 
-        for (auto* node : ctx.src->ASTNodes().Objects()) {
+        for (auto* node : src.ASTNodes().Objects()) {
             if (auto* assign_stmt = node->As<ast::AssignmentStatement>()) {
                 // Process if it's an assignment statement to a dynamically indexed array
                 // within a struct on a function or private storage variable. This
@@ -154,11 +154,11 @@
 
   private:
     /// The source program
-    const Program* const src;
+    const Program& src;
     /// The target program builder
     ProgramBuilder b;
     /// The clone context
-    program::CloneContext ctx = {&b, src, /* auto_clone_symbols */ true};
+    program::CloneContext ctx = {&b, &src, /* auto_clone_symbols */ true};
 
     /// Returns true if `expr` contains an index accessor expression to a
     /// structure member of array type.
@@ -166,12 +166,12 @@
         bool result = false;
         TraverseExpressions(expr, [&](const ast::IndexAccessorExpression* ia) {
             // Indexing using a runtime value?
-            auto* idx_sem = src->Sem().GetVal(ia->index);
+            auto* idx_sem = src.Sem().GetVal(ia->index);
             if (!idx_sem->ConstantValue()) {
                 // Indexing a member access expr?
                 if (auto* ma = ia->object->As<ast::MemberAccessorExpression>()) {
                     // That accesses an array?
-                    if (src->TypeOf(ma)->UnwrapRef()->Is<core::type::Array>()) {
+                    if (src.TypeOf(ma)->UnwrapRef()->Is<core::type::Array>()) {
                         result = true;
                         return ast::TraverseAction::Stop;
                     }
@@ -188,7 +188,7 @@
     // See https://www.w3.org/TR/WGSL/#originating-variable-section
     std::pair<const core::type::Type*, core::AddressSpace> GetOriginatingTypeAndAddressSpace(
         const ast::AssignmentStatement* assign_stmt) {
-        auto* root_ident = src->Sem().GetVal(assign_stmt->lhs)->RootIdentifier();
+        auto* root_ident = src.Sem().GetVal(assign_stmt->lhs)->RootIdentifier();
         if (TINT_UNLIKELY(!root_ident)) {
             TINT_ICE() << "Unable to determine originating variable for lhs of assignment "
                           "statement";
@@ -216,7 +216,7 @@
 LocalizeStructArrayAssignment::~LocalizeStructArrayAssignment() = default;
 
 ast::transform::Transform::ApplyResult LocalizeStructArrayAssignment::Apply(
-    const Program* src,
+    const Program& src,
     const ast::transform::DataMap&,
     ast::transform::DataMap&) const {
     return State{src}.Run();
diff --git a/src/tint/lang/hlsl/writer/ast_raise/localize_struct_array_assignment.h b/src/tint/lang/hlsl/writer/ast_raise/localize_struct_array_assignment.h
index d70e04e..0421116 100644
--- a/src/tint/lang/hlsl/writer/ast_raise/localize_struct_array_assignment.h
+++ b/src/tint/lang/hlsl/writer/ast_raise/localize_struct_array_assignment.h
@@ -37,7 +37,7 @@
     ~LocalizeStructArrayAssignment() override;
 
     /// @copydoc ast::transform::Transform::Apply
-    ApplyResult Apply(const Program* program,
+    ApplyResult Apply(const Program& program,
                       const ast::transform::DataMap& inputs,
                       ast::transform::DataMap& outputs) const override;
 
diff --git a/src/tint/lang/hlsl/writer/ast_raise/num_workgroups_from_uniform.cc b/src/tint/lang/hlsl/writer/ast_raise/num_workgroups_from_uniform.cc
index 3bb5a40..62356e1 100644
--- a/src/tint/lang/hlsl/writer/ast_raise/num_workgroups_from_uniform.cc
+++ b/src/tint/lang/hlsl/writer/ast_raise/num_workgroups_from_uniform.cc
@@ -36,10 +36,10 @@
 namespace tint::hlsl::writer {
 namespace {
 
-bool ShouldRun(const Program* program) {
-    for (auto* node : program->ASTNodes().Objects()) {
+bool ShouldRun(const Program& program) {
+    for (auto* node : program.ASTNodes().Objects()) {
         if (auto* attr = node->As<ast::BuiltinAttribute>()) {
-            if (program->Sem().Get(attr)->Value() == core::BuiltinValue::kNumWorkgroups) {
+            if (program.Sem().Get(attr)->Value() == core::BuiltinValue::kNumWorkgroups) {
                 return true;
             }
         }
@@ -69,11 +69,11 @@
 NumWorkgroupsFromUniform::~NumWorkgroupsFromUniform() = default;
 
 ast::transform::Transform::ApplyResult NumWorkgroupsFromUniform::Apply(
-    const Program* src,
+    const Program& src,
     const ast::transform::DataMap& inputs,
     ast::transform::DataMap&) const {
     ProgramBuilder b;
-    program::CloneContext ctx{&b, src, /* auto_clone_symbols */ true};
+    program::CloneContext ctx{&b, &src, /* auto_clone_symbols */ true};
 
     auto* cfg = inputs.Get<Config>();
     if (cfg == nullptr) {
@@ -90,13 +90,13 @@
 
     // Find all entry point parameters that declare the num_workgroups builtin.
     std::unordered_set<Accessor, Accessor::Hasher> to_replace;
-    for (auto* func : src->AST().Functions()) {
+    for (auto* func : src.AST().Functions()) {
         // num_workgroups is only valid for compute stages.
         if (func->PipelineStage() != ast::PipelineStage::kCompute) {
             continue;
         }
 
-        for (auto* param : src->Sem().Get(func)->Parameters()) {
+        for (auto* param : src.Sem().Get(func)->Parameters()) {
             // Because the CanonicalizeEntryPointIO transform has been run, builtins
             // will only appear as struct members.
             auto* str = param->Type()->As<sem::Struct>();
@@ -124,7 +124,7 @@
                 // If this is the only member, remove the struct and parameter too.
                 if (str->Members().Length() == 1) {
                     ctx.Remove(func->params, param->Declaration());
-                    ctx.Remove(src->AST().GlobalDeclarations(), str->Declaration());
+                    ctx.Remove(src.AST().GlobalDeclarations(), str->Declaration());
                 }
             }
         }
@@ -150,8 +150,8 @@
                 // plus 1, or group 0 if no resource bound.
                 group = 0;
 
-                for (auto* global : src->AST().GlobalVariables()) {
-                    auto* global_sem = src->Sem().Get<sem::GlobalVariable>(global);
+                for (auto* global : src.AST().GlobalVariables()) {
+                    auto* global_sem = src.Sem().Get<sem::GlobalVariable>(global);
                     if (auto bp = global_sem->BindingPoint()) {
                         if (bp->group >= group) {
                             group = bp->group + 1;
@@ -171,7 +171,7 @@
 
     // Now replace all the places where the builtins are accessed with the value
     // loaded from the uniform buffer.
-    for (auto* node : src->ASTNodes().Objects()) {
+    for (auto* node : src.ASTNodes().Objects()) {
         auto* accessor = node->As<ast::MemberAccessorExpression>();
         if (!accessor) {
             continue;
diff --git a/src/tint/lang/hlsl/writer/ast_raise/num_workgroups_from_uniform.h b/src/tint/lang/hlsl/writer/ast_raise/num_workgroups_from_uniform.h
index 4b26e33..a3beb78 100644
--- a/src/tint/lang/hlsl/writer/ast_raise/num_workgroups_from_uniform.h
+++ b/src/tint/lang/hlsl/writer/ast_raise/num_workgroups_from_uniform.h
@@ -69,7 +69,7 @@
     };
 
     /// @copydoc ast::transform::Transform::Apply
-    ApplyResult Apply(const Program* program,
+    ApplyResult Apply(const Program& program,
                       const ast::transform::DataMap& inputs,
                       ast::transform::DataMap& outputs) const override;
 };
diff --git a/src/tint/lang/hlsl/writer/ast_raise/remove_continue_in_switch.cc b/src/tint/lang/hlsl/writer/ast_raise/remove_continue_in_switch.cc
index 4f1808b..afe6e1f 100644
--- a/src/tint/lang/hlsl/writer/ast_raise/remove_continue_in_switch.cc
+++ b/src/tint/lang/hlsl/writer/ast_raise/remove_continue_in_switch.cc
@@ -37,13 +37,13 @@
 struct RemoveContinueInSwitch::State {
     /// Constructor
     /// @param program the source program
-    explicit State(const Program* program) : src(program) {}
+    explicit State(const Program& program) : src(program) {}
 
     /// Runs the transform
     /// @returns the new program or SkipTransform if the transform is not required
     ApplyResult Run() {
         // First collect all switch statements within loops that contain a continue statement.
-        for (auto* node : src->ASTNodes().Objects()) {
+        for (auto* node : src.ASTNodes().Objects()) {
             auto* cont = node->As<ast::ContinueStatement>();
             if (!cont) {
                 continue;
@@ -126,13 +126,13 @@
 
   private:
     /// The source program
-    const Program* const src;
+    const Program& src;
     /// The target program builder
     ProgramBuilder b;
     /// The clone context
-    program::CloneContext ctx = {&b, src, /* auto_clone_symbols */ true};
-    /// Alias to src->sem
-    const sem::Info& sem = src->Sem();
+    program::CloneContext ctx = {&b, &src, /* auto_clone_symbols */ true};
+    /// Alias to src.sem
+    const sem::Info& sem = src.Sem();
 
     // Vector of switch statements within a loop that contains at least one continue statement.
     Vector<const ast::SwitchStatement*, 4> switch_stmts;
@@ -177,7 +177,7 @@
 RemoveContinueInSwitch::~RemoveContinueInSwitch() = default;
 
 ast::transform::Transform::ApplyResult RemoveContinueInSwitch::Apply(
-    const Program* src,
+    const Program& src,
     const ast::transform::DataMap&,
     ast::transform::DataMap&) const {
     State state(src);
diff --git a/src/tint/lang/hlsl/writer/ast_raise/remove_continue_in_switch.h b/src/tint/lang/hlsl/writer/ast_raise/remove_continue_in_switch.h
index 4ad747e..a4623a2 100644
--- a/src/tint/lang/hlsl/writer/ast_raise/remove_continue_in_switch.h
+++ b/src/tint/lang/hlsl/writer/ast_raise/remove_continue_in_switch.h
@@ -33,7 +33,7 @@
     ~RemoveContinueInSwitch() override;
 
     /// @copydoc ast::transform::Transform::Apply
-    ApplyResult Apply(const Program* program,
+    ApplyResult Apply(const Program& program,
                       const ast::transform::DataMap& inputs,
                       ast::transform::DataMap& outputs) const override;
 
diff --git a/src/tint/lang/hlsl/writer/ast_raise/truncate_interstage_variables.cc b/src/tint/lang/hlsl/writer/ast_raise/truncate_interstage_variables.cc
index 240da0a..3a38837 100644
--- a/src/tint/lang/hlsl/writer/ast_raise/truncate_interstage_variables.cc
+++ b/src/tint/lang/hlsl/writer/ast_raise/truncate_interstage_variables.cc
@@ -49,11 +49,11 @@
 TruncateInterstageVariables::~TruncateInterstageVariables() = default;
 
 ast::transform::Transform::ApplyResult TruncateInterstageVariables::Apply(
-    const Program* src,
+    const Program& src,
     const ast::transform::DataMap& config,
     ast::transform::DataMap&) const {
     ProgramBuilder b;
-    program::CloneContext ctx{&b, src, /* auto_clone_symbols */ true};
+    program::CloneContext ctx{&b, &src, /* auto_clone_symbols */ true};
 
     const auto* data = config.Get<Config>();
     if (data == nullptr) {
diff --git a/src/tint/lang/hlsl/writer/ast_raise/truncate_interstage_variables.h b/src/tint/lang/hlsl/writer/ast_raise/truncate_interstage_variables.h
index 8fe3250..d98deac 100644
--- a/src/tint/lang/hlsl/writer/ast_raise/truncate_interstage_variables.h
+++ b/src/tint/lang/hlsl/writer/ast_raise/truncate_interstage_variables.h
@@ -121,7 +121,7 @@
     ~TruncateInterstageVariables() override;
 
     /// @copydoc ast::transform::Transform::Apply
-    ApplyResult Apply(const Program* program,
+    ApplyResult Apply(const Program& program,
                       const ast::transform::DataMap& inputs,
                       ast::transform::DataMap& outputs) const override;
 };
diff --git a/src/tint/lang/hlsl/writer/writer.cc b/src/tint/lang/hlsl/writer/writer.cc
index fa88e21..67493be 100644
--- a/src/tint/lang/hlsl/writer/writer.cc
+++ b/src/tint/lang/hlsl/writer/writer.cc
@@ -21,8 +21,8 @@
 
 namespace tint::hlsl::writer {
 
-Result<Output, std::string> Generate(const Program* program, const Options& options) {
-    if (!program->IsValid()) {
+Result<Output, std::string> Generate(const Program& program, const Options& options) {
+    if (!program.IsValid()) {
         return std::string("input program is not valid");
     }
 
@@ -33,7 +33,7 @@
     }
 
     // Generate the HLSL code.
-    auto impl = std::make_unique<ASTPrinter>(&sanitized_result.program);
+    auto impl = std::make_unique<ASTPrinter>(sanitized_result.program);
     if (!impl->Generate()) {
         return impl->Diagnostics().str();
     }
diff --git a/src/tint/lang/hlsl/writer/writer.h b/src/tint/lang/hlsl/writer/writer.h
index 2d28d7f..68b5c3c 100644
--- a/src/tint/lang/hlsl/writer/writer.h
+++ b/src/tint/lang/hlsl/writer/writer.h
@@ -33,7 +33,7 @@
 /// @param program the program to translate to HLSL
 /// @param options the configuration options to use when generating HLSL
 /// @returns the resulting HLSL and supplementary information, or an error string
-Result<Output, std::string> Generate(const Program* program, const Options& options);
+Result<Output, std::string> Generate(const Program& program, const Options& options);
 
 }  // namespace tint::hlsl::writer
 
diff --git a/src/tint/lang/hlsl/writer/writer_bench.cc b/src/tint/lang/hlsl/writer/writer_bench.cc
index 16337e1..6250e0f 100644
--- a/src/tint/lang/hlsl/writer/writer_bench.cc
+++ b/src/tint/lang/hlsl/writer/writer_bench.cc
@@ -28,7 +28,7 @@
     }
     auto& program = std::get<bench::ProgramAndFile>(res).program;
     for (auto _ : state) {
-        auto res = Generate(&program, {});
+        auto res = Generate(program, {});
         if (!res) {
             state.SkipWithError(res.Failure().c_str());
         }
diff --git a/src/tint/lang/msl/writer/ast_printer/ast_printer.cc b/src/tint/lang/msl/writer/ast_printer/ast_printer.cc
index e2fd37e..2db793f 100644
--- a/src/tint/lang/msl/writer/ast_printer/ast_printer.cc
+++ b/src/tint/lang/msl/writer/ast_printer/ast_printer.cc
@@ -132,7 +132,7 @@
 SanitizedResult::~SanitizedResult() = default;
 SanitizedResult::SanitizedResult(SanitizedResult&&) = default;
 
-SanitizedResult Sanitize(const Program* in, const Options& options) {
+SanitizedResult Sanitize(const Program& in, const Options& options) {
     ast::transform::Manager manager;
     ast::transform::DataMap data;
 
@@ -249,7 +249,7 @@
     return result;
 }
 
-ASTPrinter::ASTPrinter(const Program* program) : builder_(ProgramBuilder::Wrap(program)) {}
+ASTPrinter::ASTPrinter(const Program& program) : builder_(ProgramBuilder::Wrap(program)) {}
 
 ASTPrinter::~ASTPrinter() = default;
 
@@ -630,7 +630,7 @@
     auto* target = call->Target();
     return Switch(
         target, [&](const sem::Function* func) { return EmitFunctionCall(out, call, func); },
-        [&](const sem::Builtin* builtin) { return EmitBuiltinCall(out, call, builtin); },
+        [&](const sem::BuiltinFn* builtin) { return EmitBuiltinCall(out, call, builtin); },
         [&](const sem::ValueConversion* conv) { return EmitTypeConversion(out, call, conv); },
         [&](const sem::ValueConstructor* ctor) { return EmitTypeInitializer(out, call, ctor); },
         [&](Default) {
@@ -668,7 +668,7 @@
 
 bool ASTPrinter::EmitBuiltinCall(StringStream& out,
                                  const sem::Call* call,
-                                 const sem::Builtin* builtin) {
+                                 const sem::BuiltinFn* builtin) {
     auto* expr = call->Declaration();
     if (builtin->IsAtomic()) {
         return EmitAtomicCall(out, expr, builtin);
@@ -679,25 +679,25 @@
 
     auto name = generate_builtin_name(builtin);
 
-    switch (builtin->Type()) {
-        case core::Function::kDot:
+    switch (builtin->Fn()) {
+        case core::BuiltinFn::kDot:
             return EmitDotCall(out, expr, builtin);
-        case core::Function::kModf:
+        case core::BuiltinFn::kModf:
             return EmitModfCall(out, expr, builtin);
-        case core::Function::kFrexp:
+        case core::BuiltinFn::kFrexp:
             return EmitFrexpCall(out, expr, builtin);
-        case core::Function::kDegrees:
+        case core::BuiltinFn::kDegrees:
             return EmitDegreesCall(out, expr, builtin);
-        case core::Function::kRadians:
+        case core::BuiltinFn::kRadians:
             return EmitRadiansCall(out, expr, builtin);
-        case core::Function::kDot4I8Packed:
+        case core::BuiltinFn::kDot4I8Packed:
             return EmitDot4I8PackedCall(out, expr, builtin);
-        case core::Function::kDot4U8Packed:
+        case core::BuiltinFn::kDot4U8Packed:
             return EmitDot4U8PackedCall(out, expr, builtin);
 
-        case core::Function::kPack2X16Float:
-        case core::Function::kUnpack2X16Float: {
-            if (builtin->Type() == core::Function::kPack2X16Float) {
+        case core::BuiltinFn::kPack2X16Float:
+        case core::BuiltinFn::kUnpack2X16Float: {
+            if (builtin->Fn() == core::BuiltinFn::kPack2X16Float) {
                 out << "as_type<uint>(half2(";
             } else {
                 out << "float2(as_type<half2>(";
@@ -708,7 +708,7 @@
             out << "))";
             return true;
         }
-        case core::Function::kQuantizeToF16: {
+        case core::BuiltinFn::kQuantizeToF16: {
             std::string width = "";
             if (auto* vec = builtin->ReturnType()->As<core::type::Vector>()) {
                 width = std::to_string(vec->Width());
@@ -722,20 +722,20 @@
         }
         // TODO(crbug.com/tint/661): Combine sequential barriers to a single
         // instruction.
-        case core::Function::kStorageBarrier: {
+        case core::BuiltinFn::kStorageBarrier: {
             out << "threadgroup_barrier(mem_flags::mem_device)";
             return true;
         }
-        case core::Function::kWorkgroupBarrier: {
+        case core::BuiltinFn::kWorkgroupBarrier: {
             out << "threadgroup_barrier(mem_flags::mem_threadgroup)";
             return true;
         }
-        case core::Function::kTextureBarrier: {
+        case core::BuiltinFn::kTextureBarrier: {
             out << "threadgroup_barrier(mem_flags::mem_texture)";
             return true;
         }
 
-        case core::Function::kLength: {
+        case core::BuiltinFn::kLength: {
             auto* sem = builder_.Sem().GetVal(expr->args[0]);
             if (sem->Type()->UnwrapRef()->Is<core::type::Scalar>()) {
                 // Emulate scalar overload using fabs(x).
@@ -744,7 +744,7 @@
             break;
         }
 
-        case core::Function::kDistance: {
+        case core::BuiltinFn::kDistance: {
             auto* sem = builder_.Sem().GetVal(expr->args[0]);
             if (sem->Type()->UnwrapRef()->Is<core::type::Scalar>()) {
                 // Emulate scalar overload using fabs(x - y);
@@ -762,7 +762,7 @@
             break;
         }
 
-        case core::Function::kSubgroupBroadcast: {
+        case core::BuiltinFn::kSubgroupBroadcast: {
             // The lane argument is ushort.
             out << "simd_broadcast(";
             if (!EmitExpression(out, expr->args[0])) {
@@ -876,7 +876,7 @@
 
 bool ASTPrinter::EmitAtomicCall(StringStream& out,
                                 const ast::CallExpression* expr,
-                                const sem::Builtin* builtin) {
+                                const sem::BuiltinFn* builtin) {
     auto call = [&](const std::string& name, bool append_memory_order_relaxed) {
         out << name;
         {
@@ -897,38 +897,38 @@
         return true;
     };
 
-    switch (builtin->Type()) {
-        case core::Function::kAtomicLoad:
+    switch (builtin->Fn()) {
+        case core::BuiltinFn::kAtomicLoad:
             return call("atomic_load_explicit", true);
 
-        case core::Function::kAtomicStore:
+        case core::BuiltinFn::kAtomicStore:
             return call("atomic_store_explicit", true);
 
-        case core::Function::kAtomicAdd:
+        case core::BuiltinFn::kAtomicAdd:
             return call("atomic_fetch_add_explicit", true);
 
-        case core::Function::kAtomicSub:
+        case core::BuiltinFn::kAtomicSub:
             return call("atomic_fetch_sub_explicit", true);
 
-        case core::Function::kAtomicMax:
+        case core::BuiltinFn::kAtomicMax:
             return call("atomic_fetch_max_explicit", true);
 
-        case core::Function::kAtomicMin:
+        case core::BuiltinFn::kAtomicMin:
             return call("atomic_fetch_min_explicit", true);
 
-        case core::Function::kAtomicAnd:
+        case core::BuiltinFn::kAtomicAnd:
             return call("atomic_fetch_and_explicit", true);
 
-        case core::Function::kAtomicOr:
+        case core::BuiltinFn::kAtomicOr:
             return call("atomic_fetch_or_explicit", true);
 
-        case core::Function::kAtomicXor:
+        case core::BuiltinFn::kAtomicXor:
             return call("atomic_fetch_xor_explicit", true);
 
-        case core::Function::kAtomicExchange:
+        case core::BuiltinFn::kAtomicExchange:
             return call("atomic_exchange_explicit", true);
 
-        case core::Function::kAtomicCompareExchangeWeak: {
+        case core::BuiltinFn::kAtomicCompareExchangeWeak: {
             auto* ptr_ty = TypeOf(expr->args[0])->UnwrapRef()->As<core::type::Pointer>();
             auto sc = ptr_ty->AddressSpace();
             auto* str = builtin->ReturnType()->As<core::type::Struct>();
@@ -995,13 +995,13 @@
             break;
     }
 
-    TINT_UNREACHABLE() << "unsupported atomic builtin: " << builtin->Type();
+    TINT_UNREACHABLE() << "unsupported atomic builtin: " << builtin->Fn();
     return false;
 }
 
 bool ASTPrinter::EmitTextureCall(StringStream& out,
                                  const sem::Call* call,
-                                 const sem::Builtin* builtin) {
+                                 const sem::BuiltinFn* builtin) {
     using Usage = core::ParameterUsage;
 
     auto& signature = builtin->Signature();
@@ -1043,8 +1043,8 @@
     // MSL requires that `lod` is a constant 0 for 1D textures.
     bool level_is_constant_zero = texture_type->dim() == core::type::TextureDimension::k1d;
 
-    switch (builtin->Type()) {
-        case core::Function::kTextureDimensions: {
+    switch (builtin->Fn()) {
+        case core::BuiltinFn::kTextureDimensions: {
             std::vector<const char*> dims;
             switch (texture_type->dim()) {
                 case core::type::TextureDimension::kNone:
@@ -1097,21 +1097,21 @@
             }
             return true;
         }
-        case core::Function::kTextureNumLayers: {
+        case core::BuiltinFn::kTextureNumLayers: {
             if (!texture_expr()) {
                 return false;
             }
             out << ".get_array_size()";
             return true;
         }
-        case core::Function::kTextureNumLevels: {
+        case core::BuiltinFn::kTextureNumLevels: {
             if (!texture_expr()) {
                 return false;
             }
             out << ".get_num_mip_levels()";
             return true;
         }
-        case core::Function::kTextureNumSamples: {
+        case core::BuiltinFn::kTextureNumSamples: {
             if (!texture_expr()) {
                 return false;
             }
@@ -1128,28 +1128,28 @@
 
     bool lod_param_is_named = true;
 
-    switch (builtin->Type()) {
-        case core::Function::kTextureSample:
-        case core::Function::kTextureSampleBias:
-        case core::Function::kTextureSampleLevel:
-        case core::Function::kTextureSampleGrad:
+    switch (builtin->Fn()) {
+        case core::BuiltinFn::kTextureSample:
+        case core::BuiltinFn::kTextureSampleBias:
+        case core::BuiltinFn::kTextureSampleLevel:
+        case core::BuiltinFn::kTextureSampleGrad:
             out << ".sample(";
             break;
-        case core::Function::kTextureSampleCompare:
-        case core::Function::kTextureSampleCompareLevel:
+        case core::BuiltinFn::kTextureSampleCompare:
+        case core::BuiltinFn::kTextureSampleCompareLevel:
             out << ".sample_compare(";
             break;
-        case core::Function::kTextureGather:
+        case core::BuiltinFn::kTextureGather:
             out << ".gather(";
             break;
-        case core::Function::kTextureGatherCompare:
+        case core::BuiltinFn::kTextureGatherCompare:
             out << ".gather_compare(";
             break;
-        case core::Function::kTextureLoad:
+        case core::BuiltinFn::kTextureLoad:
             out << ".read(";
             lod_param_is_named = false;
             break;
-        case core::Function::kTextureStore:
+        case core::BuiltinFn::kTextureStore:
             out << ".write(";
             break;
         default:
@@ -1225,7 +1225,7 @@
             out << ")";
         }
     }
-    if (builtin->Type() == core::Function::kTextureSampleCompareLevel) {
+    if (builtin->Fn() == core::BuiltinFn::kTextureSampleCompareLevel) {
         maybe_write_comma();
         out << "level(0)";
     }
@@ -1310,7 +1310,7 @@
     // If this is a `textureStore()` for a read-write texture, add a fence to ensure that the
     // written values are visible to subsequent reads from the same thread.
     if (auto* storage = texture_type->As<core::type::StorageTexture>();
-        builtin->Type() == core::Function::kTextureStore &&
+        builtin->Fn() == core::BuiltinFn::kTextureStore &&
         storage->access() == core::Access::kReadWrite) {
         out << "; ";
         texture_expr();
@@ -1322,7 +1322,7 @@
 
 bool ASTPrinter::EmitDotCall(StringStream& out,
                              const ast::CallExpression* expr,
-                             const sem::Builtin* builtin) {
+                             const sem::BuiltinFn* builtin) {
     auto* vec_ty = builtin->Parameters()[0]->Type()->As<core::type::Vector>();
     std::string fn = "dot";
     if (vec_ty->type()->is_integer_scalar()) {
@@ -1367,7 +1367,7 @@
 
 bool ASTPrinter::EmitDot4I8PackedCall(StringStream& out,
                                       const ast::CallExpression* expr,
-                                      const sem::Builtin* builtin) {
+                                      const sem::BuiltinFn* builtin) {
     return CallBuiltinHelper(
         out, expr, builtin, [&](TextBuffer* b, const std::vector<std::string>& params) {
             Line(b) << "packed_char4 vec1 = as_type<packed_char4>(" << params[0] << ");";
@@ -1380,7 +1380,7 @@
 
 bool ASTPrinter::EmitDot4U8PackedCall(StringStream& out,
                                       const ast::CallExpression* expr,
-                                      const sem::Builtin* builtin) {
+                                      const sem::BuiltinFn* builtin) {
     return CallBuiltinHelper(
         out, expr, builtin, [&](TextBuffer* b, const std::vector<std::string>& params) {
             Line(b) << "packed_uchar4 vec1 = as_type<packed_uchar4>(" << params[0] << ");";
@@ -1393,7 +1393,7 @@
 
 bool ASTPrinter::EmitModfCall(StringStream& out,
                               const ast::CallExpression* expr,
-                              const sem::Builtin* builtin) {
+                              const sem::BuiltinFn* builtin) {
     return CallBuiltinHelper(
         out, expr, builtin, [&](TextBuffer* b, const std::vector<std::string>& params) {
             auto* ty = builtin->Parameters()[0]->Type();
@@ -1419,7 +1419,7 @@
 
 bool ASTPrinter::EmitFrexpCall(StringStream& out,
                                const ast::CallExpression* expr,
-                               const sem::Builtin* builtin) {
+                               const sem::BuiltinFn* builtin) {
     return CallBuiltinHelper(
         out, expr, builtin, [&](TextBuffer* b, const std::vector<std::string>& params) {
             auto* ty = builtin->Parameters()[0]->Type();
@@ -1445,7 +1445,7 @@
 
 bool ASTPrinter::EmitDegreesCall(StringStream& out,
                                  const ast::CallExpression* expr,
-                                 const sem::Builtin* builtin) {
+                                 const sem::BuiltinFn* builtin) {
     return CallBuiltinHelper(out, expr, builtin,
                              [&](TextBuffer* b, const std::vector<std::string>& params) {
                                  Line(b) << "return " << params[0] << " * " << std::setprecision(20)
@@ -1456,7 +1456,7 @@
 
 bool ASTPrinter::EmitRadiansCall(StringStream& out,
                                  const ast::CallExpression* expr,
-                                 const sem::Builtin* builtin) {
+                                 const sem::BuiltinFn* builtin) {
     return CallBuiltinHelper(out, expr, builtin,
                              [&](TextBuffer* b, const std::vector<std::string>& params) {
                                  Line(b) << "return " << params[0] << " * " << std::setprecision(20)
@@ -1465,146 +1465,146 @@
                              });
 }
 
-std::string ASTPrinter::generate_builtin_name(const sem::Builtin* builtin) {
+std::string ASTPrinter::generate_builtin_name(const sem::BuiltinFn* builtin) {
     std::string out = "";
-    switch (builtin->Type()) {
-        case core::Function::kAcos:
-        case core::Function::kAcosh:
-        case core::Function::kAll:
-        case core::Function::kAny:
-        case core::Function::kAsin:
-        case core::Function::kAsinh:
-        case core::Function::kAtanh:
-        case core::Function::kAtan:
-        case core::Function::kAtan2:
-        case core::Function::kCeil:
-        case core::Function::kCos:
-        case core::Function::kCosh:
-        case core::Function::kCross:
-        case core::Function::kDeterminant:
-        case core::Function::kDistance:
-        case core::Function::kDot:
-        case core::Function::kExp:
-        case core::Function::kExp2:
-        case core::Function::kFloor:
-        case core::Function::kFma:
-        case core::Function::kFract:
-        case core::Function::kFrexp:
-        case core::Function::kLength:
-        case core::Function::kLdexp:
-        case core::Function::kLog:
-        case core::Function::kLog2:
-        case core::Function::kMix:
-        case core::Function::kModf:
-        case core::Function::kNormalize:
-        case core::Function::kPow:
-        case core::Function::kReflect:
-        case core::Function::kRefract:
-        case core::Function::kSaturate:
-        case core::Function::kSelect:
-        case core::Function::kSin:
-        case core::Function::kSinh:
-        case core::Function::kSqrt:
-        case core::Function::kStep:
-        case core::Function::kTan:
-        case core::Function::kTanh:
-        case core::Function::kTranspose:
-        case core::Function::kTrunc:
-        case core::Function::kSign:
-        case core::Function::kClamp:
+    switch (builtin->Fn()) {
+        case core::BuiltinFn::kAcos:
+        case core::BuiltinFn::kAcosh:
+        case core::BuiltinFn::kAll:
+        case core::BuiltinFn::kAny:
+        case core::BuiltinFn::kAsin:
+        case core::BuiltinFn::kAsinh:
+        case core::BuiltinFn::kAtanh:
+        case core::BuiltinFn::kAtan:
+        case core::BuiltinFn::kAtan2:
+        case core::BuiltinFn::kCeil:
+        case core::BuiltinFn::kCos:
+        case core::BuiltinFn::kCosh:
+        case core::BuiltinFn::kCross:
+        case core::BuiltinFn::kDeterminant:
+        case core::BuiltinFn::kDistance:
+        case core::BuiltinFn::kDot:
+        case core::BuiltinFn::kExp:
+        case core::BuiltinFn::kExp2:
+        case core::BuiltinFn::kFloor:
+        case core::BuiltinFn::kFma:
+        case core::BuiltinFn::kFract:
+        case core::BuiltinFn::kFrexp:
+        case core::BuiltinFn::kLength:
+        case core::BuiltinFn::kLdexp:
+        case core::BuiltinFn::kLog:
+        case core::BuiltinFn::kLog2:
+        case core::BuiltinFn::kMix:
+        case core::BuiltinFn::kModf:
+        case core::BuiltinFn::kNormalize:
+        case core::BuiltinFn::kPow:
+        case core::BuiltinFn::kReflect:
+        case core::BuiltinFn::kRefract:
+        case core::BuiltinFn::kSaturate:
+        case core::BuiltinFn::kSelect:
+        case core::BuiltinFn::kSin:
+        case core::BuiltinFn::kSinh:
+        case core::BuiltinFn::kSqrt:
+        case core::BuiltinFn::kStep:
+        case core::BuiltinFn::kTan:
+        case core::BuiltinFn::kTanh:
+        case core::BuiltinFn::kTranspose:
+        case core::BuiltinFn::kTrunc:
+        case core::BuiltinFn::kSign:
+        case core::BuiltinFn::kClamp:
             out += builtin->str();
             break;
-        case core::Function::kAbs:
+        case core::BuiltinFn::kAbs:
             if (builtin->ReturnType()->is_float_scalar_or_vector()) {
                 out += "fabs";
             } else {
                 out += "abs";
             }
             break;
-        case core::Function::kCountLeadingZeros:
+        case core::BuiltinFn::kCountLeadingZeros:
             out += "clz";
             break;
-        case core::Function::kCountOneBits:
+        case core::BuiltinFn::kCountOneBits:
             out += "popcount";
             break;
-        case core::Function::kCountTrailingZeros:
+        case core::BuiltinFn::kCountTrailingZeros:
             out += "ctz";
             break;
-        case core::Function::kDpdx:
-        case core::Function::kDpdxCoarse:
-        case core::Function::kDpdxFine:
+        case core::BuiltinFn::kDpdx:
+        case core::BuiltinFn::kDpdxCoarse:
+        case core::BuiltinFn::kDpdxFine:
             out += "dfdx";
             break;
-        case core::Function::kDpdy:
-        case core::Function::kDpdyCoarse:
-        case core::Function::kDpdyFine:
+        case core::BuiltinFn::kDpdy:
+        case core::BuiltinFn::kDpdyCoarse:
+        case core::BuiltinFn::kDpdyFine:
             out += "dfdy";
             break;
-        case core::Function::kExtractBits:
+        case core::BuiltinFn::kExtractBits:
             out += "extract_bits";
             break;
-        case core::Function::kInsertBits:
+        case core::BuiltinFn::kInsertBits:
             out += "insert_bits";
             break;
-        case core::Function::kFwidth:
-        case core::Function::kFwidthCoarse:
-        case core::Function::kFwidthFine:
+        case core::BuiltinFn::kFwidth:
+        case core::BuiltinFn::kFwidthCoarse:
+        case core::BuiltinFn::kFwidthFine:
             out += "fwidth";
             break;
-        case core::Function::kMax:
+        case core::BuiltinFn::kMax:
             if (builtin->ReturnType()->is_float_scalar_or_vector()) {
                 out += "fmax";
             } else {
                 out += "max";
             }
             break;
-        case core::Function::kMin:
+        case core::BuiltinFn::kMin:
             if (builtin->ReturnType()->is_float_scalar_or_vector()) {
                 out += "fmin";
             } else {
                 out += "min";
             }
             break;
-        case core::Function::kFaceForward:
+        case core::BuiltinFn::kFaceForward:
             out += "faceforward";
             break;
-        case core::Function::kPack4X8Snorm:
+        case core::BuiltinFn::kPack4X8Snorm:
             out += "pack_float_to_snorm4x8";
             break;
-        case core::Function::kPack4X8Unorm:
+        case core::BuiltinFn::kPack4X8Unorm:
             out += "pack_float_to_unorm4x8";
             break;
-        case core::Function::kPack2X16Snorm:
+        case core::BuiltinFn::kPack2X16Snorm:
             out += "pack_float_to_snorm2x16";
             break;
-        case core::Function::kPack2X16Unorm:
+        case core::BuiltinFn::kPack2X16Unorm:
             out += "pack_float_to_unorm2x16";
             break;
-        case core::Function::kReverseBits:
+        case core::BuiltinFn::kReverseBits:
             out += "reverse_bits";
             break;
-        case core::Function::kRound:
+        case core::BuiltinFn::kRound:
             out += "rint";
             break;
-        case core::Function::kSmoothstep:
+        case core::BuiltinFn::kSmoothstep:
             out += "smoothstep";
             break;
-        case core::Function::kInverseSqrt:
+        case core::BuiltinFn::kInverseSqrt:
             out += "rsqrt";
             break;
-        case core::Function::kUnpack4X8Snorm:
+        case core::BuiltinFn::kUnpack4X8Snorm:
             out += "unpack_snorm4x8_to_float";
             break;
-        case core::Function::kUnpack4X8Unorm:
+        case core::BuiltinFn::kUnpack4X8Unorm:
             out += "unpack_unorm4x8_to_float";
             break;
-        case core::Function::kUnpack2X16Snorm:
+        case core::BuiltinFn::kUnpack2X16Snorm:
             out += "unpack_snorm2x16_to_float";
             break;
-        case core::Function::kUnpack2X16Unorm:
+        case core::BuiltinFn::kUnpack2X16Unorm:
             out += "unpack_unorm2x16_to_float";
             break;
-        case core::Function::kArrayLength:
+        case core::BuiltinFn::kArrayLength:
             diagnostics_.add_error(
                 diag::System::Writer,
                 "Unable to translate builtin: " + std::string(builtin->str()) +
@@ -3063,14 +3063,14 @@
 template <typename F>
 bool ASTPrinter::CallBuiltinHelper(StringStream& out,
                                    const ast::CallExpression* call,
-                                   const sem::Builtin* builtin,
+                                   const sem::BuiltinFn* builtin,
                                    F&& build) {
     // Generate the helper function if it hasn't been created already
     auto fn = tint::GetOrCreate(builtins_, builtin, [&]() -> std::string {
         TextBuffer b;
         TINT_DEFER(helpers_.Append(b));
 
-        auto fn_name = UniqueIdentifier(std::string("tint_") + core::str(builtin->Type()));
+        auto fn_name = UniqueIdentifier(std::string("tint_") + core::str(builtin->Fn()));
         std::vector<std::string> parameter_names;
         {
             auto decl = Line(&b);
diff --git a/src/tint/lang/msl/writer/ast_printer/ast_printer.h b/src/tint/lang/msl/writer/ast_printer/ast_printer.h
index 47f1ef7..43c36db 100644
--- a/src/tint/lang/msl/writer/ast_printer/ast_printer.h
+++ b/src/tint/lang/msl/writer/ast_printer/ast_printer.h
@@ -47,7 +47,7 @@
 #include "src/tint/utils/text/string_stream.h"
 
 namespace tint::sem {
-class Builtin;
+class BuiltinFn;
 class Call;
 class ValueConstructor;
 class ValueConversion;
@@ -77,14 +77,14 @@
 /// @program The program to sanitize
 /// @param options The MSL generator options.
 /// @returns the sanitized program and any supplementary information
-SanitizedResult Sanitize(const Program* program, const Options& options);
+SanitizedResult Sanitize(const Program& program, const Options& options);
 
 /// Implementation class for MSL generator
 class ASTPrinter : public tint::TextGenerator {
   public:
     /// Constructor
     /// @param program the program to generate
-    explicit ASTPrinter(const Program* program);
+    explicit ASTPrinter(const Program& program);
     ~ASTPrinter() override;
 
     /// @returns true on successful generation; false otherwise
@@ -144,7 +144,7 @@
     /// @param call the call expression
     /// @param builtin the builtin being called
     /// @returns true if the call expression is emitted
-    bool EmitBuiltinCall(StringStream& out, const sem::Call* call, const sem::Builtin* builtin);
+    bool EmitBuiltinCall(StringStream& out, const sem::Call* call, const sem::BuiltinFn* builtin);
     /// Handles generating a value conversion expression
     /// @param out the output of the expression stream
     /// @param call the call expression
@@ -175,14 +175,14 @@
     /// @returns true if the call expression is emitted
     bool EmitAtomicCall(StringStream& out,
                         const ast::CallExpression* expr,
-                        const sem::Builtin* builtin);
+                        const sem::BuiltinFn* builtin);
     /// Handles generating a call to a texture function (`textureSample`,
     /// `textureSampleGrad`, etc)
     /// @param out the output of the expression stream
     /// @param call the call expression
     /// @param builtin the semantic information for the texture builtin
     /// @returns true if the call expression is emitted
-    bool EmitTextureCall(StringStream& out, const sem::Call* call, const sem::Builtin* builtin);
+    bool EmitTextureCall(StringStream& out, const sem::Call* call, const sem::BuiltinFn* builtin);
     /// Handles generating a call to the `dot()` builtin
     /// @param out the output of the expression stream
     /// @param expr the call expression
@@ -190,7 +190,7 @@
     /// @returns true if the call expression is emitted
     bool EmitDotCall(StringStream& out,
                      const ast::CallExpression* expr,
-                     const sem::Builtin* builtin);
+                     const sem::BuiltinFn* builtin);
     /// Handles generating a call to the `modf()` builtin
     /// @param out the output of the expression stream
     /// @param expr the call expression
@@ -198,7 +198,7 @@
     /// @returns true if the call expression is emitted
     bool EmitModfCall(StringStream& out,
                       const ast::CallExpression* expr,
-                      const sem::Builtin* builtin);
+                      const sem::BuiltinFn* builtin);
     /// Handles generating a call to the `frexp()` builtin
     /// @param out the output of the expression stream
     /// @param expr the call expression
@@ -206,7 +206,7 @@
     /// @returns true if the call expression is emitted
     bool EmitFrexpCall(StringStream& out,
                        const ast::CallExpression* expr,
-                       const sem::Builtin* builtin);
+                       const sem::BuiltinFn* builtin);
     /// Handles generating a call to the `degrees()` builtin
     /// @param out the output of the expression stream
     /// @param expr the call expression
@@ -214,7 +214,7 @@
     /// @returns true if the call expression is emitted
     bool EmitDegreesCall(StringStream& out,
                          const ast::CallExpression* expr,
-                         const sem::Builtin* builtin);
+                         const sem::BuiltinFn* builtin);
     /// Handles generating a call to the `radians()` builtin
     /// @param out the output of the expression stream
     /// @param expr the call expression
@@ -222,7 +222,7 @@
     /// @returns true if the call expression is emitted
     bool EmitRadiansCall(StringStream& out,
                          const ast::CallExpression* expr,
-                         const sem::Builtin* builtin);
+                         const sem::BuiltinFn* builtin);
     /// Handles a case statement
     /// @param stmt the statement
     /// @returns true if the statement was emitted successfully
@@ -355,7 +355,7 @@
     /// @returns true if the call expression is emitted
     bool EmitDot4I8PackedCall(StringStream& out,
                               const ast::CallExpression* expr,
-                              const sem::Builtin* builtin);
+                              const sem::BuiltinFn* builtin);
     /// Handles generating a call to the `dot4U8Packed()` builtin
     /// @param out the output of the expression stream
     /// @param expr the call expression
@@ -363,12 +363,12 @@
     /// @returns true if the call expression is emitted
     bool EmitDot4U8PackedCall(StringStream& out,
                               const ast::CallExpression* expr,
-                              const sem::Builtin* builtin);
+                              const sem::BuiltinFn* builtin);
 
     /// Handles generating a builtin name
     /// @param builtin the semantic info for the builtin
     /// @returns the name or "" if not valid
-    std::string generate_builtin_name(const sem::Builtin* builtin);
+    std::string generate_builtin_name(const sem::BuiltinFn* builtin);
 
   private:
     /// CallBuiltinHelper will call the builtin helper function, creating it
@@ -387,7 +387,7 @@
     template <typename F>
     bool CallBuiltinHelper(StringStream& out,
                            const ast::CallExpression* call,
-                           const sem::Builtin* builtin,
+                           const sem::BuiltinFn* builtin,
                            F&& build);
 
     /// @returns the name of the templated tint_array helper type, generating it if this is the
@@ -442,7 +442,7 @@
     /// should be created for that index.
     std::unordered_map<std::string, std::vector<uint32_t>> workgroup_allocations_;
 
-    std::unordered_map<const sem::Builtin*, std::string> builtins_;
+    std::unordered_map<const sem::BuiltinFn*, std::string> builtins_;
     std::unordered_map<const core::type::Type*, std::string> unary_minus_funcs_;
     std::unordered_map<uint32_t, std::string> int_dot_funcs_;
     std::unordered_set<const core::type::Struct*> emitted_structs_;
diff --git a/src/tint/lang/msl/writer/ast_printer/ast_printer_test.cc b/src/tint/lang/msl/writer/ast_printer/ast_printer_test.cc
index a6d092f..7087797 100644
--- a/src/tint/lang/msl/writer/ast_printer/ast_printer_test.cc
+++ b/src/tint/lang/msl/writer/ast_printer/ast_printer_test.cc
@@ -28,9 +28,9 @@
 TEST_F(MslASTPrinterTest, InvalidProgram) {
     Diagnostics().add_error(diag::System::Writer, "make the program invalid");
     ASSERT_FALSE(IsValid());
-    auto program = std::make_unique<Program>(resolver::Resolve(*this));
-    ASSERT_FALSE(program->IsValid());
-    auto result = Generate(program.get(), Options{});
+    auto program = resolver::Resolve(*this);
+    ASSERT_FALSE(program.IsValid());
+    auto result = Generate(program, Options{});
     EXPECT_FALSE(result);
     EXPECT_EQ(result.Failure(), "input program is not valid");
 }
diff --git a/src/tint/lang/msl/writer/ast_printer/builtin_test.cc b/src/tint/lang/msl/writer/ast_printer/builtin_test.cc
index f596fd5..a137075 100644
--- a/src/tint/lang/msl/writer/ast_printer/builtin_test.cc
+++ b/src/tint/lang/msl/writer/ast_printer/builtin_test.cc
@@ -33,7 +33,7 @@
 };
 
 struct BuiltinData {
-    core::Function builtin;
+    core::BuiltinFn builtin;
     CallParamType type;
     const char* msl_name;
 };
@@ -57,88 +57,88 @@
     return out;
 }
 
-const ast::CallExpression* GenerateCall(core::Function builtin,
+const ast::CallExpression* GenerateCall(core::BuiltinFn builtin,
                                         CallParamType type,
                                         ProgramBuilder* builder) {
     std::string name;
     StringStream str;
     str << name << builtin;
     switch (builtin) {
-        case core::Function::kAcos:
-        case core::Function::kAsin:
-        case core::Function::kAtan:
-        case core::Function::kCeil:
-        case core::Function::kCos:
-        case core::Function::kCosh:
-        case core::Function::kDpdx:
-        case core::Function::kDpdxCoarse:
-        case core::Function::kDpdxFine:
-        case core::Function::kDpdy:
-        case core::Function::kDpdyCoarse:
-        case core::Function::kDpdyFine:
-        case core::Function::kExp:
-        case core::Function::kExp2:
-        case core::Function::kFloor:
-        case core::Function::kFract:
-        case core::Function::kFwidth:
-        case core::Function::kFwidthCoarse:
-        case core::Function::kFwidthFine:
-        case core::Function::kInverseSqrt:
-        case core::Function::kLength:
-        case core::Function::kLog:
-        case core::Function::kLog2:
-        case core::Function::kNormalize:
-        case core::Function::kRound:
-        case core::Function::kSin:
-        case core::Function::kSinh:
-        case core::Function::kSqrt:
-        case core::Function::kTan:
-        case core::Function::kTanh:
-        case core::Function::kTrunc:
-        case core::Function::kSign:
+        case core::BuiltinFn::kAcos:
+        case core::BuiltinFn::kAsin:
+        case core::BuiltinFn::kAtan:
+        case core::BuiltinFn::kCeil:
+        case core::BuiltinFn::kCos:
+        case core::BuiltinFn::kCosh:
+        case core::BuiltinFn::kDpdx:
+        case core::BuiltinFn::kDpdxCoarse:
+        case core::BuiltinFn::kDpdxFine:
+        case core::BuiltinFn::kDpdy:
+        case core::BuiltinFn::kDpdyCoarse:
+        case core::BuiltinFn::kDpdyFine:
+        case core::BuiltinFn::kExp:
+        case core::BuiltinFn::kExp2:
+        case core::BuiltinFn::kFloor:
+        case core::BuiltinFn::kFract:
+        case core::BuiltinFn::kFwidth:
+        case core::BuiltinFn::kFwidthCoarse:
+        case core::BuiltinFn::kFwidthFine:
+        case core::BuiltinFn::kInverseSqrt:
+        case core::BuiltinFn::kLength:
+        case core::BuiltinFn::kLog:
+        case core::BuiltinFn::kLog2:
+        case core::BuiltinFn::kNormalize:
+        case core::BuiltinFn::kRound:
+        case core::BuiltinFn::kSin:
+        case core::BuiltinFn::kSinh:
+        case core::BuiltinFn::kSqrt:
+        case core::BuiltinFn::kTan:
+        case core::BuiltinFn::kTanh:
+        case core::BuiltinFn::kTrunc:
+        case core::BuiltinFn::kSign:
             if (type == CallParamType::kF16) {
                 return builder->Call(str.str(), "h2");
             } else {
                 return builder->Call(str.str(), "f2");
             }
-        case core::Function::kLdexp:
+        case core::BuiltinFn::kLdexp:
             if (type == CallParamType::kF16) {
                 return builder->Call(str.str(), "h2", "i2");
             } else {
                 return builder->Call(str.str(), "f2", "i2");
             }
-        case core::Function::kAtan2:
-        case core::Function::kDot:
-        case core::Function::kDistance:
-        case core::Function::kPow:
-        case core::Function::kReflect:
-        case core::Function::kStep:
+        case core::BuiltinFn::kAtan2:
+        case core::BuiltinFn::kDot:
+        case core::BuiltinFn::kDistance:
+        case core::BuiltinFn::kPow:
+        case core::BuiltinFn::kReflect:
+        case core::BuiltinFn::kStep:
             if (type == CallParamType::kF16) {
                 return builder->Call(str.str(), "h2", "h2");
             } else {
                 return builder->Call(str.str(), "f2", "f2");
             }
-        case core::Function::kStorageBarrier:
+        case core::BuiltinFn::kStorageBarrier:
             return builder->Call(str.str());
-        case core::Function::kCross:
+        case core::BuiltinFn::kCross:
             if (type == CallParamType::kF16) {
                 return builder->Call(str.str(), "h3", "h3");
             } else {
                 return builder->Call(str.str(), "f3", "f3");
             }
-        case core::Function::kFma:
-        case core::Function::kMix:
-        case core::Function::kFaceForward:
-        case core::Function::kSmoothstep:
+        case core::BuiltinFn::kFma:
+        case core::BuiltinFn::kMix:
+        case core::BuiltinFn::kFaceForward:
+        case core::BuiltinFn::kSmoothstep:
             if (type == CallParamType::kF16) {
                 return builder->Call(str.str(), "h2", "h2", "h2");
             } else {
                 return builder->Call(str.str(), "f2", "f2", "f2");
             }
-        case core::Function::kAll:
-        case core::Function::kAny:
+        case core::BuiltinFn::kAll:
+        case core::BuiltinFn::kAny:
             return builder->Call(str.str(), "b2");
-        case core::Function::kAbs:
+        case core::BuiltinFn::kAbs:
             if (type == CallParamType::kF32) {
                 return builder->Call(str.str(), "f2");
             } else if (type == CallParamType::kF16) {
@@ -146,17 +146,17 @@
             } else {
                 return builder->Call(str.str(), "u2");
             }
-        case core::Function::kCountLeadingZeros:
-        case core::Function::kCountOneBits:
-        case core::Function::kCountTrailingZeros:
-        case core::Function::kReverseBits:
+        case core::BuiltinFn::kCountLeadingZeros:
+        case core::BuiltinFn::kCountOneBits:
+        case core::BuiltinFn::kCountTrailingZeros:
+        case core::BuiltinFn::kReverseBits:
             return builder->Call(str.str(), "u2");
-        case core::Function::kExtractBits:
+        case core::BuiltinFn::kExtractBits:
             return builder->Call(str.str(), "u2", "u1", "u1");
-        case core::Function::kInsertBits:
+        case core::BuiltinFn::kInsertBits:
             return builder->Call(str.str(), "u2", "u2", "u1", "u1");
-        case core::Function::kMax:
-        case core::Function::kMin:
+        case core::BuiltinFn::kMax:
+        case core::BuiltinFn::kMin:
             if (type == CallParamType::kF32) {
                 return builder->Call(str.str(), "f2", "f2");
             } else if (type == CallParamType::kF16) {
@@ -164,7 +164,7 @@
             } else {
                 return builder->Call(str.str(), "u2", "u2");
             }
-        case core::Function::kClamp:
+        case core::BuiltinFn::kClamp:
             if (type == CallParamType::kF32) {
                 return builder->Call(str.str(), "f2", "f2", "f2");
             } else if (type == CallParamType::kF16) {
@@ -172,32 +172,32 @@
             } else {
                 return builder->Call(str.str(), "u2", "u2", "u2");
             }
-        case core::Function::kSelect:
+        case core::BuiltinFn::kSelect:
             if (type == CallParamType::kF16) {
                 return builder->Call(str.str(), "h2", "h2", "b2");
             } else {
                 return builder->Call(str.str(), "f2", "f2", "b2");
             }
-        case core::Function::kDeterminant:
+        case core::BuiltinFn::kDeterminant:
             if (type == CallParamType::kF16) {
                 return builder->Call(str.str(), "hm2x2");
             } else {
                 return builder->Call(str.str(), "m2x2");
             }
-        case core::Function::kPack2X16Snorm:
-        case core::Function::kPack2X16Unorm:
+        case core::BuiltinFn::kPack2X16Snorm:
+        case core::BuiltinFn::kPack2X16Unorm:
             return builder->Call(str.str(), "f2");
-        case core::Function::kPack4X8Snorm:
-        case core::Function::kPack4X8Unorm:
+        case core::BuiltinFn::kPack4X8Snorm:
+        case core::BuiltinFn::kPack4X8Unorm:
             return builder->Call(str.str(), "f4");
-        case core::Function::kUnpack4X8Snorm:
-        case core::Function::kUnpack4X8Unorm:
-        case core::Function::kUnpack2X16Snorm:
-        case core::Function::kUnpack2X16Unorm:
+        case core::BuiltinFn::kUnpack4X8Snorm:
+        case core::BuiltinFn::kUnpack4X8Unorm:
+        case core::BuiltinFn::kUnpack2X16Snorm:
+        case core::BuiltinFn::kUnpack2X16Unorm:
             return builder->Call(str.str(), "u1");
-        case core::Function::kWorkgroupBarrier:
+        case core::BuiltinFn::kWorkgroupBarrier:
             return builder->Call(str.str());
-        case core::Function::kTranspose:
+        case core::BuiltinFn::kTranspose:
             if (type == CallParamType::kF16) {
                 return builder->Call(str.str(), "hm3x2");
             } else {
@@ -243,7 +243,7 @@
     ASSERT_NE(sem, nullptr);
     auto* target = sem->Target();
     ASSERT_NE(target, nullptr);
-    auto* builtin = target->As<sem::Builtin>();
+    auto* builtin = target->As<sem::BuiltinFn>();
     ASSERT_NE(builtin, nullptr);
 
     EXPECT_EQ(gen.generate_builtin_name(builtin), param.msl_name);
@@ -253,122 +253,124 @@
     MslBuiltinTest,
     testing::Values(
         /* Logical built-in */
-        BuiltinData{core::Function::kAll, CallParamType::kBool, "all"},
-        BuiltinData{core::Function::kAny, CallParamType::kBool, "any"},
-        BuiltinData{core::Function::kSelect, CallParamType::kF32, "select"},
+        BuiltinData{core::BuiltinFn::kAll, CallParamType::kBool, "all"},
+        BuiltinData{core::BuiltinFn::kAny, CallParamType::kBool, "any"},
+        BuiltinData{core::BuiltinFn::kSelect, CallParamType::kF32, "select"},
         /* Float built-in */
-        BuiltinData{core::Function::kAbs, CallParamType::kF32, "fabs"},
-        BuiltinData{core::Function::kAbs, CallParamType::kF16, "fabs"},
-        BuiltinData{core::Function::kAcos, CallParamType::kF32, "acos"},
-        BuiltinData{core::Function::kAcos, CallParamType::kF16, "acos"},
-        BuiltinData{core::Function::kAsin, CallParamType::kF32, "asin"},
-        BuiltinData{core::Function::kAsin, CallParamType::kF16, "asin"},
-        BuiltinData{core::Function::kAtan, CallParamType::kF32, "atan"},
-        BuiltinData{core::Function::kAtan, CallParamType::kF16, "atan"},
-        BuiltinData{core::Function::kAtan2, CallParamType::kF32, "atan2"},
-        BuiltinData{core::Function::kAtan2, CallParamType::kF16, "atan2"},
-        BuiltinData{core::Function::kCeil, CallParamType::kF32, "ceil"},
-        BuiltinData{core::Function::kCeil, CallParamType::kF16, "ceil"},
-        BuiltinData{core::Function::kClamp, CallParamType::kF32, "clamp"},
-        BuiltinData{core::Function::kClamp, CallParamType::kF16, "clamp"},
-        BuiltinData{core::Function::kCos, CallParamType::kF32, "cos"},
-        BuiltinData{core::Function::kCos, CallParamType::kF16, "cos"},
-        BuiltinData{core::Function::kCosh, CallParamType::kF32, "cosh"},
-        BuiltinData{core::Function::kCosh, CallParamType::kF16, "cosh"},
-        BuiltinData{core::Function::kCross, CallParamType::kF32, "cross"},
-        BuiltinData{core::Function::kCross, CallParamType::kF16, "cross"},
-        BuiltinData{core::Function::kDistance, CallParamType::kF32, "distance"},
-        BuiltinData{core::Function::kDistance, CallParamType::kF16, "distance"},
-        BuiltinData{core::Function::kExp, CallParamType::kF32, "exp"},
-        BuiltinData{core::Function::kExp, CallParamType::kF16, "exp"},
-        BuiltinData{core::Function::kExp2, CallParamType::kF32, "exp2"},
-        BuiltinData{core::Function::kExp2, CallParamType::kF16, "exp2"},
-        BuiltinData{core::Function::kFaceForward, CallParamType::kF32, "faceforward"},
-        BuiltinData{core::Function::kFaceForward, CallParamType::kF16, "faceforward"},
-        BuiltinData{core::Function::kFloor, CallParamType::kF32, "floor"},
-        BuiltinData{core::Function::kFloor, CallParamType::kF16, "floor"},
-        BuiltinData{core::Function::kFma, CallParamType::kF32, "fma"},
-        BuiltinData{core::Function::kFma, CallParamType::kF16, "fma"},
-        BuiltinData{core::Function::kFract, CallParamType::kF32, "fract"},
-        BuiltinData{core::Function::kFract, CallParamType::kF16, "fract"},
-        BuiltinData{core::Function::kInverseSqrt, CallParamType::kF32, "rsqrt"},
-        BuiltinData{core::Function::kInverseSqrt, CallParamType::kF16, "rsqrt"},
-        BuiltinData{core::Function::kLdexp, CallParamType::kF32, "ldexp"},
-        BuiltinData{core::Function::kLdexp, CallParamType::kF16, "ldexp"},
-        BuiltinData{core::Function::kLength, CallParamType::kF32, "length"},
-        BuiltinData{core::Function::kLength, CallParamType::kF16, "length"},
-        BuiltinData{core::Function::kLog, CallParamType::kF32, "log"},
-        BuiltinData{core::Function::kLog, CallParamType::kF16, "log"},
-        BuiltinData{core::Function::kLog2, CallParamType::kF32, "log2"},
-        BuiltinData{core::Function::kLog2, CallParamType::kF16, "log2"},
-        BuiltinData{core::Function::kMax, CallParamType::kF32, "fmax"},
-        BuiltinData{core::Function::kMax, CallParamType::kF16, "fmax"},
-        BuiltinData{core::Function::kMin, CallParamType::kF32, "fmin"},
-        BuiltinData{core::Function::kMin, CallParamType::kF16, "fmin"},
-        BuiltinData{core::Function::kNormalize, CallParamType::kF32, "normalize"},
-        BuiltinData{core::Function::kNormalize, CallParamType::kF16, "normalize"},
-        BuiltinData{core::Function::kPow, CallParamType::kF32, "pow"},
-        BuiltinData{core::Function::kPow, CallParamType::kF16, "pow"},
-        BuiltinData{core::Function::kReflect, CallParamType::kF32, "reflect"},
-        BuiltinData{core::Function::kReflect, CallParamType::kF16, "reflect"},
-        BuiltinData{core::Function::kSign, CallParamType::kF32, "sign"},
-        BuiltinData{core::Function::kSign, CallParamType::kF16, "sign"},
-        BuiltinData{core::Function::kSin, CallParamType::kF32, "sin"},
-        BuiltinData{core::Function::kSin, CallParamType::kF16, "sin"},
-        BuiltinData{core::Function::kSinh, CallParamType::kF32, "sinh"},
-        BuiltinData{core::Function::kSinh, CallParamType::kF16, "sinh"},
-        BuiltinData{core::Function::kSmoothstep, CallParamType::kF32, "smoothstep"},
-        BuiltinData{core::Function::kSmoothstep, CallParamType::kF16, "smoothstep"},
-        BuiltinData{core::Function::kSqrt, CallParamType::kF32, "sqrt"},
-        BuiltinData{core::Function::kSqrt, CallParamType::kF16, "sqrt"},
-        BuiltinData{core::Function::kStep, CallParamType::kF32, "step"},
-        BuiltinData{core::Function::kStep, CallParamType::kF16, "step"},
-        BuiltinData{core::Function::kTan, CallParamType::kF32, "tan"},
-        BuiltinData{core::Function::kTan, CallParamType::kF16, "tan"},
-        BuiltinData{core::Function::kTanh, CallParamType::kF32, "tanh"},
-        BuiltinData{core::Function::kTanh, CallParamType::kF16, "tanh"},
-        BuiltinData{core::Function::kTrunc, CallParamType::kF32, "trunc"},
-        BuiltinData{core::Function::kTrunc, CallParamType::kF16, "trunc"},
+        BuiltinData{core::BuiltinFn::kAbs, CallParamType::kF32, "fabs"},
+        BuiltinData{core::BuiltinFn::kAbs, CallParamType::kF16, "fabs"},
+        BuiltinData{core::BuiltinFn::kAcos, CallParamType::kF32, "acos"},
+        BuiltinData{core::BuiltinFn::kAcos, CallParamType::kF16, "acos"},
+        BuiltinData{core::BuiltinFn::kAsin, CallParamType::kF32, "asin"},
+        BuiltinData{core::BuiltinFn::kAsin, CallParamType::kF16, "asin"},
+        BuiltinData{core::BuiltinFn::kAtan, CallParamType::kF32, "atan"},
+        BuiltinData{core::BuiltinFn::kAtan, CallParamType::kF16, "atan"},
+        BuiltinData{core::BuiltinFn::kAtan2, CallParamType::kF32, "atan2"},
+        BuiltinData{core::BuiltinFn::kAtan2, CallParamType::kF16, "atan2"},
+        BuiltinData{core::BuiltinFn::kCeil, CallParamType::kF32, "ceil"},
+        BuiltinData{core::BuiltinFn::kCeil, CallParamType::kF16, "ceil"},
+        BuiltinData{core::BuiltinFn::kClamp, CallParamType::kF32, "clamp"},
+        BuiltinData{core::BuiltinFn::kClamp, CallParamType::kF16, "clamp"},
+        BuiltinData{core::BuiltinFn::kCos, CallParamType::kF32, "cos"},
+        BuiltinData{core::BuiltinFn::kCos, CallParamType::kF16, "cos"},
+        BuiltinData{core::BuiltinFn::kCosh, CallParamType::kF32, "cosh"},
+        BuiltinData{core::BuiltinFn::kCosh, CallParamType::kF16, "cosh"},
+        BuiltinData{core::BuiltinFn::kCross, CallParamType::kF32, "cross"},
+        BuiltinData{core::BuiltinFn::kCross, CallParamType::kF16, "cross"},
+        BuiltinData{core::BuiltinFn::kDistance, CallParamType::kF32, "distance"},
+        BuiltinData{core::BuiltinFn::kDistance, CallParamType::kF16, "distance"},
+        BuiltinData{core::BuiltinFn::kExp, CallParamType::kF32, "exp"},
+        BuiltinData{core::BuiltinFn::kExp, CallParamType::kF16, "exp"},
+        BuiltinData{core::BuiltinFn::kExp2, CallParamType::kF32, "exp2"},
+        BuiltinData{core::BuiltinFn::kExp2, CallParamType::kF16, "exp2"},
+        BuiltinData{core::BuiltinFn::kFaceForward, CallParamType::kF32, "faceforward"},
+        BuiltinData{core::BuiltinFn::kFaceForward, CallParamType::kF16, "faceforward"},
+        BuiltinData{core::BuiltinFn::kFloor, CallParamType::kF32, "floor"},
+        BuiltinData{core::BuiltinFn::kFloor, CallParamType::kF16, "floor"},
+        BuiltinData{core::BuiltinFn::kFma, CallParamType::kF32, "fma"},
+        BuiltinData{core::BuiltinFn::kFma, CallParamType::kF16, "fma"},
+        BuiltinData{core::BuiltinFn::kFract, CallParamType::kF32, "fract"},
+        BuiltinData{core::BuiltinFn::kFract, CallParamType::kF16, "fract"},
+        BuiltinData{core::BuiltinFn::kInverseSqrt, CallParamType::kF32, "rsqrt"},
+        BuiltinData{core::BuiltinFn::kInverseSqrt, CallParamType::kF16, "rsqrt"},
+        BuiltinData{core::BuiltinFn::kLdexp, CallParamType::kF32, "ldexp"},
+        BuiltinData{core::BuiltinFn::kLdexp, CallParamType::kF16, "ldexp"},
+        BuiltinData{core::BuiltinFn::kLength, CallParamType::kF32, "length"},
+        BuiltinData{core::BuiltinFn::kLength, CallParamType::kF16, "length"},
+        BuiltinData{core::BuiltinFn::kLog, CallParamType::kF32, "log"},
+        BuiltinData{core::BuiltinFn::kLog, CallParamType::kF16, "log"},
+        BuiltinData{core::BuiltinFn::kLog2, CallParamType::kF32, "log2"},
+        BuiltinData{core::BuiltinFn::kLog2, CallParamType::kF16, "log2"},
+        BuiltinData{core::BuiltinFn::kMax, CallParamType::kF32, "fmax"},
+        BuiltinData{core::BuiltinFn::kMax, CallParamType::kF16, "fmax"},
+        BuiltinData{core::BuiltinFn::kMin, CallParamType::kF32, "fmin"},
+        BuiltinData{core::BuiltinFn::kMin, CallParamType::kF16, "fmin"},
+        BuiltinData{core::BuiltinFn::kNormalize, CallParamType::kF32, "normalize"},
+        BuiltinData{core::BuiltinFn::kNormalize, CallParamType::kF16, "normalize"},
+        BuiltinData{core::BuiltinFn::kPow, CallParamType::kF32, "pow"},
+        BuiltinData{core::BuiltinFn::kPow, CallParamType::kF16, "pow"},
+        BuiltinData{core::BuiltinFn::kReflect, CallParamType::kF32, "reflect"},
+        BuiltinData{core::BuiltinFn::kReflect, CallParamType::kF16, "reflect"},
+        BuiltinData{core::BuiltinFn::kSign, CallParamType::kF32, "sign"},
+        BuiltinData{core::BuiltinFn::kSign, CallParamType::kF16, "sign"},
+        BuiltinData{core::BuiltinFn::kSin, CallParamType::kF32, "sin"},
+        BuiltinData{core::BuiltinFn::kSin, CallParamType::kF16, "sin"},
+        BuiltinData{core::BuiltinFn::kSinh, CallParamType::kF32, "sinh"},
+        BuiltinData{core::BuiltinFn::kSinh, CallParamType::kF16, "sinh"},
+        BuiltinData{core::BuiltinFn::kSmoothstep, CallParamType::kF32, "smoothstep"},
+        BuiltinData{core::BuiltinFn::kSmoothstep, CallParamType::kF16, "smoothstep"},
+        BuiltinData{core::BuiltinFn::kSqrt, CallParamType::kF32, "sqrt"},
+        BuiltinData{core::BuiltinFn::kSqrt, CallParamType::kF16, "sqrt"},
+        BuiltinData{core::BuiltinFn::kStep, CallParamType::kF32, "step"},
+        BuiltinData{core::BuiltinFn::kStep, CallParamType::kF16, "step"},
+        BuiltinData{core::BuiltinFn::kTan, CallParamType::kF32, "tan"},
+        BuiltinData{core::BuiltinFn::kTan, CallParamType::kF16, "tan"},
+        BuiltinData{core::BuiltinFn::kTanh, CallParamType::kF32, "tanh"},
+        BuiltinData{core::BuiltinFn::kTanh, CallParamType::kF16, "tanh"},
+        BuiltinData{core::BuiltinFn::kTrunc, CallParamType::kF32, "trunc"},
+        BuiltinData{core::BuiltinFn::kTrunc, CallParamType::kF16, "trunc"},
         /* Integer built-in */
-        BuiltinData{core::Function::kAbs, CallParamType::kU32, "abs"},
-        BuiltinData{core::Function::kClamp, CallParamType::kU32, "clamp"},
-        BuiltinData{core::Function::kCountLeadingZeros, CallParamType::kU32, "clz"},
-        BuiltinData{core::Function::kCountOneBits, CallParamType::kU32, "popcount"},
-        BuiltinData{core::Function::kCountTrailingZeros, CallParamType::kU32, "ctz"},
-        BuiltinData{core::Function::kExtractBits, CallParamType::kU32, "extract_bits"},
-        BuiltinData{core::Function::kInsertBits, CallParamType::kU32, "insert_bits"},
-        BuiltinData{core::Function::kMax, CallParamType::kU32, "max"},
-        BuiltinData{core::Function::kMin, CallParamType::kU32, "min"},
-        BuiltinData{core::Function::kReverseBits, CallParamType::kU32, "reverse_bits"},
-        BuiltinData{core::Function::kRound, CallParamType::kU32, "rint"},
+        BuiltinData{core::BuiltinFn::kAbs, CallParamType::kU32, "abs"},
+        BuiltinData{core::BuiltinFn::kClamp, CallParamType::kU32, "clamp"},
+        BuiltinData{core::BuiltinFn::kCountLeadingZeros, CallParamType::kU32, "clz"},
+        BuiltinData{core::BuiltinFn::kCountOneBits, CallParamType::kU32, "popcount"},
+        BuiltinData{core::BuiltinFn::kCountTrailingZeros, CallParamType::kU32, "ctz"},
+        BuiltinData{core::BuiltinFn::kExtractBits, CallParamType::kU32, "extract_bits"},
+        BuiltinData{core::BuiltinFn::kInsertBits, CallParamType::kU32, "insert_bits"},
+        BuiltinData{core::BuiltinFn::kMax, CallParamType::kU32, "max"},
+        BuiltinData{core::BuiltinFn::kMin, CallParamType::kU32, "min"},
+        BuiltinData{core::BuiltinFn::kReverseBits, CallParamType::kU32, "reverse_bits"},
+        BuiltinData{core::BuiltinFn::kRound, CallParamType::kU32, "rint"},
         /* Matrix built-in */
-        BuiltinData{core::Function::kDeterminant, CallParamType::kF32, "determinant"},
-        BuiltinData{core::Function::kTranspose, CallParamType::kF32, "transpose"},
+        BuiltinData{core::BuiltinFn::kDeterminant, CallParamType::kF32, "determinant"},
+        BuiltinData{core::BuiltinFn::kTranspose, CallParamType::kF32, "transpose"},
         /* Vector built-in */
-        BuiltinData{core::Function::kDot, CallParamType::kF32, "dot"},
+        BuiltinData{core::BuiltinFn::kDot, CallParamType::kF32, "dot"},
         /* Derivate built-in */
-        BuiltinData{core::Function::kDpdx, CallParamType::kF32, "dfdx"},
-        BuiltinData{core::Function::kDpdxCoarse, CallParamType::kF32, "dfdx"},
-        BuiltinData{core::Function::kDpdxFine, CallParamType::kF32, "dfdx"},
-        BuiltinData{core::Function::kDpdy, CallParamType::kF32, "dfdy"},
-        BuiltinData{core::Function::kDpdyCoarse, CallParamType::kF32, "dfdy"},
-        BuiltinData{core::Function::kDpdyFine, CallParamType::kF32, "dfdy"},
-        BuiltinData{core::Function::kFwidth, CallParamType::kF32, "fwidth"},
-        BuiltinData{core::Function::kFwidthCoarse, CallParamType::kF32, "fwidth"},
-        BuiltinData{core::Function::kFwidthFine, CallParamType::kF32, "fwidth"},
+        BuiltinData{core::BuiltinFn::kDpdx, CallParamType::kF32, "dfdx"},
+        BuiltinData{core::BuiltinFn::kDpdxCoarse, CallParamType::kF32, "dfdx"},
+        BuiltinData{core::BuiltinFn::kDpdxFine, CallParamType::kF32, "dfdx"},
+        BuiltinData{core::BuiltinFn::kDpdy, CallParamType::kF32, "dfdy"},
+        BuiltinData{core::BuiltinFn::kDpdyCoarse, CallParamType::kF32, "dfdy"},
+        BuiltinData{core::BuiltinFn::kDpdyFine, CallParamType::kF32, "dfdy"},
+        BuiltinData{core::BuiltinFn::kFwidth, CallParamType::kF32, "fwidth"},
+        BuiltinData{core::BuiltinFn::kFwidthCoarse, CallParamType::kF32, "fwidth"},
+        BuiltinData{core::BuiltinFn::kFwidthFine, CallParamType::kF32, "fwidth"},
         /* Data packing builtin */
-        BuiltinData{core::Function::kPack4X8Snorm, CallParamType::kF32, "pack_float_to_snorm4x8"},
-        BuiltinData{core::Function::kPack4X8Unorm, CallParamType::kF32, "pack_float_to_unorm4x8"},
-        BuiltinData{core::Function::kPack2X16Snorm, CallParamType::kF32, "pack_float_to_snorm2x16"},
-        BuiltinData{core::Function::kPack2X16Unorm, CallParamType::kF32, "pack_float_to_unorm2x16"},
+        BuiltinData{core::BuiltinFn::kPack4X8Snorm, CallParamType::kF32, "pack_float_to_snorm4x8"},
+        BuiltinData{core::BuiltinFn::kPack4X8Unorm, CallParamType::kF32, "pack_float_to_unorm4x8"},
+        BuiltinData{core::BuiltinFn::kPack2X16Snorm, CallParamType::kF32,
+                    "pack_float_to_snorm2x16"},
+        BuiltinData{core::BuiltinFn::kPack2X16Unorm, CallParamType::kF32,
+                    "pack_float_to_unorm2x16"},
         /* Data unpacking builtin */
-        BuiltinData{core::Function::kUnpack4X8Snorm, CallParamType::kU32,
+        BuiltinData{core::BuiltinFn::kUnpack4X8Snorm, CallParamType::kU32,
                     "unpack_snorm4x8_to_float"},
-        BuiltinData{core::Function::kUnpack4X8Unorm, CallParamType::kU32,
+        BuiltinData{core::BuiltinFn::kUnpack4X8Unorm, CallParamType::kU32,
                     "unpack_unorm4x8_to_float"},
-        BuiltinData{core::Function::kUnpack2X16Snorm, CallParamType::kU32,
+        BuiltinData{core::BuiltinFn::kUnpack2X16Snorm, CallParamType::kU32,
                     "unpack_snorm2x16_to_float"},
-        BuiltinData{core::Function::kUnpack2X16Unorm, CallParamType::kU32,
+        BuiltinData{core::BuiltinFn::kUnpack2X16Unorm, CallParamType::kU32,
                     "unpack_unorm2x16_to_float"}));
 
 TEST_F(MslASTPrinterTest, Builtin_Call) {
diff --git a/src/tint/lang/msl/writer/ast_printer/helper_test.h b/src/tint/lang/msl/writer/ast_printer/helper_test.h
index aeb4982..c8b1167 100644
--- a/src/tint/lang/msl/writer/ast_printer/helper_test.h
+++ b/src/tint/lang/msl/writer/ast_printer/helper_test.h
@@ -49,12 +49,14 @@
         if (gen_) {
             return *gen_;
         }
-        [&] {
-            ASSERT_TRUE(IsValid()) << "Builder program is not valid\n" << Diagnostics().str();
-        }();
+        if (!IsValid()) {
+            ADD_FAILURE() << "ProgramBuilder is not valid: " << Diagnostics();
+        }
         program = std::make_unique<Program>(resolver::Resolve(*this));
-        [&] { ASSERT_TRUE(program->IsValid()) << program->Diagnostics().str(); }();
-        gen_ = std::make_unique<ASTPrinter>(program.get());
+        if (!program->IsValid()) {
+            ADD_FAILURE() << program->Diagnostics();
+        }
+        gen_ = std::make_unique<ASTPrinter>(*program);
         return *gen_;
     }
 
@@ -68,16 +70,20 @@
         if (gen_) {
             return *gen_;
         }
-        [&] {
-            ASSERT_TRUE(IsValid()) << "Builder program is not valid\n" << Diagnostics().str();
-        }();
+        if (!IsValid()) {
+            ADD_FAILURE() << "ProgramBuilder is not valid: " << Diagnostics();
+        }
         program = std::make_unique<Program>(resolver::Resolve(*this));
-        [&] { ASSERT_TRUE(program->IsValid()) << program->Diagnostics().str(); }();
+        if (!program->IsValid()) {
+            ADD_FAILURE() << program->Diagnostics();
+        }
 
-        auto result = Sanitize(program.get(), options);
-        [&] { ASSERT_TRUE(result.program.IsValid()) << result.program.Diagnostics().str(); }();
+        auto result = Sanitize(*program, options);
+        if (!result.program.IsValid()) {
+            ADD_FAILURE() << result.program.Diagnostics();
+        }
         *program = std::move(result.program);
-        gen_ = std::make_unique<ASTPrinter>(program.get());
+        gen_ = std::make_unique<ASTPrinter>(*program);
         return *gen_;
     }
 
diff --git a/src/tint/lang/msl/writer/ast_printer/import_test.cc b/src/tint/lang/msl/writer/ast_printer/import_test.cc
index f77ddec..a3f501d 100644
--- a/src/tint/lang/msl/writer/ast_printer/import_test.cc
+++ b/src/tint/lang/msl/writer/ast_printer/import_test.cc
@@ -46,7 +46,7 @@
     ASSERT_NE(sem, nullptr);
     auto* target = sem->Target();
     ASSERT_NE(target, nullptr);
-    auto* builtin = target->As<sem::Builtin>();
+    auto* builtin = target->As<sem::BuiltinFn>();
     ASSERT_NE(builtin, nullptr);
 
     ASSERT_EQ(gen.generate_builtin_name(builtin), param.msl_name);
diff --git a/src/tint/lang/msl/writer/ast_raise/module_scope_var_to_entry_point_param.cc b/src/tint/lang/msl/writer/ast_raise/module_scope_var_to_entry_point_param.cc
index ef07aa9..95ab089 100644
--- a/src/tint/lang/msl/writer/ast_raise/module_scope_var_to_entry_point_param.cc
+++ b/src/tint/lang/msl/writer/ast_raise/module_scope_var_to_entry_point_param.cc
@@ -43,8 +43,8 @@
 // The name of the struct member for arrays that are wrapped in structures.
 const char* kWrappedArrayMemberName = "arr";
 
-bool ShouldRun(const Program* program) {
-    for (auto* decl : program->AST().GlobalDeclarations()) {
+bool ShouldRun(const Program& program) {
+    for (auto* decl : program.AST().GlobalDeclarations()) {
         if (decl->Is<ast::Variable>()) {
             return true;
         }
@@ -587,7 +587,7 @@
 ModuleScopeVarToEntryPointParam::~ModuleScopeVarToEntryPointParam() = default;
 
 ast::transform::Transform::ApplyResult ModuleScopeVarToEntryPointParam::Apply(
-    const Program* src,
+    const Program& src,
     const ast::transform::DataMap&,
     ast::transform::DataMap&) const {
     if (!ShouldRun(src)) {
@@ -595,7 +595,7 @@
     }
 
     ProgramBuilder b;
-    program::CloneContext ctx{&b, src, /* auto_clone_symbols */ true};
+    program::CloneContext ctx{&b, &src, /* auto_clone_symbols */ true};
     State state{ctx};
     state.Process();
 
diff --git a/src/tint/lang/msl/writer/ast_raise/module_scope_var_to_entry_point_param.h b/src/tint/lang/msl/writer/ast_raise/module_scope_var_to_entry_point_param.h
index 034b22a..5d2ca0d 100644
--- a/src/tint/lang/msl/writer/ast_raise/module_scope_var_to_entry_point_param.h
+++ b/src/tint/lang/msl/writer/ast_raise/module_scope_var_to_entry_point_param.h
@@ -70,7 +70,7 @@
     ~ModuleScopeVarToEntryPointParam() override;
 
     /// @copydoc ast::transform::Transform::Apply
-    ApplyResult Apply(const Program* program,
+    ApplyResult Apply(const Program& program,
                       const ast::transform::DataMap& inputs,
                       ast::transform::DataMap& outputs) const override;
 
diff --git a/src/tint/lang/msl/writer/ast_raise/packed_vec3.cc b/src/tint/lang/msl/writer/ast_raise/packed_vec3.cc
index d24d83f..bfea334 100644
--- a/src/tint/lang/msl/writer/ast_raise/packed_vec3.cc
+++ b/src/tint/lang/msl/writer/ast_raise/packed_vec3.cc
@@ -18,7 +18,7 @@
 #include <string>
 #include <utility>
 
-#include "src/tint/lang/core/builtin.h"
+#include "src/tint/lang/core/builtin_type.h"
 #include "src/tint/lang/core/fluent_types.h"
 #include "src/tint/lang/core/type/array.h"
 #include "src/tint/lang/core/type/reference.h"
@@ -49,7 +49,7 @@
 struct PackedVec3::State {
     /// Constructor
     /// @param program the source program
-    explicit State(const Program* program) : src(program) {}
+    explicit State(const Program& program) : src(program) {}
 
     /// The name of the struct member used when wrapping packed vec3 types.
     static constexpr const char* kStructMemberName = "elements";
@@ -101,7 +101,7 @@
     ast::Type MakePackedVec3(const core::type::Type* ty) {
         auto* vec = ty->As<core::type::Vector>();
         TINT_ASSERT(vec != nullptr && vec->Width() == 3);
-        return b.ty(core::Builtin::kPackedVec3, CreateASTTypeFor(ctx, vec->type()));
+        return b.ty(core::BuiltinType::kPackedVec3, CreateASTTypeFor(ctx, vec->type()));
     }
 
     /// Recursively rewrite a type using `__packed_vec3`, if needed.
@@ -342,7 +342,7 @@
     bool ShouldRun() {
         // Check for vec3s in the types of all uniform and storage buffer variables to determine
         // if the transform is necessary.
-        for (auto* decl : src->AST().GlobalVariables()) {
+        for (auto* decl : src.AST().GlobalVariables()) {
             auto* var = sem.Get<sem::GlobalVariable>(decl);
             if (var && core::IsHostShareable(var->AddressSpace()) &&
                 ContainsVec3(var->Type()->UnwrapRef())) {
@@ -375,7 +375,7 @@
         // Replace vec3 types in host-shareable address spaces with `__packed_vec3` types, and
         // collect expressions that need to be converted to or from values that use the
         // `__packed_vec3` type.
-        for (auto* node : ctx.src->ASTNodes().Objects()) {
+        for (auto* node : src.ASTNodes().Objects()) {
             Switch(
                 sem.Get(node),
                 [&](const sem::TypeExpression* type) {
@@ -508,19 +508,19 @@
 
   private:
     /// The source program
-    const Program* const src;
+    const Program& src;
     /// The target program builder
     ProgramBuilder b;
     /// The clone context
-    program::CloneContext ctx = {&b, src, /* auto_clone_symbols */ true};
+    program::CloneContext ctx = {&b, &src, /* auto_clone_symbols */ true};
     /// Alias to the semantic info in ctx.src
-    const sem::Info& sem = ctx.src->Sem();
+    const sem::Info& sem = src.Sem();
 };
 
 PackedVec3::PackedVec3() = default;
 PackedVec3::~PackedVec3() = default;
 
-ast::transform::Transform::ApplyResult PackedVec3::Apply(const Program* src,
+ast::transform::Transform::ApplyResult PackedVec3::Apply(const Program& src,
                                                          const ast::transform::DataMap&,
                                                          ast::transform::DataMap&) const {
     return State{src}.Run();
diff --git a/src/tint/lang/msl/writer/ast_raise/packed_vec3.h b/src/tint/lang/msl/writer/ast_raise/packed_vec3.h
index 4ef04a5..d557c41 100644
--- a/src/tint/lang/msl/writer/ast_raise/packed_vec3.h
+++ b/src/tint/lang/msl/writer/ast_raise/packed_vec3.h
@@ -46,7 +46,7 @@
     ~PackedVec3() override;
 
     /// @copydoc ast::transform::Transform::Apply
-    ApplyResult Apply(const Program* program,
+    ApplyResult Apply(const Program& program,
                       const ast::transform::DataMap& inputs,
                       ast::transform::DataMap& outputs) const override;
 
diff --git a/src/tint/lang/msl/writer/ast_raise/pixel_local.cc b/src/tint/lang/msl/writer/ast_raise/pixel_local.cc
index df67318..170f695 100644
--- a/src/tint/lang/msl/writer/ast_raise/pixel_local.cc
+++ b/src/tint/lang/msl/writer/ast_raise/pixel_local.cc
@@ -37,23 +37,23 @@
 /// PIMPL state for the transform
 struct PixelLocal::State {
     /// The source program
-    const Program* const src;
+    const Program& src;
     /// The target program builder
     ProgramBuilder b;
     /// The clone context
-    program::CloneContext ctx = {&b, src, /* auto_clone_symbols */ true};
+    program::CloneContext ctx = {&b, &src, /* auto_clone_symbols */ true};
     /// The transform config
     const Config& cfg;
 
     /// Constructor
     /// @param program the source program
     /// @param config the transform config
-    State(const Program* program, const Config& config) : src(program), cfg(config) {}
+    State(const Program& program, const Config& config) : src(program), cfg(config) {}
 
     /// Runs the transform
     /// @returns the new program or SkipTransform if the transform is not required
     ApplyResult Run() {
-        auto& sem = src->Sem();
+        auto& sem = src.Sem();
 
         // If the pixel local extension isn't enabled, then there must be no use of pixel_local
         // variables, and so there's nothing for this transform to do.
@@ -67,7 +67,7 @@
         // Change all module scope `var<pixel_local>` variables to `var<private>`.
         // We need to do this even if the variable is not referenced by the entry point as later
         // stages do not understand the pixel_local address space.
-        for (auto* global : src->AST().GlobalVariables()) {
+        for (auto* global : src.AST().GlobalVariables()) {
             if (auto* var = global->As<ast::Var>()) {
                 if (sem.Get(var)->AddressSpace() == core::AddressSpace::kPixelLocal) {
                     // Change the 'var<pixel_local>' to 'var<private>'
@@ -79,7 +79,7 @@
 
         // Find the single entry point
         const sem::Function* entry_point = nullptr;
-        for (auto* fn : src->AST().Functions()) {
+        for (auto* fn : src.AST().Functions()) {
             if (fn->IsEntryPoint()) {
                 if (entry_point != nullptr) {
                     TINT_ICE() << "PixelLocal transform requires that the SingleEntryPoint "
@@ -251,7 +251,7 @@
 
 PixelLocal::~PixelLocal() = default;
 
-ast::transform::Transform::ApplyResult PixelLocal::Apply(const Program* src,
+ast::transform::Transform::ApplyResult PixelLocal::Apply(const Program& src,
                                                          const ast::transform::DataMap& inputs,
                                                          ast::transform::DataMap&) const {
     auto* cfg = inputs.Get<Config>();
diff --git a/src/tint/lang/msl/writer/ast_raise/pixel_local.h b/src/tint/lang/msl/writer/ast_raise/pixel_local.h
index 4d1b51f..eb313c8 100644
--- a/src/tint/lang/msl/writer/ast_raise/pixel_local.h
+++ b/src/tint/lang/msl/writer/ast_raise/pixel_local.h
@@ -83,7 +83,7 @@
     ~PixelLocal() override;
 
     /// @copydoc ast::transform::Transform::Apply
-    ApplyResult Apply(const Program* program,
+    ApplyResult Apply(const Program& program,
                       const ast::transform::DataMap& inputs,
                       ast::transform::DataMap& outputs) const override;
 
diff --git a/src/tint/lang/msl/writer/ast_raise/subgroup_ballot.cc b/src/tint/lang/msl/writer/ast_raise/subgroup_ballot.cc
index e15bf24..f977803 100644
--- a/src/tint/lang/msl/writer/ast_raise/subgroup_ballot.cc
+++ b/src/tint/lang/msl/writer/ast_raise/subgroup_ballot.cc
@@ -35,11 +35,11 @@
 /// PIMPL state for the transform
 struct SubgroupBallot::State {
     /// The source program
-    const Program* const src;
+    const Program& src;
     /// The target program builder
     ProgramBuilder b;
     /// The clone context
-    program::CloneContext ctx = {&b, src, /* auto_clone_symbols */ true};
+    program::CloneContext ctx = {&b, &src, /* auto_clone_symbols */ true};
 
     /// The name of the `tint_subgroup_ballot` helper function.
     Symbol ballot_helper{};
@@ -52,12 +52,12 @@
 
     /// Constructor
     /// @param program the source program
-    explicit State(const Program* program) : src(program) {}
+    explicit State(const Program& program) : src(program) {}
 
     /// Runs the transform
     /// @returns the new program or SkipTransform if the transform is not required
     ApplyResult Run() {
-        auto& sem = src->Sem();
+        auto& sem = src.Sem();
 
         bool made_changes = false;
         for (auto* node : ctx.src->ASTNodes().Objects()) {
@@ -65,8 +65,8 @@
             if (call) {
                 // If this is a call to a `subgroupBallot()` builtin, replace it with a call to the
                 // helper function and make a note of the function that we are in.
-                auto* builtin = call->Target()->As<sem::Builtin>();
-                if (builtin && builtin->Type() == core::Function::kSubgroupBallot) {
+                auto* builtin = call->Target()->As<sem::BuiltinFn>();
+                if (builtin && builtin->Fn() == core::BuiltinFn::kSubgroupBallot) {
                     ctx.Replace(call->Declaration(), b.Call(GetHelper()));
                     ballot_callers.Add(call->Stmt()->Function());
                     made_changes = true;
@@ -79,7 +79,7 @@
 
         // Set the subgroup size mask at the start of each entry point that transitively calls
         // `subgroupBallot()`.
-        for (auto* global : src->AST().GlobalDeclarations()) {
+        for (auto* global : src.AST().GlobalDeclarations()) {
             auto* func = global->As<ast::Function>();
             if (func && func->IsEntryPoint() && TransitvelyCallsSubgroupBallot(sem.Get(func))) {
                 SetSubgroupSizeMask(func);
@@ -145,7 +145,7 @@
         for (auto* param : ep->params) {
             auto* builtin = ast::GetAttribute<ast::BuiltinAttribute>(param->attributes);
             if (builtin &&
-                src->Sem()
+                src.Sem()
                         .Get<sem::BuiltinEnumExpression<core::BuiltinValue>>(builtin->builtin)
                         ->Value() == core::BuiltinValue::kSubgroupSize) {
                 subgroup_size = ctx.Clone(param->name->symbol);
@@ -183,7 +183,7 @@
 
 SubgroupBallot::~SubgroupBallot() = default;
 
-ast::transform::Transform::ApplyResult SubgroupBallot::Apply(const Program* src,
+ast::transform::Transform::ApplyResult SubgroupBallot::Apply(const Program& src,
                                                              const ast::transform::DataMap&,
                                                              ast::transform::DataMap&) const {
     return State(src).Run();
diff --git a/src/tint/lang/msl/writer/ast_raise/subgroup_ballot.h b/src/tint/lang/msl/writer/ast_raise/subgroup_ballot.h
index cde3203..cd13d4c 100644
--- a/src/tint/lang/msl/writer/ast_raise/subgroup_ballot.h
+++ b/src/tint/lang/msl/writer/ast_raise/subgroup_ballot.h
@@ -36,7 +36,7 @@
     ~SubgroupBallot() override;
 
     /// @copydoc ast::transform::Transform::Apply
-    ApplyResult Apply(const Program* program,
+    ApplyResult Apply(const Program& program,
                       const ast::transform::DataMap& inputs,
                       ast::transform::DataMap& outputs) const override;
 
diff --git a/src/tint/lang/msl/writer/writer.cc b/src/tint/lang/msl/writer/writer.cc
index fcfc910..088d81d 100644
--- a/src/tint/lang/msl/writer/writer.cc
+++ b/src/tint/lang/msl/writer/writer.cc
@@ -24,8 +24,8 @@
 
 namespace tint::msl::writer {
 
-Result<Output, std::string> Generate(const Program* program, const Options& options) {
-    if (!program->IsValid()) {
+Result<Output, std::string> Generate(const Program& program, const Options& options) {
+    if (!program.IsValid()) {
         return std::string("input program is not valid");
     }
 
@@ -64,7 +64,7 @@
             std::move(sanitized_result.used_array_length_from_uniform_indices);
 
         // Generate the MSL code.
-        auto impl = std::make_unique<ASTPrinter>(&sanitized_result.program);
+        auto impl = std::make_unique<ASTPrinter>(sanitized_result.program);
         if (!impl->Generate()) {
             return impl->Diagnostics().str();
         }
diff --git a/src/tint/lang/msl/writer/writer.h b/src/tint/lang/msl/writer/writer.h
index 1bfbc8f..c0ada69 100644
--- a/src/tint/lang/msl/writer/writer.h
+++ b/src/tint/lang/msl/writer/writer.h
@@ -33,7 +33,7 @@
 /// @param program the program to translate to MSL
 /// @param options the configuration options to use when generating MSL
 /// @returns the resulting MSL and supplementary information, or an error string
-Result<Output, std::string> Generate(const Program* program, const Options& options);
+Result<Output, std::string> Generate(const Program& program, const Options& options);
 
 }  // namespace tint::msl::writer
 
diff --git a/src/tint/lang/msl/writer/writer_bench.cc b/src/tint/lang/msl/writer/writer_bench.cc
index b10a350..beeec4d 100644
--- a/src/tint/lang/msl/writer/writer_bench.cc
+++ b/src/tint/lang/msl/writer/writer_bench.cc
@@ -61,7 +61,7 @@
         }
     }
     for (auto _ : state) {
-        auto res = Generate(&program, gen_options);
+        auto res = Generate(program, gen_options);
         if (!res) {
             state.SkipWithError(res.Failure().c_str());
         }
diff --git a/src/tint/lang/spirv/BUILD.bazel b/src/tint/lang/spirv/BUILD.bazel
index 9f81589..a2906a0 100644
--- a/src/tint/lang/spirv/BUILD.bazel
+++ b/src/tint/lang/spirv/BUILD.bazel
@@ -23,4 +23,18 @@
 
 load("//src/tint:flags.bzl", "COPTS")
 load("@bazel_skylib//lib:selects.bzl", "selects")
+cc_library(
+  name = "spirv",
+  srcs = [
+    "builtin_fn.cc",
+  ],
+  hdrs = [
+    "builtin_fn.h",
+  ],
+  deps = [
+    "//src/tint/utils/traits",
+  ],
+  copts = COPTS,
+  visibility = ["//visibility:public"],
+)
 
diff --git a/src/tint/lang/spirv/BUILD.cmake b/src/tint/lang/spirv/BUILD.cmake
index c6fc438..1a2be15 100644
--- a/src/tint/lang/spirv/BUILD.cmake
+++ b/src/tint/lang/spirv/BUILD.cmake
@@ -26,3 +26,16 @@
 include(lang/spirv/reader/BUILD.cmake)
 include(lang/spirv/type/BUILD.cmake)
 include(lang/spirv/writer/BUILD.cmake)
+
+################################################################################
+# Target:    tint_lang_spirv
+# Kind:      lib
+################################################################################
+tint_add_target(tint_lang_spirv lib
+  lang/spirv/builtin_fn.cc
+  lang/spirv/builtin_fn.h
+)
+
+tint_target_add_dependencies(tint_lang_spirv lib
+  tint_utils_traits
+)
diff --git a/src/tint/lang/spirv/BUILD.gn b/src/tint/lang/spirv/BUILD.gn
index b8a448e..0bfa460 100644
--- a/src/tint/lang/spirv/BUILD.gn
+++ b/src/tint/lang/spirv/BUILD.gn
@@ -24,3 +24,11 @@
 import("../../../../scripts/tint_overrides_with_defaults.gni")
 
 import("${tint_src_dir}/tint.gni")
+
+libtint_source_set("spirv") {
+  sources = [
+    "builtin_fn.cc",
+    "builtin_fn.h",
+  ]
+  deps = [ "${tint_src_dir}/utils/traits" ]
+}
diff --git a/src/tint/lang/spirv/builtin_fn.cc b/src/tint/lang/spirv/builtin_fn.cc
new file mode 100644
index 0000000..dbf99a9
--- /dev/null
+++ b/src/tint/lang/spirv/builtin_fn.cc
@@ -0,0 +1,102 @@
+// Copyright 2023 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+////////////////////////////////////////////////////////////////////////////////
+// File generated by 'tools/src/cmd/gen' using the template:
+//   src/tint/lang/spirv/builtin_fn.cc.tmpl
+//
+// To regenerate run: './tools/run gen'
+//
+//                       Do not modify this file directly
+////////////////////////////////////////////////////////////////////////////////
+
+#include "src/tint/lang/spirv/builtin_fn.h"
+
+namespace tint::spirv {
+
+const char* str(BuiltinFn i) {
+    switch (i) {
+        case BuiltinFn::kNone:
+            return "<none>";
+        case BuiltinFn::kArrayLength:
+            return "spirv.array_length";
+        case BuiltinFn::kAtomicAnd:
+            return "spirv.atomic_and";
+        case BuiltinFn::kAtomicCompareExchange:
+            return "spirv.atomic_compare_exchange";
+        case BuiltinFn::kAtomicExchange:
+            return "spirv.atomic_exchange";
+        case BuiltinFn::kAtomicIadd:
+            return "spirv.atomic_iadd";
+        case BuiltinFn::kAtomicIsub:
+            return "spirv.atomic_isub";
+        case BuiltinFn::kAtomicLoad:
+            return "spirv.atomic_load";
+        case BuiltinFn::kAtomicOr:
+            return "spirv.atomic_or";
+        case BuiltinFn::kAtomicSmax:
+            return "spirv.atomic_smax";
+        case BuiltinFn::kAtomicSmin:
+            return "spirv.atomic_smin";
+        case BuiltinFn::kAtomicStore:
+            return "spirv.atomic_store";
+        case BuiltinFn::kAtomicUmax:
+            return "spirv.atomic_umax";
+        case BuiltinFn::kAtomicUmin:
+            return "spirv.atomic_umin";
+        case BuiltinFn::kAtomicXor:
+            return "spirv.atomic_xor";
+        case BuiltinFn::kDot:
+            return "spirv.dot";
+        case BuiltinFn::kImageDrefGather:
+            return "spirv.image_dref_gather";
+        case BuiltinFn::kImageFetch:
+            return "spirv.image_fetch";
+        case BuiltinFn::kImageGather:
+            return "spirv.image_gather";
+        case BuiltinFn::kImageQuerySize:
+            return "spirv.image_query_size";
+        case BuiltinFn::kImageQuerySizeLod:
+            return "spirv.image_query_size_lod";
+        case BuiltinFn::kImageRead:
+            return "spirv.image_read";
+        case BuiltinFn::kImageSampleImplicitLod:
+            return "spirv.image_sample_implicit_lod";
+        case BuiltinFn::kImageSampleExplicitLod:
+            return "spirv.image_sample_explicit_lod";
+        case BuiltinFn::kImageSampleDrefImplicitLod:
+            return "spirv.image_sample_dref_implicit_lod";
+        case BuiltinFn::kImageSampleDrefExplicitLod:
+            return "spirv.image_sample_dref_explicit_lod";
+        case BuiltinFn::kImageWrite:
+            return "spirv.image_write";
+        case BuiltinFn::kMatrixTimesMatrix:
+            return "spirv.matrix_times_matrix";
+        case BuiltinFn::kMatrixTimesScalar:
+            return "spirv.matrix_times_scalar";
+        case BuiltinFn::kMatrixTimesVector:
+            return "spirv.matrix_times_vector";
+        case BuiltinFn::kSampledImage:
+            return "spirv.sampled_image";
+        case BuiltinFn::kSelect:
+            return "spirv.select";
+        case BuiltinFn::kVectorTimesMatrix:
+            return "spirv.vector_times_matrix";
+        case BuiltinFn::kVectorTimesScalar:
+            return "spirv.vector_times_scalar";
+    }
+    return "<unknown>";
+}
+
+}  // namespace tint::spirv
diff --git a/src/tint/lang/spirv/ir/function.cc.tmpl b/src/tint/lang/spirv/builtin_fn.cc.tmpl
similarity index 67%
rename from src/tint/lang/spirv/ir/function.cc.tmpl
rename to src/tint/lang/spirv/builtin_fn.cc.tmpl
index f745c2a..220d19e 100644
--- a/src/tint/lang/spirv/ir/function.cc.tmpl
+++ b/src/tint/lang/spirv/builtin_fn.cc.tmpl
@@ -1,6 +1,6 @@
 {{- /*
 --------------------------------------------------------------------------------
-Template file for use with tools/src/cmd/gen to generate function.cc
+Template file for use with tools/src/cmd/gen to generate builtin_fn.cc
 
 To update the generated file, run:
     ./tools/run gen
@@ -12,20 +12,20 @@
 */ -}}
 
 {{- $I := LoadIntrinsics "src/tint/lang/spirv/spirv.def" -}}
-#include "src/tint/lang/spirv/ir/function.h"
+#include "src/tint/lang/spirv/builtin_fn.h"
 
-namespace tint::spirv::ir {
+namespace tint::spirv {
 
-const char* str(Function i) {
+const char* str(BuiltinFn i) {
     switch (i) {
-        case Function::kNone:
+        case BuiltinFn::kNone:
             return "<none>";
 {{- range $I.Sem.Builtins  }}
-        case Function::k{{PascalCase .Name}}:
+        case BuiltinFn::k{{PascalCase .Name}}:
             return "spirv.{{.Name}}";
 {{- end  }}
     }
     return "<unknown>";
 }
 
-}  // namespace tint::spirv::ir
+}  // namespace tint::spirv
diff --git a/src/tint/lang/spirv/ir/function.h b/src/tint/lang/spirv/builtin_fn.h
similarity index 80%
rename from src/tint/lang/spirv/ir/function.h
rename to src/tint/lang/spirv/builtin_fn.h
index caeb705..e65a27f 100644
--- a/src/tint/lang/spirv/ir/function.h
+++ b/src/tint/lang/spirv/builtin_fn.h
@@ -14,15 +14,15 @@
 
 ////////////////////////////////////////////////////////////////////////////////
 // File generated by 'tools/src/cmd/gen' using the template:
-//   src/tint/lang/spirv/ir/function.h.tmpl
+//   src/tint/lang/spirv/builtin_fn.h.tmpl
 //
 // To regenerate run: './tools/run gen'
 //
 //                       Do not modify this file directly
 ////////////////////////////////////////////////////////////////////////////////
 
-#ifndef SRC_TINT_LANG_SPIRV_IR_FUNCTION_H_
-#define SRC_TINT_LANG_SPIRV_IR_FUNCTION_H_
+#ifndef SRC_TINT_LANG_SPIRV_BUILTIN_FN_H_
+#define SRC_TINT_LANG_SPIRV_BUILTIN_FN_H_
 
 #include <cstdint>
 #include <string>
@@ -30,10 +30,10 @@
 #include "src/tint/utils/traits/traits.h"
 
 // \cond DO_NOT_DOCUMENT
-namespace tint::spirv::ir {
+namespace tint::spirv {
 
 /// Enumerator of all builtin functions
-enum class Function : uint8_t {
+enum class BuiltinFn : uint8_t {
     kArrayLength,
     kAtomicAnd,
     kAtomicCompareExchange,
@@ -55,6 +55,11 @@
     kImageQuerySize,
     kImageQuerySizeLod,
     kImageRead,
+    kImageSampleImplicitLod,
+    kImageSampleExplicitLod,
+    kImageSampleDrefImplicitLod,
+    kImageSampleDrefExplicitLod,
+    kImageWrite,
     kMatrixTimesMatrix,
     kMatrixTimesScalar,
     kMatrixTimesVector,
@@ -67,16 +72,16 @@
 
 /// @returns the name of the builtin function type. The spelling, including
 /// case, matches the name in the WGSL spec.
-const char* str(Function i);
+const char* str(BuiltinFn i);
 
 /// Emits the name of the builtin function type. The spelling, including case,
 /// matches the name in the WGSL spec.
 template <typename STREAM, typename = traits::EnableIfIsOStream<STREAM>>
-auto& operator<<(STREAM& o, Function i) {
+auto& operator<<(STREAM& o, BuiltinFn i) {
     return o << str(i);
 }
 
-}  // namespace tint::spirv::ir
+}  // namespace tint::spirv
 // \endcond
 
-#endif  // SRC_TINT_LANG_SPIRV_IR_FUNCTION_H_
+#endif  // SRC_TINT_LANG_SPIRV_BUILTIN_FN_H_
diff --git a/src/tint/lang/spirv/ir/function.h.tmpl b/src/tint/lang/spirv/builtin_fn.h.tmpl
similarity index 73%
rename from src/tint/lang/spirv/ir/function.h.tmpl
rename to src/tint/lang/spirv/builtin_fn.h.tmpl
index 987568d..94c6bca 100644
--- a/src/tint/lang/spirv/ir/function.h.tmpl
+++ b/src/tint/lang/spirv/builtin_fn.h.tmpl
@@ -1,6 +1,6 @@
 {{- /*
 --------------------------------------------------------------------------------
-Template file for use with tools/src/cmd/gen to generate function.h
+Template file for use with tools/src/cmd/gen to generate builtin_fn.h
 
 To update the generated file, run:
     ./tools/run gen
@@ -13,8 +13,8 @@
 
 {{- $I := LoadIntrinsics "src/tint/lang/spirv/spirv.def" -}}
 
-#ifndef SRC_TINT_LANG_SPIRV_IR_FUNCTION_H_
-#define SRC_TINT_LANG_SPIRV_IR_FUNCTION_H_
+#ifndef SRC_TINT_LANG_SPIRV_BUILTIN_FN_H_
+#define SRC_TINT_LANG_SPIRV_BUILTIN_FN_H_
 
 #include <cstdint>
 #include <string>
@@ -22,10 +22,10 @@
 #include "src/tint/utils/traits/traits.h"
 
 // \cond DO_NOT_DOCUMENT
-namespace tint::spirv::ir {
+namespace tint::spirv {
 
 /// Enumerator of all builtin functions
-enum class Function : uint8_t {
+enum class BuiltinFn : uint8_t {
 {{- range $I.Sem.Builtins }}
     k{{PascalCase .Name}},
 {{- end }}
@@ -34,16 +34,16 @@
 
 /// @returns the name of the builtin function type. The spelling, including
 /// case, matches the name in the WGSL spec.
-const char* str(Function i);
+const char* str(BuiltinFn i);
 
 /// Emits the name of the builtin function type. The spelling, including case,
 /// matches the name in the WGSL spec.
 template <typename STREAM, typename = traits::EnableIfIsOStream<STREAM>>
-auto& operator<<(STREAM& o, Function i) {
+auto& operator<<(STREAM& o, BuiltinFn i) {
   return o << str(i);
 }
 
-}  // namespace tint::spirv::ir
+}  // namespace tint::spirv
 // \endcond
 
-#endif  // SRC_TINT_LANG_SPIRV_IR_FUNCTION_H_
+#endif  // SRC_TINT_LANG_SPIRV_BUILTIN_FN_H_
diff --git a/src/tint/lang/spirv/intrinsic/data/data.cc b/src/tint/lang/spirv/intrinsic/data/data.cc
index e7dcb6f..3ae440d 100644
--- a/src/tint/lang/spirv/intrinsic/data/data.cc
+++ b/src/tint/lang/spirv/intrinsic/data/data.cc
@@ -1095,6 +1095,22 @@
   }
 };
 
+/// EnumMatcher for 'match writable'
+constexpr NumberMatcher kWritableMatcher {
+/* match */ [](MatchState&, Number number) -> Number {
+    switch (static_cast<core::Access>(number.Value())) {
+      case core::Access::kWrite:
+      case core::Access::kReadWrite:
+        return number;
+      default:
+        return Number::invalid;
+    }
+  },
+/* string */ [](MatchState*) -> std::string {
+    return "write or read_write";
+  }
+};
+
 /// Type and number matchers
 
 /// The template types, types, and type matchers
@@ -1163,6 +1179,7 @@
   /* [7] */ kI32TexelFormatMatcher,
   /* [8] */ kU32TexelFormatMatcher,
   /* [9] */ kReadableMatcher,
+  /* [10] */ kWritableMatcher,
 };
 
 constexpr TypeMatcherIndex kTypeMatcherIndices[] = {
@@ -1231,15 +1248,17 @@
   /* [62] */ TypeMatcherIndex(0),
   /* [63] */ TypeMatcherIndex(10),
   /* [64] */ TypeMatcherIndex(0),
-  /* [65] */ TypeMatcherIndex(22),
-  /* [66] */ TypeMatcherIndex(0),
-  /* [67] */ TypeMatcherIndex(21),
-  /* [68] */ TypeMatcherIndex(4),
-  /* [69] */ TypeMatcherIndex(37),
-  /* [70] */ TypeMatcherIndex(38),
-  /* [71] */ TypeMatcherIndex(39),
-  /* [72] */ TypeMatcherIndex(40),
-  /* [73] */ TypeMatcherIndex(41),
+  /* [65] */ TypeMatcherIndex(10),
+  /* [66] */ TypeMatcherIndex(2),
+  /* [67] */ TypeMatcherIndex(22),
+  /* [68] */ TypeMatcherIndex(0),
+  /* [69] */ TypeMatcherIndex(21),
+  /* [70] */ TypeMatcherIndex(4),
+  /* [71] */ TypeMatcherIndex(37),
+  /* [72] */ TypeMatcherIndex(38),
+  /* [73] */ TypeMatcherIndex(39),
+  /* [74] */ TypeMatcherIndex(40),
+  /* [75] */ TypeMatcherIndex(41),
 };
 
 static_assert(TypeMatcherIndex::CanIndex(kTypeMatcherIndices),
@@ -1252,10 +1271,16 @@
   /* [3] */ NumberMatcherIndex(0),
   /* [4] */ NumberMatcherIndex(1),
   /* [5] */ NumberMatcherIndex(2),
-  /* [6] */ NumberMatcherIndex(0),
-  /* [7] */ NumberMatcherIndex(2),
-  /* [8] */ NumberMatcherIndex(1),
-  /* [9] */ NumberMatcherIndex(0),
+  /* [6] */ NumberMatcherIndex(6),
+  /* [7] */ NumberMatcherIndex(10),
+  /* [8] */ NumberMatcherIndex(7),
+  /* [9] */ NumberMatcherIndex(10),
+  /* [10] */ NumberMatcherIndex(8),
+  /* [11] */ NumberMatcherIndex(10),
+  /* [12] */ NumberMatcherIndex(0),
+  /* [13] */ NumberMatcherIndex(2),
+  /* [14] */ NumberMatcherIndex(1),
+  /* [15] */ NumberMatcherIndex(0),
 };
 
 static_assert(NumberMatcherIndex::CanIndex(kNumberMatcherIndices),
@@ -1301,7 +1326,7 @@
   {
     /* [6] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(27),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(6),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
@@ -1313,37 +1338,37 @@
   {
     /* [8] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(2),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [9] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(29),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [10] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(31),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(29),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [11] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(33),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(31),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [12] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(35),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(9),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [13] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(2),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(35),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
@@ -1355,43 +1380,43 @@
   {
     /* [15] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(31),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(29),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [16] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(6),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(29),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [17] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(29),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(31),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [18] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(12),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [19] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(32),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(35),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [20] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(49),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [21] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(9),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(35),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
@@ -1403,49 +1428,49 @@
   {
     /* [23] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(65),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [24] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(32),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(27),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [25] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(51),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(29),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [26] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(0),
-    /* number_matcher_indices */ NumberMatcherIndicesIndex(1),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(26),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [27] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
-    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
-  },
-  {
-    /* [28] */
-    /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
-    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
-  },
-  {
-    /* [29] */
-    /* usage */ core::ParameterUsage::kNone,
     /* type_matcher_indices */ TypeMatcherIndicesIndex(2),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
+    /* [28] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(26),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [29] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(43),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
     /* [30] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(37),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(33),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
@@ -1457,43 +1482,43 @@
   {
     /* [32] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(2),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(26),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [33] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(2),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [34] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(39),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(26),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [35] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(25),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(43),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [36] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(2),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(27),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [37] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(29),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [38] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(4),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(2),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
@@ -1505,235 +1530,235 @@
   {
     /* [40] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(32),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(31),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [41] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(52),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(33),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [42] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(7),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(35),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [43] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(43),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(2),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [44] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(32),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [45] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(52),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(31),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [46] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(10),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(6),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [47] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(45),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(29),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [48] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(32),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [49] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(52),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(32),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [50] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(13),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(49),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [51] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(45),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(9),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [52] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(32),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(35),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [53] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(52),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [54] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(47),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(32),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [55] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(43),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(51),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [56] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(32),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(6),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [57] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(52),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(29),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [58] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(28),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [59] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(43),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(26),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [60] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(2),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(31),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [61] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(32),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(9),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [62] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(34),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(35),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [63] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(45),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [64] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(2),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(26),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [65] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(32),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(31),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [66] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(69),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(12),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [67] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(43),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(35),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [68] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(2),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [69] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(32),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(26),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [70] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(3),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(65),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [71] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(29),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(6),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [72] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(29),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [73] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(32),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [74] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(12),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(29),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [75] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(29),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(31),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [76] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(15),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [77] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(32),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(35),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [78] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(15),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
@@ -1745,115 +1770,115 @@
   {
     /* [80] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(35),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [81] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(32),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(18),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [82] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(18),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [83] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(25),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [84] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(35),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [85] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(32),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(35),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [86] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(70),
-    /* number_matcher_indices */ NumberMatcherIndicesIndex(3),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(27),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [87] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(2),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(29),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [88] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(2),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [89] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(71),
-    /* number_matcher_indices */ NumberMatcherIndicesIndex(3),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(26),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [90] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(61),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(43),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [91] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(33),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [92] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(72),
-    /* number_matcher_indices */ NumberMatcherIndicesIndex(3),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(35),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [93] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(63),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(2),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [94] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(26),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [95] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(73),
-    /* number_matcher_indices */ NumberMatcherIndicesIndex(3),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(43),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [96] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(63),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(27),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [97] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(29),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [98] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(68),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(26),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
@@ -1865,133 +1890,133 @@
   {
     /* [100] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(2),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(43),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [101] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(67),
-    /* number_matcher_indices */ NumberMatcherIndicesIndex(1),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(33),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [102] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(23),
-    /* number_matcher_indices */ NumberMatcherIndicesIndex(1),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(35),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [103] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(23),
-    /* number_matcher_indices */ NumberMatcherIndicesIndex(1),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(26),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [104] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(65),
-    /* number_matcher_indices */ NumberMatcherIndicesIndex(8),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(2),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [105] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(21),
-    /* number_matcher_indices */ NumberMatcherIndicesIndex(0),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(43),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [106] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(2),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(37),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [107] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(7),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(35),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [108] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(26),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [109] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(10),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(2),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [110] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(26),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [111] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(13),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(39),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [112] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [113] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(16),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(26),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [114] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(2),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [115] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(19),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(26),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [116] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
-    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(0),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(1),
   },
   {
     /* [117] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(28),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [118] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(2),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [119] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(34),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(2),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [120] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(2),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(37),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [121] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(38),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(35),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
@@ -2003,53 +2028,1115 @@
   {
     /* [123] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(40),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [124] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(2),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(39),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [125] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(65),
-    /* number_matcher_indices */ NumberMatcherIndicesIndex(6),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(25),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [126] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(65),
-    /* number_matcher_indices */ NumberMatcherIndicesIndex(8),
-  },
-  {
-    /* [127] */
-    /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(65),
-    /* number_matcher_indices */ NumberMatcherIndicesIndex(3),
-  },
-  {
-    /* [128] */
-    /* usage */ core::ParameterUsage::kNone,
     /* type_matcher_indices */ TypeMatcherIndicesIndex(2),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
+    /* [127] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [128] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(4),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
     /* [129] */
     /* usage */ core::ParameterUsage::kNone,
-    /* type_matcher_indices */ TypeMatcherIndicesIndex(65),
-    /* number_matcher_indices */ NumberMatcherIndicesIndex(3),
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
   },
   {
     /* [130] */
     /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(32),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [131] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(52),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [132] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(7),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [133] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(43),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [134] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(32),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [135] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(52),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [136] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(10),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [137] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(45),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [138] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(32),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [139] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(52),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [140] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(13),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [141] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(45),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [142] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(32),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [143] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(52),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [144] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(47),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [145] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(43),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [146] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(32),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [147] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(52),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [148] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(28),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [149] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(43),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [150] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(2),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [151] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(32),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [152] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(34),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [153] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(45),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [154] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(2),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [155] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(32),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [156] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(71),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [157] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(43),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [158] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(2),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [159] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(32),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [160] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(3),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [161] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(29),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [162] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [163] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(32),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [164] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(12),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [165] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(29),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [166] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [167] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(32),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [168] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(15),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [169] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(35),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [170] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [171] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(32),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [172] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(18),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [173] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(25),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [174] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [175] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(32),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [176] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(6),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [177] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(29),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [178] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [179] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(31),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [180] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(9),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [181] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(35),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [182] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [183] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(31),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [184] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(12),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [185] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(35),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [186] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [187] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(65),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [188] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(15),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [189] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(35),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [190] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [191] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(26),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [192] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(18),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [193] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(25),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [194] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [195] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(26),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [196] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(27),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [197] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(29),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [198] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(2),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [199] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(43),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [200] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(33),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [201] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(35),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [202] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(2),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [203] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(43),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [204] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(37),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [205] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(35),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [206] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(2),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [207] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(26),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [208] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(39),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [209] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(25),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [210] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(2),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [211] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(26),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [212] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(72),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(6),
+  },
+  {
+    /* [213] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(2),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [214] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(25),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [215] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [216] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(72),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(8),
+  },
+  {
+    /* [217] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(2),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [218] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(57),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [219] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [220] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(72),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(10),
+  },
+  {
+    /* [221] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(2),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [222] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(59),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [223] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [224] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(73),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(6),
+  },
+  {
+    /* [225] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(61),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [226] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(25),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [227] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [228] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(73),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(8),
+  },
+  {
+    /* [229] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(61),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [230] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(57),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [231] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [232] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(73),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(10),
+  },
+  {
+    /* [233] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(61),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [234] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(59),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [235] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [236] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(74),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(6),
+  },
+  {
+    /* [237] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(63),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [238] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(25),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [239] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [240] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(74),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(8),
+  },
+  {
+    /* [241] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(63),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [242] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(57),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [243] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [244] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(74),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(10),
+  },
+  {
+    /* [245] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(63),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [246] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(59),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [247] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [248] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(75),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(6),
+  },
+  {
+    /* [249] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(63),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [250] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(25),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [251] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [252] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(75),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(8),
+  },
+  {
+    /* [253] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(63),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [254] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(57),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [255] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [256] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(75),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(10),
+  },
+  {
+    /* [257] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(63),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [258] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(59),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [259] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [260] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(72),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(3),
+  },
+  {
+    /* [261] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(2),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [262] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [263] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(73),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(3),
+  },
+  {
+    /* [264] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(61),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [265] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [266] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(74),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(3),
+  },
+  {
+    /* [267] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(63),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [268] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [269] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(75),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(3),
+  },
+  {
+    /* [270] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(63),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [271] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [272] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(3),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [273] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(26),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [274] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [275] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(70),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [276] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(2),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [277] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(2),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [278] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(69),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(1),
+  },
+  {
+    /* [279] */
+    /* usage */ core::ParameterUsage::kNone,
     /* type_matcher_indices */ TypeMatcherIndicesIndex(23),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(1),
   },
   {
-    /* [131] */
+    /* [280] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(23),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(1),
+  },
+  {
+    /* [281] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(67),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(14),
+  },
+  {
+    /* [282] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(21),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(0),
+  },
+  {
+    /* [283] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(2),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [284] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(7),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [285] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [286] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(10),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [287] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [288] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(13),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [289] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [290] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(16),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [291] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [292] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(19),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [293] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(44),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [294] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(28),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [295] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(2),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [296] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(34),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [297] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(2),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [298] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(38),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [299] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(2),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [300] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(40),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [301] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(2),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [302] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(67),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(12),
+  },
+  {
+    /* [303] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(67),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(14),
+  },
+  {
+    /* [304] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(67),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(3),
+  },
+  {
+    /* [305] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(2),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+  },
+  {
+    /* [306] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(67),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(3),
+  },
+  {
+    /* [307] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* type_matcher_indices */ TypeMatcherIndicesIndex(23),
+    /* number_matcher_indices */ NumberMatcherIndicesIndex(1),
+  },
+  {
+    /* [308] */
     /* usage */ core::ParameterUsage::kNone,
     /* type_matcher_indices */ TypeMatcherIndicesIndex(2),
     /* number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
@@ -2122,51 +3209,71 @@
   },
   {
     /* [12] */
-    /* name */ "I",
+    /* name */ "D",
     /* matcher_index */ TypeMatcherIndex(46),
   },
   {
     /* [13] */
-    /* name */ "C",
+    /* name */ "I",
     /* matcher_index */ TypeMatcherIndex(46),
   },
   {
     /* [14] */
-    /* name */ "S",
+    /* name */ "C",
     /* matcher_index */ TypeMatcherIndex(46),
   },
   {
     /* [15] */
-    /* name */ "T",
+    /* name */ "S",
     /* matcher_index */ TypeMatcherIndex(46),
   },
   {
     /* [16] */
-    /* name */ "U",
-    /* matcher_index */ TypeMatcherIndex(8),
-  },
-  {
-    /* [17] */
     /* name */ "T",
     /* matcher_index */ TypeMatcherIndex(47),
   },
   {
+    /* [17] */
+    /* name */ "C",
+    /* matcher_index */ TypeMatcherIndex(46),
+  },
+  {
     /* [18] */
+    /* name */ "D",
+    /* matcher_index */ TypeMatcherIndex(46),
+  },
+  {
+    /* [19] */
+    /* name */ "T",
+    /* matcher_index */ TypeMatcherIndex(46),
+  },
+  {
+    /* [20] */
+    /* name */ "U",
+    /* matcher_index */ TypeMatcherIndex(8),
+  },
+  {
+    /* [21] */
+    /* name */ "T",
+    /* matcher_index */ TypeMatcherIndex(47),
+  },
+  {
+    /* [22] */
     /* name */ "S",
     /* matcher_index */ TypeMatcherIndex(49),
   },
   {
-    /* [19] */
+    /* [23] */
     /* name */ "I",
     /* matcher_index */ TypeMatcherIndex(8),
   },
   {
-    /* [20] */
+    /* [24] */
     /* name */ "T",
     /* matcher_index */ TypeMatcherIndex(45),
   },
   {
-    /* [21] */
+    /* [25] */
     /* name */ "T",
     /* matcher_index */ TypeMatcherIndex(48),
   },
@@ -2255,208 +3362,208 @@
   {
     /* [0] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 1,
-    /* num_template_types */ 1,
+    /* num_parameters */ 3,
+    /* num_template_types */ 2,
     /* num_template_numbers */ 0,
     /* template_types */ TemplateTypeIndex(0),
     /* template_numbers */ TemplateNumberIndex(/* invalid */),
-    /* parameters */ ParameterIndex(38),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(54),
+    /* parameters */ ParameterIndex(272),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
     /* [1] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 1,
-    /* num_template_types */ 1,
+    /* num_parameters */ 3,
+    /* num_template_types */ 2,
     /* num_template_numbers */ 0,
     /* template_types */ TemplateTypeIndex(0),
     /* template_numbers */ TemplateNumberIndex(/* invalid */),
-    /* parameters */ ParameterIndex(42),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(53),
+    /* parameters */ ParameterIndex(6),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
     /* [2] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 1,
-    /* num_template_types */ 1,
+    /* num_parameters */ 4,
+    /* num_template_types */ 2,
     /* num_template_numbers */ 0,
     /* template_types */ TemplateTypeIndex(0),
     /* template_numbers */ TemplateNumberIndex(/* invalid */),
-    /* parameters */ ParameterIndex(46),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(55),
+    /* parameters */ ParameterIndex(56),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
     /* [3] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 1,
-    /* num_template_types */ 1,
+    /* num_parameters */ 5,
+    /* num_template_types */ 3,
     /* num_template_numbers */ 0,
-    /* template_types */ TemplateTypeIndex(0),
+    /* template_types */ TemplateTypeIndex(16),
     /* template_numbers */ TemplateNumberIndex(/* invalid */),
-    /* parameters */ ParameterIndex(50),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(55),
+    /* parameters */ ParameterIndex(56),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
     /* [4] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 1,
-    /* num_template_types */ 1,
+    /* num_parameters */ 4,
+    /* num_template_types */ 3,
     /* num_template_numbers */ 0,
-    /* template_types */ TemplateTypeIndex(0),
+    /* template_types */ TemplateTypeIndex(16),
     /* template_numbers */ TemplateNumberIndex(/* invalid */),
-    /* parameters */ ParameterIndex(113),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(53),
+    /* parameters */ ParameterIndex(176),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
     /* [5] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 1,
-    /* num_template_types */ 1,
+    /* num_parameters */ 3,
+    /* num_template_types */ 2,
     /* num_template_numbers */ 0,
     /* template_types */ TemplateTypeIndex(0),
     /* template_numbers */ TemplateNumberIndex(/* invalid */),
-    /* parameters */ ParameterIndex(115),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(55),
+    /* parameters */ ParameterIndex(12),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
     /* [6] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 1,
-    /* num_template_types */ 1,
+    /* num_parameters */ 4,
+    /* num_template_types */ 2,
     /* num_template_numbers */ 0,
     /* template_types */ TemplateTypeIndex(0),
     /* template_numbers */ TemplateNumberIndex(/* invalid */),
-    /* parameters */ ParameterIndex(54),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(53),
+    /* parameters */ ParameterIndex(61),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
     /* [7] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 1,
-    /* num_template_types */ 0,
+    /* num_parameters */ 4,
+    /* num_template_types */ 3,
     /* num_template_numbers */ 0,
-    /* template_types */ TemplateTypeIndex(/* invalid */),
+    /* template_types */ TemplateTypeIndex(16),
     /* template_numbers */ TemplateNumberIndex(/* invalid */),
-    /* parameters */ ParameterIndex(58),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(53),
+    /* parameters */ ParameterIndex(180),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
     /* [8] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 1,
-    /* num_template_types */ 0,
+    /* num_parameters */ 5,
+    /* num_template_types */ 3,
     /* num_template_numbers */ 0,
-    /* template_types */ TemplateTypeIndex(/* invalid */),
+    /* template_types */ TemplateTypeIndex(16),
     /* template_numbers */ TemplateNumberIndex(/* invalid */),
-    /* parameters */ ParameterIndex(62),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(55),
+    /* parameters */ ParameterIndex(61),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
     /* [9] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 1,
-    /* num_template_types */ 0,
+    /* num_parameters */ 3,
+    /* num_template_types */ 2,
     /* num_template_numbers */ 0,
-    /* template_types */ TemplateTypeIndex(/* invalid */),
+    /* template_types */ TemplateTypeIndex(0),
     /* template_numbers */ TemplateNumberIndex(/* invalid */),
-    /* parameters */ ParameterIndex(121),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(53),
+    /* parameters */ ParameterIndex(18),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
     /* [10] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 1,
-    /* num_template_types */ 0,
+    /* num_parameters */ 4,
+    /* num_template_types */ 2,
     /* num_template_numbers */ 0,
-    /* template_types */ TemplateTypeIndex(/* invalid */),
+    /* template_types */ TemplateTypeIndex(0),
     /* template_numbers */ TemplateNumberIndex(/* invalid */),
-    /* parameters */ ParameterIndex(123),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(55),
+    /* parameters */ ParameterIndex(66),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
     /* [11] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 1,
-    /* num_template_types */ 0,
+    /* num_parameters */ 5,
+    /* num_template_types */ 3,
     /* num_template_numbers */ 0,
-    /* template_types */ TemplateTypeIndex(/* invalid */),
+    /* template_types */ TemplateTypeIndex(16),
     /* template_numbers */ TemplateNumberIndex(/* invalid */),
     /* parameters */ ParameterIndex(66),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(53),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
     /* [12] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 1,
-    /* num_template_types */ 0,
-    /* num_template_numbers */ 2,
-    /* template_types */ TemplateTypeIndex(/* invalid */),
-    /* template_numbers */ TemplateNumberIndex(3),
-    /* parameters */ ParameterIndex(86),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(54),
+    /* num_parameters */ 4,
+    /* num_template_types */ 3,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(16),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(184),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
     /* [13] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 1,
-    /* num_template_types */ 0,
-    /* num_template_numbers */ 2,
-    /* template_types */ TemplateTypeIndex(/* invalid */),
-    /* template_numbers */ TemplateNumberIndex(3),
-    /* parameters */ ParameterIndex(89),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(53),
+    /* num_parameters */ 3,
+    /* num_template_types */ 2,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(0),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(76),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
     /* [14] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 1,
-    /* num_template_types */ 0,
-    /* num_template_numbers */ 2,
-    /* template_types */ TemplateTypeIndex(/* invalid */),
-    /* template_numbers */ TemplateNumberIndex(3),
-    /* parameters */ ParameterIndex(92),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(55),
+    /* num_parameters */ 4,
+    /* num_template_types */ 2,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(0),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(188),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
     /* [15] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 1,
-    /* num_template_types */ 0,
-    /* num_template_numbers */ 2,
-    /* template_types */ TemplateTypeIndex(/* invalid */),
-    /* template_numbers */ TemplateNumberIndex(3),
-    /* parameters */ ParameterIndex(95),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(55),
+    /* num_parameters */ 3,
+    /* num_template_types */ 2,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(0),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(81),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
@@ -2464,64 +3571,64 @@
     /* [16] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* num_parameters */ 4,
-    /* num_template_types */ 3,
+    /* num_template_types */ 2,
     /* num_template_numbers */ 0,
-    /* template_types */ TemplateTypeIndex(4),
+    /* template_types */ TemplateTypeIndex(0),
     /* template_numbers */ TemplateNumberIndex(/* invalid */),
-    /* parameters */ ParameterIndex(70),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(41),
+    /* parameters */ ParameterIndex(192),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
     /* [17] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 4,
-    /* num_template_types */ 3,
+    /* num_parameters */ 3,
+    /* num_template_types */ 1,
     /* num_template_numbers */ 0,
-    /* template_types */ TemplateTypeIndex(4),
+    /* template_types */ TemplateTypeIndex(1),
     /* template_numbers */ TemplateNumberIndex(/* invalid */),
-    /* parameters */ ParameterIndex(16),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(41),
+    /* parameters */ ParameterIndex(36),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
     /* [18] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 5,
-    /* num_template_types */ 3,
+    /* num_parameters */ 4,
+    /* num_template_types */ 2,
     /* num_template_numbers */ 0,
-    /* template_types */ TemplateTypeIndex(4),
+    /* template_types */ TemplateTypeIndex(11),
     /* template_numbers */ TemplateNumberIndex(/* invalid */),
-    /* parameters */ ParameterIndex(16),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(41),
+    /* parameters */ ParameterIndex(196),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
     /* [19] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 4,
-    /* num_template_types */ 3,
+    /* num_parameters */ 3,
+    /* num_template_types */ 1,
     /* num_template_numbers */ 0,
-    /* template_types */ TemplateTypeIndex(4),
+    /* template_types */ TemplateTypeIndex(1),
     /* template_numbers */ TemplateNumberIndex(/* invalid */),
-    /* parameters */ ParameterIndex(21),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(41),
+    /* parameters */ ParameterIndex(120),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
     /* [20] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 5,
-    /* num_template_types */ 4,
+    /* num_parameters */ 3,
+    /* num_template_types */ 1,
     /* num_template_numbers */ 0,
-    /* template_types */ TemplateTypeIndex(4),
+    /* template_types */ TemplateTypeIndex(1),
     /* template_numbers */ TemplateNumberIndex(/* invalid */),
-    /* parameters */ ParameterIndex(21),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(41),
+    /* parameters */ ParameterIndex(41),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
@@ -2529,25 +3636,25 @@
     /* [21] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* num_parameters */ 4,
-    /* num_template_types */ 3,
+    /* num_template_types */ 2,
     /* num_template_numbers */ 0,
-    /* template_types */ TemplateTypeIndex(4),
+    /* template_types */ TemplateTypeIndex(11),
     /* template_numbers */ TemplateNumberIndex(/* invalid */),
-    /* parameters */ ParameterIndex(74),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(41),
+    /* parameters */ ParameterIndex(200),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
     /* [22] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 4,
-    /* num_template_types */ 3,
+    /* num_parameters */ 3,
+    /* num_template_types */ 1,
     /* num_template_numbers */ 0,
-    /* template_types */ TemplateTypeIndex(4),
+    /* template_types */ TemplateTypeIndex(1),
     /* template_numbers */ TemplateNumberIndex(/* invalid */),
-    /* parameters */ ParameterIndex(78),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(41),
+    /* parameters */ ParameterIndex(124),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
@@ -2555,22 +3662,22 @@
     /* [23] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* num_parameters */ 4,
-    /* num_template_types */ 3,
+    /* num_template_types */ 2,
     /* num_template_numbers */ 0,
-    /* template_types */ TemplateTypeIndex(4),
+    /* template_types */ TemplateTypeIndex(0),
     /* template_numbers */ TemplateNumberIndex(/* invalid */),
-    /* parameters */ ParameterIndex(82),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(41),
+    /* parameters */ ParameterIndex(56),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
     /* [24] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 4,
+    /* num_parameters */ 5,
     /* num_template_types */ 2,
     /* num_template_numbers */ 0,
-    /* template_types */ TemplateTypeIndex(5),
+    /* template_types */ TemplateTypeIndex(0),
     /* template_numbers */ TemplateNumberIndex(/* invalid */),
     /* parameters */ ParameterIndex(6),
     /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
@@ -2583,9 +3690,9 @@
     /* num_parameters */ 5,
     /* num_template_types */ 3,
     /* num_template_numbers */ 0,
-    /* template_types */ TemplateTypeIndex(5),
+    /* template_types */ TemplateTypeIndex(16),
     /* template_numbers */ TemplateNumberIndex(/* invalid */),
-    /* parameters */ ParameterIndex(6),
+    /* parameters */ ParameterIndex(56),
     /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
@@ -2593,12 +3700,12 @@
   {
     /* [26] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 4,
-    /* num_template_types */ 2,
+    /* num_parameters */ 5,
+    /* num_template_types */ 3,
     /* num_template_numbers */ 0,
-    /* template_types */ TemplateTypeIndex(5),
+    /* template_types */ TemplateTypeIndex(16),
     /* template_numbers */ TemplateNumberIndex(/* invalid */),
-    /* parameters */ ParameterIndex(11),
+    /* parameters */ ParameterIndex(71),
     /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
@@ -2606,12 +3713,12 @@
   {
     /* [27] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 5,
+    /* num_parameters */ 6,
     /* num_template_types */ 3,
     /* num_template_numbers */ 0,
-    /* template_types */ TemplateTypeIndex(5),
+    /* template_types */ TemplateTypeIndex(16),
     /* template_numbers */ TemplateNumberIndex(/* invalid */),
-    /* parameters */ ParameterIndex(11),
+    /* parameters */ ParameterIndex(6),
     /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
@@ -2622,9 +3729,9 @@
     /* num_parameters */ 4,
     /* num_template_types */ 2,
     /* num_template_numbers */ 0,
-    /* template_types */ TemplateTypeIndex(5),
+    /* template_types */ TemplateTypeIndex(0),
     /* template_numbers */ TemplateNumberIndex(/* invalid */),
-    /* parameters */ ParameterIndex(30),
+    /* parameters */ ParameterIndex(61),
     /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
@@ -2632,12 +3739,12 @@
   {
     /* [29] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 4,
+    /* num_parameters */ 5,
     /* num_template_types */ 2,
     /* num_template_numbers */ 0,
-    /* template_types */ TemplateTypeIndex(5),
+    /* template_types */ TemplateTypeIndex(0),
     /* template_numbers */ TemplateNumberIndex(/* invalid */),
-    /* parameters */ ParameterIndex(34),
+    /* parameters */ ParameterIndex(12),
     /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
@@ -2645,12 +3752,12 @@
   {
     /* [30] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 3,
-    /* num_template_types */ 2,
-    /* num_template_numbers */ 2,
-    /* template_types */ TemplateTypeIndex(7),
-    /* template_numbers */ TemplateNumberIndex(5),
-    /* parameters */ ParameterIndex(86),
+    /* num_parameters */ 5,
+    /* num_template_types */ 3,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(16),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(61),
     /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
@@ -2658,38 +3765,38 @@
   {
     /* [31] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 3,
-    /* num_template_types */ 2,
-    /* num_template_numbers */ 2,
-    /* template_types */ TemplateTypeIndex(7),
-    /* template_numbers */ TemplateNumberIndex(7),
-    /* parameters */ ParameterIndex(86),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(57),
+    /* num_parameters */ 6,
+    /* num_template_types */ 3,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(16),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(12),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
     /* [32] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 3,
+    /* num_parameters */ 4,
     /* num_template_types */ 2,
-    /* num_template_numbers */ 2,
-    /* template_types */ TemplateTypeIndex(7),
-    /* template_numbers */ TemplateNumberIndex(9),
-    /* parameters */ ParameterIndex(86),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(59),
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(0),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(66),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
     /* [33] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 3,
+    /* num_parameters */ 5,
     /* num_template_types */ 2,
-    /* num_template_numbers */ 2,
-    /* template_types */ TemplateTypeIndex(7),
-    /* template_numbers */ TemplateNumberIndex(5),
-    /* parameters */ ParameterIndex(89),
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(0),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(18),
     /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
@@ -2697,38 +3804,38 @@
   {
     /* [34] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 3,
-    /* num_template_types */ 2,
-    /* num_template_numbers */ 2,
-    /* template_types */ TemplateTypeIndex(7),
-    /* template_numbers */ TemplateNumberIndex(7),
-    /* parameters */ ParameterIndex(89),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(57),
+    /* num_parameters */ 5,
+    /* num_template_types */ 3,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(16),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(66),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
     /* [35] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 3,
-    /* num_template_types */ 2,
-    /* num_template_numbers */ 2,
-    /* template_types */ TemplateTypeIndex(7),
-    /* template_numbers */ TemplateNumberIndex(9),
-    /* parameters */ ParameterIndex(89),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(59),
+    /* num_parameters */ 6,
+    /* num_template_types */ 3,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(16),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(18),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
     /* [36] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 3,
+    /* num_parameters */ 4,
     /* num_template_types */ 2,
-    /* num_template_numbers */ 2,
-    /* template_types */ TemplateTypeIndex(7),
-    /* template_numbers */ TemplateNumberIndex(5),
-    /* parameters */ ParameterIndex(92),
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(0),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(188),
     /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
@@ -2736,38 +3843,38 @@
   {
     /* [37] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 3,
+    /* num_parameters */ 5,
     /* num_template_types */ 2,
-    /* num_template_numbers */ 2,
-    /* template_types */ TemplateTypeIndex(7),
-    /* template_numbers */ TemplateNumberIndex(7),
-    /* parameters */ ParameterIndex(92),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(57),
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(0),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(76),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
     /* [38] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 3,
+    /* num_parameters */ 4,
     /* num_template_types */ 2,
-    /* num_template_numbers */ 2,
-    /* template_types */ TemplateTypeIndex(7),
-    /* template_numbers */ TemplateNumberIndex(9),
-    /* parameters */ ParameterIndex(92),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(59),
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(0),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(192),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
     /* [39] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 3,
+    /* num_parameters */ 5,
     /* num_template_types */ 2,
-    /* num_template_numbers */ 2,
-    /* template_types */ TemplateTypeIndex(7),
-    /* template_numbers */ TemplateNumberIndex(5),
-    /* parameters */ ParameterIndex(95),
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(0),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(81),
     /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
@@ -2775,129 +3882,129 @@
   {
     /* [40] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 3,
-    /* num_template_types */ 2,
-    /* num_template_numbers */ 2,
-    /* template_types */ TemplateTypeIndex(7),
-    /* template_numbers */ TemplateNumberIndex(7),
-    /* parameters */ ParameterIndex(95),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(57),
+    /* num_parameters */ 4,
+    /* num_template_types */ 1,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(1),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(86),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
     /* [41] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 3,
+    /* num_parameters */ 5,
     /* num_template_types */ 2,
-    /* num_template_numbers */ 2,
-    /* template_types */ TemplateTypeIndex(7),
-    /* template_numbers */ TemplateNumberIndex(9),
-    /* parameters */ ParameterIndex(95),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(59),
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(11),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(86),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
     /* [42] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 2,
-    /* num_template_types */ 2,
+    /* num_parameters */ 4,
+    /* num_template_types */ 1,
     /* num_template_numbers */ 0,
-    /* template_types */ TemplateTypeIndex(4),
+    /* template_types */ TemplateTypeIndex(1),
     /* template_numbers */ TemplateNumberIndex(/* invalid */),
-    /* parameters */ ParameterIndex(38),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(54),
+    /* parameters */ ParameterIndex(91),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
     /* [43] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 2,
+    /* num_parameters */ 5,
     /* num_template_types */ 2,
     /* num_template_numbers */ 0,
-    /* template_types */ TemplateTypeIndex(4),
+    /* template_types */ TemplateTypeIndex(11),
     /* template_numbers */ TemplateNumberIndex(/* invalid */),
-    /* parameters */ ParameterIndex(107),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(53),
+    /* parameters */ ParameterIndex(91),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
     /* [44] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 2,
-    /* num_template_types */ 2,
+    /* num_parameters */ 4,
+    /* num_template_types */ 1,
     /* num_template_numbers */ 0,
-    /* template_types */ TemplateTypeIndex(4),
+    /* template_types */ TemplateTypeIndex(1),
     /* template_numbers */ TemplateNumberIndex(/* invalid */),
-    /* parameters */ ParameterIndex(109),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(55),
+    /* parameters */ ParameterIndex(204),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
     /* [45] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 2,
-    /* num_template_types */ 2,
+    /* num_parameters */ 4,
+    /* num_template_types */ 1,
     /* num_template_numbers */ 0,
-    /* template_types */ TemplateTypeIndex(4),
+    /* template_types */ TemplateTypeIndex(1),
     /* template_numbers */ TemplateNumberIndex(/* invalid */),
-    /* parameters */ ParameterIndex(111),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(55),
+    /* parameters */ ParameterIndex(208),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
     /* [46] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 2,
-    /* num_template_types */ 2,
+    /* num_parameters */ 1,
+    /* num_template_types */ 1,
     /* num_template_numbers */ 0,
-    /* template_types */ TemplateTypeIndex(4),
+    /* template_types */ TemplateTypeIndex(0),
     /* template_numbers */ TemplateNumberIndex(/* invalid */),
-    /* parameters */ ParameterIndex(113),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(53),
+    /* parameters */ ParameterIndex(128),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(54),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
     /* [47] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 2,
-    /* num_template_types */ 2,
+    /* num_parameters */ 1,
+    /* num_template_types */ 1,
     /* num_template_numbers */ 0,
-    /* template_types */ TemplateTypeIndex(4),
+    /* template_types */ TemplateTypeIndex(0),
     /* template_numbers */ TemplateNumberIndex(/* invalid */),
-    /* parameters */ ParameterIndex(115),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(55),
+    /* parameters */ ParameterIndex(132),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(53),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
     /* [48] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 2,
+    /* num_parameters */ 1,
     /* num_template_types */ 1,
     /* num_template_numbers */ 0,
-    /* template_types */ TemplateTypeIndex(5),
+    /* template_types */ TemplateTypeIndex(0),
     /* template_numbers */ TemplateNumberIndex(/* invalid */),
-    /* parameters */ ParameterIndex(117),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(53),
+    /* parameters */ ParameterIndex(136),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(55),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
     /* [49] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 2,
+    /* num_parameters */ 1,
     /* num_template_types */ 1,
     /* num_template_numbers */ 0,
-    /* template_types */ TemplateTypeIndex(5),
+    /* template_types */ TemplateTypeIndex(0),
     /* template_numbers */ TemplateNumberIndex(/* invalid */),
-    /* parameters */ ParameterIndex(119),
+    /* parameters */ ParameterIndex(140),
     /* return_type_matcher_indices */ TypeMatcherIndicesIndex(55),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
@@ -2905,12 +4012,12 @@
   {
     /* [50] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 2,
+    /* num_parameters */ 1,
     /* num_template_types */ 1,
     /* num_template_numbers */ 0,
-    /* template_types */ TemplateTypeIndex(5),
+    /* template_types */ TemplateTypeIndex(0),
     /* template_numbers */ TemplateNumberIndex(/* invalid */),
-    /* parameters */ ParameterIndex(121),
+    /* parameters */ ParameterIndex(290),
     /* return_type_matcher_indices */ TypeMatcherIndicesIndex(53),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
@@ -2918,12 +4025,12 @@
   {
     /* [51] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 2,
+    /* num_parameters */ 1,
     /* num_template_types */ 1,
     /* num_template_numbers */ 0,
-    /* template_types */ TemplateTypeIndex(5),
+    /* template_types */ TemplateTypeIndex(0),
     /* template_numbers */ TemplateNumberIndex(/* invalid */),
-    /* parameters */ ParameterIndex(123),
+    /* parameters */ ParameterIndex(292),
     /* return_type_matcher_indices */ TypeMatcherIndicesIndex(55),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
@@ -2931,130 +4038,130 @@
   {
     /* [52] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 2,
-    /* num_template_types */ 2,
+    /* num_parameters */ 1,
+    /* num_template_types */ 1,
     /* num_template_numbers */ 0,
-    /* template_types */ TemplateTypeIndex(17),
+    /* template_types */ TemplateTypeIndex(0),
     /* template_numbers */ TemplateNumberIndex(/* invalid */),
-    /* parameters */ ParameterIndex(38),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(3),
+    /* parameters */ ParameterIndex(144),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(53),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
     /* [53] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 2,
-    /* num_template_types */ 2,
+    /* num_parameters */ 1,
+    /* num_template_types */ 0,
     /* num_template_numbers */ 0,
-    /* template_types */ TemplateTypeIndex(17),
+    /* template_types */ TemplateTypeIndex(/* invalid */),
     /* template_numbers */ TemplateNumberIndex(/* invalid */),
-    /* parameters */ ParameterIndex(107),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(6),
+    /* parameters */ ParameterIndex(148),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(53),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
     /* [54] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 2,
-    /* num_template_types */ 2,
+    /* num_parameters */ 1,
+    /* num_template_types */ 0,
     /* num_template_numbers */ 0,
-    /* template_types */ TemplateTypeIndex(17),
+    /* template_types */ TemplateTypeIndex(/* invalid */),
     /* template_numbers */ TemplateNumberIndex(/* invalid */),
-    /* parameters */ ParameterIndex(109),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(9),
+    /* parameters */ ParameterIndex(152),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(55),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
     /* [55] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 2,
-    /* num_template_types */ 2,
+    /* num_parameters */ 1,
+    /* num_template_types */ 0,
     /* num_template_numbers */ 0,
-    /* template_types */ TemplateTypeIndex(17),
+    /* template_types */ TemplateTypeIndex(/* invalid */),
     /* template_numbers */ TemplateNumberIndex(/* invalid */),
-    /* parameters */ ParameterIndex(111),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(12),
+    /* parameters */ ParameterIndex(298),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(53),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
     /* [56] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 2,
-    /* num_template_types */ 2,
+    /* num_parameters */ 1,
+    /* num_template_types */ 0,
     /* num_template_numbers */ 0,
-    /* template_types */ TemplateTypeIndex(17),
+    /* template_types */ TemplateTypeIndex(/* invalid */),
     /* template_numbers */ TemplateNumberIndex(/* invalid */),
-    /* parameters */ ParameterIndex(113),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(15),
+    /* parameters */ ParameterIndex(300),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(55),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
     /* [57] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 2,
-    /* num_template_types */ 2,
+    /* num_parameters */ 1,
+    /* num_template_types */ 0,
     /* num_template_numbers */ 0,
-    /* template_types */ TemplateTypeIndex(17),
+    /* template_types */ TemplateTypeIndex(/* invalid */),
     /* template_numbers */ TemplateNumberIndex(/* invalid */),
-    /* parameters */ ParameterIndex(115),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(18),
+    /* parameters */ ParameterIndex(156),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(53),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
     /* [58] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 2,
-    /* num_template_types */ 1,
-    /* num_template_numbers */ 0,
-    /* template_types */ TemplateTypeIndex(18),
-    /* template_numbers */ TemplateNumberIndex(/* invalid */),
-    /* parameters */ ParameterIndex(117),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(27),
+    /* num_parameters */ 1,
+    /* num_template_types */ 0,
+    /* num_template_numbers */ 2,
+    /* template_types */ TemplateTypeIndex(/* invalid */),
+    /* template_numbers */ TemplateNumberIndex(3),
+    /* parameters */ ParameterIndex(260),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(54),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
     /* [59] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 2,
-    /* num_template_types */ 1,
-    /* num_template_numbers */ 0,
-    /* template_types */ TemplateTypeIndex(18),
-    /* template_numbers */ TemplateNumberIndex(/* invalid */),
-    /* parameters */ ParameterIndex(119),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(33),
+    /* num_parameters */ 1,
+    /* num_template_types */ 0,
+    /* num_template_numbers */ 2,
+    /* template_types */ TemplateTypeIndex(/* invalid */),
+    /* template_numbers */ TemplateNumberIndex(3),
+    /* parameters */ ParameterIndex(263),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(53),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
     /* [60] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 2,
-    /* num_template_types */ 1,
-    /* num_template_numbers */ 0,
-    /* template_types */ TemplateTypeIndex(18),
-    /* template_numbers */ TemplateNumberIndex(/* invalid */),
-    /* parameters */ ParameterIndex(121),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(37),
+    /* num_parameters */ 1,
+    /* num_template_types */ 0,
+    /* num_template_numbers */ 2,
+    /* template_types */ TemplateTypeIndex(/* invalid */),
+    /* template_numbers */ TemplateNumberIndex(3),
+    /* parameters */ ParameterIndex(266),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(55),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
     /* [61] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 2,
-    /* num_template_types */ 1,
-    /* num_template_numbers */ 0,
-    /* template_types */ TemplateTypeIndex(18),
-    /* template_numbers */ TemplateNumberIndex(/* invalid */),
-    /* parameters */ ParameterIndex(123),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(39),
+    /* num_parameters */ 1,
+    /* num_template_types */ 0,
+    /* num_template_numbers */ 2,
+    /* template_types */ TemplateTypeIndex(/* invalid */),
+    /* template_numbers */ TemplateNumberIndex(3),
+    /* parameters */ ParameterIndex(269),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(55),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
@@ -3062,11 +4169,11 @@
     /* [62] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* num_parameters */ 4,
-    /* num_template_types */ 4,
+    /* num_template_types */ 3,
     /* num_template_numbers */ 0,
-    /* template_types */ TemplateTypeIndex(0),
+    /* template_types */ TemplateTypeIndex(4),
     /* template_numbers */ TemplateNumberIndex(/* invalid */),
-    /* parameters */ ParameterIndex(38),
+    /* parameters */ ParameterIndex(160),
     /* return_type_matcher_indices */ TypeMatcherIndicesIndex(41),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
@@ -3075,11 +4182,11 @@
     /* [63] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* num_parameters */ 4,
-    /* num_template_types */ 4,
+    /* num_template_types */ 3,
     /* num_template_numbers */ 0,
-    /* template_types */ TemplateTypeIndex(0),
+    /* template_types */ TemplateTypeIndex(4),
     /* template_numbers */ TemplateNumberIndex(/* invalid */),
-    /* parameters */ ParameterIndex(42),
+    /* parameters */ ParameterIndex(46),
     /* return_type_matcher_indices */ TypeMatcherIndicesIndex(41),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
@@ -3087,10 +4194,10 @@
   {
     /* [64] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 4,
-    /* num_template_types */ 4,
+    /* num_parameters */ 5,
+    /* num_template_types */ 3,
     /* num_template_numbers */ 0,
-    /* template_types */ TemplateTypeIndex(0),
+    /* template_types */ TemplateTypeIndex(4),
     /* template_numbers */ TemplateNumberIndex(/* invalid */),
     /* parameters */ ParameterIndex(46),
     /* return_type_matcher_indices */ TypeMatcherIndicesIndex(41),
@@ -3101,11 +4208,11 @@
     /* [65] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* num_parameters */ 4,
-    /* num_template_types */ 4,
+    /* num_template_types */ 3,
     /* num_template_numbers */ 0,
-    /* template_types */ TemplateTypeIndex(0),
+    /* template_types */ TemplateTypeIndex(4),
     /* template_numbers */ TemplateNumberIndex(/* invalid */),
-    /* parameters */ ParameterIndex(50),
+    /* parameters */ ParameterIndex(51),
     /* return_type_matcher_indices */ TypeMatcherIndicesIndex(41),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
@@ -3113,12 +4220,12 @@
   {
     /* [66] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 4,
+    /* num_parameters */ 5,
     /* num_template_types */ 4,
     /* num_template_numbers */ 0,
-    /* template_types */ TemplateTypeIndex(0),
+    /* template_types */ TemplateTypeIndex(4),
     /* template_numbers */ TemplateNumberIndex(/* invalid */),
-    /* parameters */ ParameterIndex(54),
+    /* parameters */ ParameterIndex(51),
     /* return_type_matcher_indices */ TypeMatcherIndicesIndex(41),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
@@ -3129,10 +4236,10 @@
     /* num_parameters */ 4,
     /* num_template_types */ 3,
     /* num_template_numbers */ 0,
-    /* template_types */ TemplateTypeIndex(12),
+    /* template_types */ TemplateTypeIndex(4),
     /* template_numbers */ TemplateNumberIndex(/* invalid */),
-    /* parameters */ ParameterIndex(58),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
+    /* parameters */ ParameterIndex(164),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(41),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
@@ -3142,10 +4249,10 @@
     /* num_parameters */ 4,
     /* num_template_types */ 3,
     /* num_template_numbers */ 0,
-    /* template_types */ TemplateTypeIndex(12),
+    /* template_types */ TemplateTypeIndex(4),
     /* template_numbers */ TemplateNumberIndex(/* invalid */),
-    /* parameters */ ParameterIndex(62),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
+    /* parameters */ ParameterIndex(168),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(41),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
@@ -3155,10 +4262,10 @@
     /* num_parameters */ 4,
     /* num_template_types */ 3,
     /* num_template_numbers */ 0,
-    /* template_types */ TemplateTypeIndex(12),
+    /* template_types */ TemplateTypeIndex(4),
     /* template_numbers */ TemplateNumberIndex(/* invalid */),
-    /* parameters */ ParameterIndex(66),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
+    /* parameters */ ParameterIndex(172),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(41),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
@@ -3168,9 +4275,9 @@
     /* num_parameters */ 4,
     /* num_template_types */ 2,
     /* num_template_numbers */ 0,
-    /* template_types */ TemplateTypeIndex(9),
+    /* template_types */ TemplateTypeIndex(5),
     /* template_numbers */ TemplateNumberIndex(/* invalid */),
-    /* parameters */ ParameterIndex(6),
+    /* parameters */ ParameterIndex(36),
     /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
@@ -3181,9 +4288,9 @@
     /* num_parameters */ 5,
     /* num_template_types */ 3,
     /* num_template_numbers */ 0,
-    /* template_types */ TemplateTypeIndex(9),
+    /* template_types */ TemplateTypeIndex(5),
     /* template_numbers */ TemplateNumberIndex(/* invalid */),
-    /* parameters */ ParameterIndex(6),
+    /* parameters */ ParameterIndex(36),
     /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
@@ -3194,9 +4301,9 @@
     /* num_parameters */ 4,
     /* num_template_types */ 2,
     /* num_template_numbers */ 0,
-    /* template_types */ TemplateTypeIndex(9),
+    /* template_types */ TemplateTypeIndex(5),
     /* template_numbers */ TemplateNumberIndex(/* invalid */),
-    /* parameters */ ParameterIndex(11),
+    /* parameters */ ParameterIndex(41),
     /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
@@ -3207,9 +4314,9 @@
     /* num_parameters */ 5,
     /* num_template_types */ 3,
     /* num_template_numbers */ 0,
-    /* template_types */ TemplateTypeIndex(9),
+    /* template_types */ TemplateTypeIndex(5),
     /* template_numbers */ TemplateNumberIndex(/* invalid */),
-    /* parameters */ ParameterIndex(11),
+    /* parameters */ ParameterIndex(41),
     /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
@@ -3220,9 +4327,9 @@
     /* num_parameters */ 4,
     /* num_template_types */ 2,
     /* num_template_numbers */ 0,
-    /* template_types */ TemplateTypeIndex(9),
+    /* template_types */ TemplateTypeIndex(5),
     /* template_numbers */ TemplateNumberIndex(/* invalid */),
-    /* parameters */ ParameterIndex(30),
+    /* parameters */ ParameterIndex(120),
     /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
@@ -3233,9 +4340,9 @@
     /* num_parameters */ 4,
     /* num_template_types */ 2,
     /* num_template_numbers */ 0,
-    /* template_types */ TemplateTypeIndex(9),
+    /* template_types */ TemplateTypeIndex(5),
     /* template_numbers */ TemplateNumberIndex(/* invalid */),
-    /* parameters */ ParameterIndex(34),
+    /* parameters */ ParameterIndex(124),
     /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
@@ -3244,12 +4351,12 @@
     /* [76] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* num_parameters */ 3,
-    /* num_template_types */ 1,
-    /* num_template_numbers */ 0,
-    /* template_types */ TemplateTypeIndex(21),
-    /* template_numbers */ TemplateNumberIndex(/* invalid */),
-    /* parameters */ ParameterIndex(98),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(2),
+    /* num_template_types */ 2,
+    /* num_template_numbers */ 2,
+    /* template_types */ TemplateTypeIndex(7),
+    /* template_numbers */ TemplateNumberIndex(5),
+    /* parameters */ ParameterIndex(260),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
@@ -3257,154 +4364,1064 @@
     /* [77] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* num_parameters */ 3,
-    /* num_template_types */ 1,
-    /* num_template_numbers */ 1,
-    /* template_types */ TemplateTypeIndex(21),
-    /* template_numbers */ TemplateNumberIndex(11),
-    /* parameters */ ParameterIndex(101),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(23),
-    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(1),
+    /* num_template_types */ 2,
+    /* num_template_numbers */ 2,
+    /* template_types */ TemplateTypeIndex(7),
+    /* template_numbers */ TemplateNumberIndex(7),
+    /* parameters */ ParameterIndex(260),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(57),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
     /* [78] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 2,
-    /* num_template_types */ 1,
-    /* num_template_numbers */ 1,
-    /* template_types */ TemplateTypeIndex(19),
-    /* template_numbers */ TemplateNumberIndex(4),
-    /* parameters */ ParameterIndex(105),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(54),
+    /* num_parameters */ 3,
+    /* num_template_types */ 2,
+    /* num_template_numbers */ 2,
+    /* template_types */ TemplateTypeIndex(7),
+    /* template_numbers */ TemplateNumberIndex(9),
+    /* parameters */ ParameterIndex(260),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(59),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
     /* [79] */
-    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 4,
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 3,
     /* num_template_types */ 2,
-    /* num_template_numbers */ 1,
-    /* template_types */ TemplateTypeIndex(15),
-    /* template_numbers */ TemplateNumberIndex(13),
-    /* parameters */ ParameterIndex(26),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(2),
+    /* num_template_numbers */ 2,
+    /* template_types */ TemplateTypeIndex(7),
+    /* template_numbers */ TemplateNumberIndex(5),
+    /* parameters */ ParameterIndex(263),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
     /* [80] */
-    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 6,
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 3,
     /* num_template_types */ 2,
-    /* num_template_numbers */ 1,
-    /* template_types */ TemplateTypeIndex(15),
-    /* template_numbers */ TemplateNumberIndex(13),
-    /* parameters */ ParameterIndex(0),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(2),
+    /* num_template_numbers */ 2,
+    /* template_types */ TemplateTypeIndex(7),
+    /* template_numbers */ TemplateNumberIndex(7),
+    /* parameters */ ParameterIndex(263),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(57),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
     /* [81] */
-    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* num_parameters */ 3,
     /* num_template_types */ 2,
-    /* num_template_numbers */ 1,
-    /* template_types */ TemplateTypeIndex(15),
-    /* template_numbers */ TemplateNumberIndex(13),
-    /* parameters */ ParameterIndex(0),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(2),
+    /* num_template_numbers */ 2,
+    /* template_types */ TemplateTypeIndex(7),
+    /* template_numbers */ TemplateNumberIndex(9),
+    /* parameters */ ParameterIndex(263),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(59),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
     /* [82] */
-    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 4,
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 3,
     /* num_template_types */ 2,
-    /* num_template_numbers */ 1,
-    /* template_types */ TemplateTypeIndex(15),
-    /* template_numbers */ TemplateNumberIndex(13),
-    /* parameters */ ParameterIndex(26),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(/* invalid */),
+    /* num_template_numbers */ 2,
+    /* template_types */ TemplateTypeIndex(7),
+    /* template_numbers */ TemplateNumberIndex(5),
+    /* parameters */ ParameterIndex(266),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
     /* [83] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 2,
-    /* num_template_types */ 1,
-    /* num_template_numbers */ 1,
-    /* template_types */ TemplateTypeIndex(20),
-    /* template_numbers */ TemplateNumberIndex(11),
-    /* parameters */ ParameterIndex(102),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(2),
+    /* num_parameters */ 3,
+    /* num_template_types */ 2,
+    /* num_template_numbers */ 2,
+    /* template_types */ TemplateTypeIndex(7),
+    /* template_numbers */ TemplateNumberIndex(7),
+    /* parameters */ ParameterIndex(266),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(57),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
     /* [84] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 2,
-    /* num_template_types */ 1,
-    /* num_template_numbers */ 3,
-    /* template_types */ TemplateTypeIndex(20),
-    /* template_numbers */ TemplateNumberIndex(0),
-    /* parameters */ ParameterIndex(125),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(65),
-    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(4),
+    /* num_parameters */ 3,
+    /* num_template_types */ 2,
+    /* num_template_numbers */ 2,
+    /* template_types */ TemplateTypeIndex(7),
+    /* template_numbers */ TemplateNumberIndex(9),
+    /* parameters */ ParameterIndex(266),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(59),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
     /* [85] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 2,
-    /* num_template_types */ 1,
+    /* num_parameters */ 3,
+    /* num_template_types */ 2,
     /* num_template_numbers */ 2,
-    /* template_types */ TemplateTypeIndex(20),
-    /* template_numbers */ TemplateNumberIndex(11),
-    /* parameters */ ParameterIndex(127),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(65),
-    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(3),
+    /* template_types */ TemplateTypeIndex(7),
+    /* template_numbers */ TemplateNumberIndex(5),
+    /* parameters */ ParameterIndex(269),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
     /* [86] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 2,
-    /* num_template_types */ 1,
+    /* num_parameters */ 3,
+    /* num_template_types */ 2,
     /* num_template_numbers */ 2,
-    /* template_types */ TemplateTypeIndex(20),
-    /* template_numbers */ TemplateNumberIndex(11),
-    /* parameters */ ParameterIndex(129),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(23),
-    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(4),
+    /* template_types */ TemplateTypeIndex(7),
+    /* template_numbers */ TemplateNumberIndex(7),
+    /* parameters */ ParameterIndex(269),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(57),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
     /* [87] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* num_parameters */ 2,
-    /* num_template_types */ 1,
+    /* num_parameters */ 3,
+    /* num_template_types */ 2,
     /* num_template_numbers */ 2,
-    /* template_types */ TemplateTypeIndex(20),
-    /* template_numbers */ TemplateNumberIndex(11),
-    /* parameters */ ParameterIndex(103),
-    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(23),
-    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(4),
+    /* template_types */ TemplateTypeIndex(7),
+    /* template_numbers */ TemplateNumberIndex(9),
+    /* parameters */ ParameterIndex(269),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(59),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
     /* [88] */
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 4,
+    /* num_template_types */ 2,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(11),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(212),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(/* invalid */),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [89] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 4,
+    /* num_template_types */ 2,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(11),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(216),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(/* invalid */),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [90] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 4,
+    /* num_template_types */ 2,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(11),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(220),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(/* invalid */),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [91] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 4,
+    /* num_template_types */ 2,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(11),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(224),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(/* invalid */),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [92] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 4,
+    /* num_template_types */ 2,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(11),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(228),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(/* invalid */),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [93] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 4,
+    /* num_template_types */ 2,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(11),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(232),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(/* invalid */),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [94] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 4,
+    /* num_template_types */ 2,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(11),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(236),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(/* invalid */),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [95] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 4,
+    /* num_template_types */ 2,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(11),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(240),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(/* invalid */),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [96] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 4,
+    /* num_template_types */ 2,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(11),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(244),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(/* invalid */),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [97] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 4,
+    /* num_template_types */ 2,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(11),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(248),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(/* invalid */),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [98] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 4,
+    /* num_template_types */ 2,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(11),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(252),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(/* invalid */),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [99] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 4,
+    /* num_template_types */ 2,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(11),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(256),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(/* invalid */),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [100] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 2,
+    /* num_template_types */ 2,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(4),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(128),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(54),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [101] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 2,
+    /* num_template_types */ 2,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(4),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(284),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(53),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [102] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 2,
+    /* num_template_types */ 2,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(4),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(286),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(55),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [103] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 2,
+    /* num_template_types */ 2,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(4),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(288),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(55),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [104] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 2,
+    /* num_template_types */ 2,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(4),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(290),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(53),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [105] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 2,
+    /* num_template_types */ 2,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(4),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(292),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(55),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [106] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 2,
+    /* num_template_types */ 1,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(5),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(294),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(53),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [107] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 2,
+    /* num_template_types */ 1,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(5),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(296),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(55),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [108] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 2,
+    /* num_template_types */ 1,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(5),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(298),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(53),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [109] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 2,
+    /* num_template_types */ 1,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(5),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(300),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(55),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [110] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 2,
+    /* num_template_types */ 2,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(21),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(128),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(3),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [111] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 2,
+    /* num_template_types */ 2,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(21),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(284),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(6),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [112] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 2,
+    /* num_template_types */ 2,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(21),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(286),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(9),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [113] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 2,
+    /* num_template_types */ 2,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(21),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(288),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(12),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [114] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 2,
+    /* num_template_types */ 2,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(21),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(290),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(15),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [115] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 2,
+    /* num_template_types */ 2,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(21),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(292),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(18),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [116] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 2,
+    /* num_template_types */ 1,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(22),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(294),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(27),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [117] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 2,
+    /* num_template_types */ 1,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(22),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(296),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(33),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [118] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 2,
+    /* num_template_types */ 1,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(22),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(298),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(37),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [119] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 2,
+    /* num_template_types */ 1,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(22),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(300),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(39),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [120] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 4,
+    /* num_template_types */ 4,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(0),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(128),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(41),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [121] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 4,
+    /* num_template_types */ 4,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(0),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(132),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(41),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [122] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 4,
+    /* num_template_types */ 4,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(0),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(136),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(41),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [123] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 4,
+    /* num_template_types */ 4,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(0),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(140),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(41),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [124] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 4,
+    /* num_template_types */ 4,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(0),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(144),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(41),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [125] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 4,
+    /* num_template_types */ 3,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(13),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(148),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [126] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 4,
+    /* num_template_types */ 3,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(13),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(152),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [127] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 4,
+    /* num_template_types */ 3,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(13),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(156),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [128] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 4,
+    /* num_template_types */ 2,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(9),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(36),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [129] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 5,
+    /* num_template_types */ 3,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(9),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(36),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [130] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 4,
+    /* num_template_types */ 2,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(9),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(41),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [131] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 5,
+    /* num_template_types */ 3,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(9),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(41),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [132] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 4,
+    /* num_template_types */ 2,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(9),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(120),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [133] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 4,
+    /* num_template_types */ 2,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(9),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(124),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(25),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [134] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 4,
+    /* num_template_types */ 1,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(1),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(24),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(26),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [135] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 5,
+    /* num_template_types */ 2,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(11),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(96),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(26),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [136] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 4,
+    /* num_template_types */ 1,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(1),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(30),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(26),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [137] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 5,
+    /* num_template_types */ 2,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(11),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(101),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(26),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [138] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 4,
+    /* num_template_types */ 1,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(1),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(106),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(26),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [139] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 4,
+    /* num_template_types */ 1,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(1),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(111),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(26),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [140] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 5,
+    /* num_template_types */ 1,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(1),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(24),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(26),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [141] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 6,
+    /* num_template_types */ 2,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(11),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(24),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(26),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [142] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 5,
+    /* num_template_types */ 1,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(1),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(30),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(26),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [143] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 6,
+    /* num_template_types */ 2,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(11),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(30),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(26),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [144] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 5,
+    /* num_template_types */ 1,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(1),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(106),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(26),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [145] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 5,
+    /* num_template_types */ 1,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(1),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(111),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(26),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [146] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 3,
+    /* num_template_types */ 1,
+    /* num_template_numbers */ 0,
+    /* template_types */ TemplateTypeIndex(25),
+    /* template_numbers */ TemplateNumberIndex(/* invalid */),
+    /* parameters */ ParameterIndex(275),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(2),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [147] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 3,
+    /* num_template_types */ 1,
+    /* num_template_numbers */ 1,
+    /* template_types */ TemplateTypeIndex(25),
+    /* template_numbers */ TemplateNumberIndex(11),
+    /* parameters */ ParameterIndex(278),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(23),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(1),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [148] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* num_parameters */ 2,
     /* num_template_types */ 1,
     /* num_template_numbers */ 1,
-    /* template_types */ TemplateTypeIndex(20),
+    /* template_types */ TemplateTypeIndex(23),
+    /* template_numbers */ TemplateNumberIndex(4),
+    /* parameters */ ParameterIndex(282),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(54),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [149] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 4,
+    /* num_template_types */ 2,
+    /* num_template_numbers */ 1,
+    /* template_types */ TemplateTypeIndex(19),
+    /* template_numbers */ TemplateNumberIndex(13),
+    /* parameters */ ParameterIndex(116),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(2),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [150] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 6,
+    /* num_template_types */ 2,
+    /* num_template_numbers */ 1,
+    /* template_types */ TemplateTypeIndex(19),
+    /* template_numbers */ TemplateNumberIndex(13),
+    /* parameters */ ParameterIndex(0),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(2),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [151] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 3,
+    /* num_template_types */ 2,
+    /* num_template_numbers */ 1,
+    /* template_types */ TemplateTypeIndex(19),
+    /* template_numbers */ TemplateNumberIndex(13),
+    /* parameters */ ParameterIndex(0),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(2),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [152] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 4,
+    /* num_template_types */ 2,
+    /* num_template_numbers */ 1,
+    /* template_types */ TemplateTypeIndex(19),
+    /* template_numbers */ TemplateNumberIndex(13),
+    /* parameters */ ParameterIndex(116),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(/* invalid */),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [153] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 2,
+    /* num_template_types */ 1,
+    /* num_template_numbers */ 1,
+    /* template_types */ TemplateTypeIndex(24),
     /* template_numbers */ TemplateNumberIndex(11),
-    /* parameters */ ParameterIndex(130),
+    /* parameters */ ParameterIndex(279),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(2),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(/* invalid */),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [154] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 2,
+    /* num_template_types */ 1,
+    /* num_template_numbers */ 3,
+    /* template_types */ TemplateTypeIndex(24),
+    /* template_numbers */ TemplateNumberIndex(0),
+    /* parameters */ ParameterIndex(302),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(67),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(4),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [155] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 2,
+    /* num_template_types */ 1,
+    /* num_template_numbers */ 2,
+    /* template_types */ TemplateTypeIndex(24),
+    /* template_numbers */ TemplateNumberIndex(11),
+    /* parameters */ ParameterIndex(304),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(67),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(3),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [156] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 2,
+    /* num_template_types */ 1,
+    /* num_template_numbers */ 2,
+    /* template_types */ TemplateTypeIndex(24),
+    /* template_numbers */ TemplateNumberIndex(11),
+    /* parameters */ ParameterIndex(306),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(23),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(4),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [157] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 2,
+    /* num_template_types */ 1,
+    /* num_template_numbers */ 2,
+    /* template_types */ TemplateTypeIndex(24),
+    /* template_numbers */ TemplateNumberIndex(11),
+    /* parameters */ ParameterIndex(280),
+    /* return_type_matcher_indices */ TypeMatcherIndicesIndex(23),
+    /* return_number_matcher_indices */ NumberMatcherIndicesIndex(4),
+    /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
+  },
+  {
+    /* [158] */
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* num_parameters */ 2,
+    /* num_template_types */ 1,
+    /* num_template_numbers */ 1,
+    /* template_types */ TemplateTypeIndex(24),
+    /* template_numbers */ TemplateNumberIndex(11),
+    /* parameters */ ParameterIndex(307),
     /* return_type_matcher_indices */ TypeMatcherIndicesIndex(23),
     /* return_number_matcher_indices */ NumberMatcherIndicesIndex(1),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
@@ -3419,91 +5436,91 @@
     /* [0] */
     /* fn array_length<I : u32, A : access>(ptr<storage, struct_with_runtime_array, A>, I) -> u32 */
     /* num overloads */ 1,
-    /* overloads */ OverloadIndex(78),
+    /* overloads */ OverloadIndex(148),
   },
   {
     /* [1] */
     /* fn atomic_and<T : iu32, U : u32, S : workgroup_or_storage>(ptr<S, atomic<T>, read_write>, U, U, T) -> T */
     /* num overloads */ 1,
-    /* overloads */ OverloadIndex(79),
+    /* overloads */ OverloadIndex(149),
   },
   {
     /* [2] */
     /* fn atomic_compare_exchange<T : iu32, U : u32, S : workgroup_or_storage>(ptr<S, atomic<T>, read_write>, U, U, U, T, T) -> T */
     /* num overloads */ 1,
-    /* overloads */ OverloadIndex(80),
+    /* overloads */ OverloadIndex(150),
   },
   {
     /* [3] */
     /* fn atomic_exchange<T : iu32, U : u32, S : workgroup_or_storage>(ptr<S, atomic<T>, read_write>, U, U, T) -> T */
     /* num overloads */ 1,
-    /* overloads */ OverloadIndex(79),
+    /* overloads */ OverloadIndex(149),
   },
   {
     /* [4] */
     /* fn atomic_iadd<T : iu32, U : u32, S : workgroup_or_storage>(ptr<S, atomic<T>, read_write>, U, U, T) -> T */
     /* num overloads */ 1,
-    /* overloads */ OverloadIndex(79),
+    /* overloads */ OverloadIndex(149),
   },
   {
     /* [5] */
     /* fn atomic_isub<T : iu32, U : u32, S : workgroup_or_storage>(ptr<S, atomic<T>, read_write>, U, U, T) -> T */
     /* num overloads */ 1,
-    /* overloads */ OverloadIndex(79),
+    /* overloads */ OverloadIndex(149),
   },
   {
     /* [6] */
     /* fn atomic_load<T : iu32, U : u32, S : workgroup_or_storage>(ptr<S, atomic<T>, read_write>, U, U) -> T */
     /* num overloads */ 1,
-    /* overloads */ OverloadIndex(81),
+    /* overloads */ OverloadIndex(151),
   },
   {
     /* [7] */
     /* fn atomic_or<T : iu32, U : u32, S : workgroup_or_storage>(ptr<S, atomic<T>, read_write>, U, U, T) -> T */
     /* num overloads */ 1,
-    /* overloads */ OverloadIndex(79),
+    /* overloads */ OverloadIndex(149),
   },
   {
     /* [8] */
     /* fn atomic_smax<T : iu32, U : u32, S : workgroup_or_storage>(ptr<S, atomic<T>, read_write>, U, U, T) -> T */
     /* num overloads */ 1,
-    /* overloads */ OverloadIndex(79),
+    /* overloads */ OverloadIndex(149),
   },
   {
     /* [9] */
     /* fn atomic_smin<T : iu32, U : u32, S : workgroup_or_storage>(ptr<S, atomic<T>, read_write>, U, U, T) -> T */
     /* num overloads */ 1,
-    /* overloads */ OverloadIndex(79),
+    /* overloads */ OverloadIndex(149),
   },
   {
     /* [10] */
     /* fn atomic_store<T : iu32, U : u32, S : workgroup_or_storage>(ptr<S, atomic<T>, read_write>, U, U, T) */
     /* num overloads */ 1,
-    /* overloads */ OverloadIndex(82),
+    /* overloads */ OverloadIndex(152),
   },
   {
     /* [11] */
     /* fn atomic_umax<T : iu32, U : u32, S : workgroup_or_storage>(ptr<S, atomic<T>, read_write>, U, U, T) -> T */
     /* num overloads */ 1,
-    /* overloads */ OverloadIndex(79),
+    /* overloads */ OverloadIndex(149),
   },
   {
     /* [12] */
     /* fn atomic_umin<T : iu32, U : u32, S : workgroup_or_storage>(ptr<S, atomic<T>, read_write>, U, U, T) -> T */
     /* num overloads */ 1,
-    /* overloads */ OverloadIndex(79),
+    /* overloads */ OverloadIndex(149),
   },
   {
     /* [13] */
     /* fn atomic_xor<T : iu32, U : u32, S : workgroup_or_storage>(ptr<S, atomic<T>, read_write>, U, U, T) -> T */
     /* num overloads */ 1,
-    /* overloads */ OverloadIndex(79),
+    /* overloads */ OverloadIndex(149),
   },
   {
     /* [14] */
     /* fn dot<N : num, T : f32_f16>(vec<N, T>, vec<N, T>) -> T */
     /* num overloads */ 1,
-    /* overloads */ OverloadIndex(83),
+    /* overloads */ OverloadIndex(153),
   },
   {
     /* [15] */
@@ -3514,7 +5531,7 @@
     /* fn image_dref_gather<A : f32, B : iu32>(sampled_image<texture_depth_cube>, vec3<f32>, A, B) -> vec4<f32> */
     /* fn image_dref_gather<A : f32, B : iu32>(sampled_image<texture_depth_cube_array>, vec4<f32>, A, B) -> vec4<f32> */
     /* num overloads */ 6,
-    /* overloads */ OverloadIndex(70),
+    /* overloads */ OverloadIndex(128),
   },
   {
     /* [16] */
@@ -3527,7 +5544,7 @@
     /* fn image_fetch<I : iu32, C : iu32, S : iu32>(texture_depth_2d_array, vec3<C>, I, S) -> vec4<f32> */
     /* fn image_fetch<I : iu32, C : iu32, S : iu32>(texture_depth_multisampled_2d, vec2<C>, I, S) -> vec4<f32> */
     /* num overloads */ 8,
-    /* overloads */ OverloadIndex(62),
+    /* overloads */ OverloadIndex(120),
   },
   {
     /* [17] */
@@ -3546,7 +5563,7 @@
     /* fn image_gather<A : iu32, B : iu32>(sampled_image<texture_depth_cube>, vec3<f32>, A, B) -> vec4<f32> */
     /* fn image_gather<A : iu32, B : iu32>(sampled_image<texture_depth_cube_array>, vec4<f32>, A, B) -> vec4<f32> */
     /* num overloads */ 14,
-    /* overloads */ OverloadIndex(16),
+    /* overloads */ OverloadIndex(62),
   },
   {
     /* [18] */
@@ -3567,7 +5584,7 @@
     /* fn image_query_size<F : texel_format, A : access>(texture_storage_2d_array<F, A>) -> vec3<u32> */
     /* fn image_query_size<F : texel_format, A : access>(texture_storage_3d<F, A>) -> vec3<u32> */
     /* num overloads */ 16,
-    /* overloads */ OverloadIndex(0),
+    /* overloads */ OverloadIndex(46),
   },
   {
     /* [19] */
@@ -3582,7 +5599,7 @@
     /* fn image_query_size_lod<A : iu32>(texture_depth_cube, A) -> vec2<u32> */
     /* fn image_query_size_lod<A : iu32>(texture_depth_cube_array, A) -> vec3<u32> */
     /* num overloads */ 10,
-    /* overloads */ OverloadIndex(42),
+    /* overloads */ OverloadIndex(100),
   },
   {
     /* [20] */
@@ -3599,28 +5616,123 @@
     /* fn image_read<F : i32_texel_format, A : readable, C : iu32, S : iu32>(texture_storage_3d<F, A>, vec3<C>, S) -> vec4<i32> */
     /* fn image_read<F : u32_texel_format, A : readable, C : iu32, S : iu32>(texture_storage_3d<F, A>, vec3<C>, S) -> vec4<u32> */
     /* num overloads */ 12,
-    /* overloads */ OverloadIndex(30),
+    /* overloads */ OverloadIndex(76),
   },
   {
     /* [21] */
-    /* fn matrix_times_matrix<T : f32_f16, K : num, C : num, R : num>(mat<K, R, T>, mat<C, K, T>) -> mat<C, R, T> */
-    /* num overloads */ 1,
-    /* overloads */ OverloadIndex(84),
+    /* fn image_sample_implicit_lod<T : fiu32, C : iu32>(sampled_image<texture_1d<T>>, f32, C) -> vec4<f32> */
+    /* fn image_sample_implicit_lod<T : fiu32, C : iu32>(sampled_image<texture_2d<T>>, vec2<f32>, C) -> vec4<f32> */
+    /* fn image_sample_implicit_lod<T : fiu32, C : iu32>(sampled_image<texture_2d<T>>, vec2<f32>, C, f32) -> vec4<f32> */
+    /* fn image_sample_implicit_lod<T : fiu32, C : iu32, D : iu32>(sampled_image<texture_2d<T>>, vec2<f32>, C, f32, vec2<D>) -> vec4<f32> */
+    /* fn image_sample_implicit_lod<T : fiu32, C : iu32, D : iu32>(sampled_image<texture_2d<T>>, vec2<f32>, C, vec2<D>) -> vec4<f32> */
+    /* fn image_sample_implicit_lod<T : fiu32, C : iu32>(sampled_image<texture_2d_array<T>>, vec3<f32>, C) -> vec4<f32> */
+    /* fn image_sample_implicit_lod<T : fiu32, C : iu32>(sampled_image<texture_2d_array<T>>, vec3<f32>, C, f32) -> vec4<f32> */
+    /* fn image_sample_implicit_lod<T : fiu32, C : iu32, D : iu32>(sampled_image<texture_2d_array<T>>, vec3<f32>, C, vec2<D>) -> vec4<f32> */
+    /* fn image_sample_implicit_lod<T : fiu32, C : iu32, D : iu32>(sampled_image<texture_2d_array<T>>, vec3<f32>, C, f32, vec2<D>) -> vec4<f32> */
+    /* fn image_sample_implicit_lod<T : fiu32, C : iu32>(sampled_image<texture_3d<T>>, vec3<f32>, C) -> vec4<f32> */
+    /* fn image_sample_implicit_lod<T : fiu32, C : iu32>(sampled_image<texture_3d<T>>, vec3<f32>, C, f32) -> vec4<f32> */
+    /* fn image_sample_implicit_lod<T : fiu32, C : iu32, D : iu32>(sampled_image<texture_3d<T>>, vec3<f32>, C, f32, vec3<D>) -> vec4<f32> */
+    /* fn image_sample_implicit_lod<T : fiu32, C : iu32, D : iu32>(sampled_image<texture_3d<T>>, vec3<f32>, C, vec3<D>) -> vec4<f32> */
+    /* fn image_sample_implicit_lod<T : fiu32, C : iu32>(sampled_image<texture_cube<T>>, vec3<f32>, C) -> vec4<f32> */
+    /* fn image_sample_implicit_lod<T : fiu32, C : iu32>(sampled_image<texture_cube<T>>, vec3<f32>, C, f32) -> vec4<f32> */
+    /* fn image_sample_implicit_lod<T : fiu32, C : iu32>(sampled_image<texture_cube_array<T>>, vec4<f32>, C) -> vec4<f32> */
+    /* fn image_sample_implicit_lod<T : fiu32, C : iu32>(sampled_image<texture_cube_array<T>>, vec4<f32>, C, f32) -> vec4<f32> */
+    /* fn image_sample_implicit_lod<C : iu32>(sampled_image<texture_depth_2d>, vec2<f32>, C) -> vec4<f32> */
+    /* fn image_sample_implicit_lod<C : iu32, D : iu32>(sampled_image<texture_depth_2d>, vec2<f32>, C, vec2<D>) -> vec4<f32> */
+    /* fn image_sample_implicit_lod<C : iu32>(sampled_image<texture_depth_cube>, vec3<f32>, C) -> vec4<f32> */
+    /* fn image_sample_implicit_lod<C : iu32>(sampled_image<texture_depth_2d_array>, vec3<f32>, C) -> vec4<f32> */
+    /* fn image_sample_implicit_lod<C : iu32, D : iu32>(sampled_image<texture_depth_2d_array>, vec3<f32>, C, vec2<D>) -> vec4<f32> */
+    /* fn image_sample_implicit_lod<C : iu32>(sampled_image<texture_depth_cube_array>, vec4<f32>, C) -> vec4<f32> */
+    /* num overloads */ 23,
+    /* overloads */ OverloadIndex(0),
   },
   {
     /* [22] */
-    /* fn matrix_times_scalar<T : f32_f16, N : num, M : num>(mat<N, M, T>, T) -> mat<N, M, T> */
-    /* num overloads */ 1,
-    /* overloads */ OverloadIndex(85),
+    /* fn image_sample_explicit_lod<T : fiu32, C : iu32>(sampled_image<texture_2d<T>>, vec2<f32>, C, f32) -> vec4<f32> */
+    /* fn image_sample_explicit_lod<T : fiu32, C : iu32>(sampled_image<texture_2d<T>>, vec2<f32>, C, vec2<f32>, vec2<f32>) -> vec4<f32> */
+    /* fn image_sample_explicit_lod<T : fiu32, C : iu32, D : iu32>(sampled_image<texture_2d<T>>, vec2<f32>, C, f32, vec2<D>) -> vec4<f32> */
+    /* fn image_sample_explicit_lod<T : fiu32, C : iu32, D : iu32>(sampled_image<texture_2d<T>>, vec2<f32>, C, vec2<f32>, vec2<D>) -> vec4<f32> */
+    /* fn image_sample_explicit_lod<T : fiu32, C : iu32, D : iu32>(sampled_image<texture_2d<T>>, vec2<f32>, C, vec2<f32>, vec2<f32>, vec2<D>) -> vec4<f32> */
+    /* fn image_sample_explicit_lod<T : fiu32, C : iu32>(sampled_image<texture_2d_array<T>>, vec3<f32>, C, f32) -> vec4<f32> */
+    /* fn image_sample_explicit_lod<T : fiu32, C : iu32>(sampled_image<texture_2d_array<T>>, vec3<f32>, C, vec2<f32>, vec2<f32>) -> vec4<f32> */
+    /* fn image_sample_explicit_lod<T : fiu32, C : iu32, D : iu32>(sampled_image<texture_2d_array<T>>, vec3<f32>, C, f32, vec2<D>) -> vec4<f32> */
+    /* fn image_sample_explicit_lod<T : fiu32, C : iu32, D : iu32>(sampled_image<texture_2d_array<T>>, vec3<f32>, C, vec2<f32>, vec2<f32>, vec2<D>) -> vec4<f32> */
+    /* fn image_sample_explicit_lod<T : fiu32, C : iu32>(sampled_image<texture_3d<T>>, vec3<f32>, C, f32) -> vec4<f32> */
+    /* fn image_sample_explicit_lod<T : fiu32, C : iu32>(sampled_image<texture_3d<T>>, vec3<f32>, C, vec3<f32>, vec3<f32>) -> vec4<f32> */
+    /* fn image_sample_explicit_lod<T : fiu32, C : iu32, D : iu32>(sampled_image<texture_3d<T>>, vec3<f32>, C, f32, vec3<D>) -> vec4<f32> */
+    /* fn image_sample_explicit_lod<T : fiu32, C : iu32, D : iu32>(sampled_image<texture_3d<T>>, vec3<f32>, C, vec3<f32>, vec3<f32>, vec3<D>) -> vec4<f32> */
+    /* fn image_sample_explicit_lod<T : fiu32, C : iu32>(sampled_image<texture_cube<T>>, vec3<f32>, C, f32) -> vec4<f32> */
+    /* fn image_sample_explicit_lod<T : fiu32, C : iu32>(sampled_image<texture_cube<T>>, vec3<f32>, C, vec3<f32>, vec3<f32>) -> vec4<f32> */
+    /* fn image_sample_explicit_lod<T : fiu32, C : iu32>(sampled_image<texture_cube_array<T>>, vec4<f32>, C, f32) -> vec4<f32> */
+    /* fn image_sample_explicit_lod<T : fiu32, C : iu32>(sampled_image<texture_cube_array<T>>, vec4<f32>, C, vec3<f32>, vec3<f32>) -> vec4<f32> */
+    /* fn image_sample_explicit_lod<C : iu32>(sampled_image<texture_depth_2d>, vec2<f32>, C, f32) -> vec4<f32> */
+    /* fn image_sample_explicit_lod<C : iu32, D : iu32>(sampled_image<texture_depth_2d>, vec2<f32>, C, f32, vec2<D>) -> vec4<f32> */
+    /* fn image_sample_explicit_lod<C : iu32>(sampled_image<texture_depth_2d_array>, vec3<f32>, C, f32) -> vec4<f32> */
+    /* fn image_sample_explicit_lod<C : iu32, D : iu32>(sampled_image<texture_depth_2d_array>, vec3<f32>, C, f32, vec2<D>) -> vec4<f32> */
+    /* fn image_sample_explicit_lod<C : iu32>(sampled_image<texture_depth_cube>, vec3<f32>, C, f32) -> vec4<f32> */
+    /* fn image_sample_explicit_lod<C : iu32>(sampled_image<texture_depth_cube_array>, vec4<f32>, C, f32) -> vec4<f32> */
+    /* num overloads */ 23,
+    /* overloads */ OverloadIndex(23),
   },
   {
     /* [23] */
-    /* fn matrix_times_vector<T : f32_f16, N : num, M : num>(mat<N, M, T>, vec<N, T>) -> vec<M, T> */
-    /* num overloads */ 1,
-    /* overloads */ OverloadIndex(86),
+    /* fn image_sample_dref_implicit_lod<C : iu32>(sampled_image<texture_depth_2d>, vec2<f32>, f32, C) -> f32 */
+    /* fn image_sample_dref_implicit_lod<C : iu32, D : iu32>(sampled_image<texture_depth_2d>, vec2<f32>, f32, C, vec2<D>) -> f32 */
+    /* fn image_sample_dref_implicit_lod<C : iu32>(sampled_image<texture_depth_2d_array>, vec3<f32>, f32, C) -> f32 */
+    /* fn image_sample_dref_implicit_lod<C : iu32, D : iu32>(sampled_image<texture_depth_2d_array>, vec3<f32>, f32, C, vec2<D>) -> f32 */
+    /* fn image_sample_dref_implicit_lod<C : iu32>(sampled_image<texture_depth_cube>, vec3<f32>, f32, C) -> f32 */
+    /* fn image_sample_dref_implicit_lod<C : iu32>(sampled_image<texture_depth_cube_array>, vec4<f32>, f32, C) -> f32 */
+    /* num overloads */ 6,
+    /* overloads */ OverloadIndex(134),
   },
   {
     /* [24] */
+    /* fn image_sample_dref_explicit_lod<C : iu32>(sampled_image<texture_depth_2d>, vec2<f32>, f32, C, f32) -> f32 */
+    /* fn image_sample_dref_explicit_lod<C : iu32, D : iu32>(sampled_image<texture_depth_2d>, vec2<f32>, f32, C, f32, vec2<D>) -> f32 */
+    /* fn image_sample_dref_explicit_lod<C : iu32>(sampled_image<texture_depth_2d_array>, vec3<f32>, f32, C, f32) -> f32 */
+    /* fn image_sample_dref_explicit_lod<C : iu32, D : iu32>(sampled_image<texture_depth_2d_array>, vec3<f32>, f32, C, f32, vec2<D>) -> f32 */
+    /* fn image_sample_dref_explicit_lod<C : iu32>(sampled_image<texture_depth_cube>, vec3<f32>, f32, C, f32) -> f32 */
+    /* fn image_sample_dref_explicit_lod<C : iu32>(sampled_image<texture_depth_cube_array>, vec4<f32>, f32, C, f32) -> f32 */
+    /* num overloads */ 6,
+    /* overloads */ OverloadIndex(140),
+  },
+  {
+    /* [25] */
+    /* fn image_write<C : iu32, D : iu32>(texture_storage_1d<f32_texel_format, writable>, C, vec4<f32>, D) */
+    /* fn image_write<C : iu32, D : iu32>(texture_storage_1d<i32_texel_format, writable>, C, vec4<i32>, D) */
+    /* fn image_write<C : iu32, D : iu32>(texture_storage_1d<u32_texel_format, writable>, C, vec4<u32>, D) */
+    /* fn image_write<C : iu32, D : iu32>(texture_storage_2d<f32_texel_format, writable>, vec2<C>, vec4<f32>, D) */
+    /* fn image_write<C : iu32, D : iu32>(texture_storage_2d<i32_texel_format, writable>, vec2<C>, vec4<i32>, D) */
+    /* fn image_write<C : iu32, D : iu32>(texture_storage_2d<u32_texel_format, writable>, vec2<C>, vec4<u32>, D) */
+    /* fn image_write<C : iu32, D : iu32>(texture_storage_2d_array<f32_texel_format, writable>, vec3<C>, vec4<f32>, D) */
+    /* fn image_write<C : iu32, D : iu32>(texture_storage_2d_array<i32_texel_format, writable>, vec3<C>, vec4<i32>, D) */
+    /* fn image_write<C : iu32, D : iu32>(texture_storage_2d_array<u32_texel_format, writable>, vec3<C>, vec4<u32>, D) */
+    /* fn image_write<C : iu32, D : iu32>(texture_storage_3d<f32_texel_format, writable>, vec3<C>, vec4<f32>, D) */
+    /* fn image_write<C : iu32, D : iu32>(texture_storage_3d<i32_texel_format, writable>, vec3<C>, vec4<i32>, D) */
+    /* fn image_write<C : iu32, D : iu32>(texture_storage_3d<u32_texel_format, writable>, vec3<C>, vec4<u32>, D) */
+    /* num overloads */ 12,
+    /* overloads */ OverloadIndex(88),
+  },
+  {
+    /* [26] */
+    /* fn matrix_times_matrix<T : f32_f16, K : num, C : num, R : num>(mat<K, R, T>, mat<C, K, T>) -> mat<C, R, T> */
+    /* num overloads */ 1,
+    /* overloads */ OverloadIndex(154),
+  },
+  {
+    /* [27] */
+    /* fn matrix_times_scalar<T : f32_f16, N : num, M : num>(mat<N, M, T>, T) -> mat<N, M, T> */
+    /* num overloads */ 1,
+    /* overloads */ OverloadIndex(155),
+  },
+  {
+    /* [28] */
+    /* fn matrix_times_vector<T : f32_f16, N : num, M : num>(mat<N, M, T>, vec<N, T>) -> vec<M, T> */
+    /* num overloads */ 1,
+    /* overloads */ OverloadIndex(156),
+  },
+  {
+    /* [29] */
     /* fn sampled_image<T : fiu32, S : samplers>(texture_1d<T>, S) -> sampled_image<texture_1d<T>> */
     /* fn sampled_image<T : fiu32, S : samplers>(texture_2d<T>, S) -> sampled_image<texture_2d<T>> */
     /* fn sampled_image<T : fiu32, S : samplers>(texture_2d_array<T>, S) -> sampled_image<texture_2d_array<T>> */
@@ -3632,26 +5744,26 @@
     /* fn sampled_image<S : samplers>(texture_depth_cube, S) -> sampled_image<texture_depth_cube> */
     /* fn sampled_image<S : samplers>(texture_depth_cube_array, S) -> sampled_image<texture_depth_cube_array> */
     /* num overloads */ 10,
-    /* overloads */ OverloadIndex(52),
+    /* overloads */ OverloadIndex(110),
   },
   {
-    /* [25] */
+    /* [30] */
     /* fn select<T : scalar>(bool, T, T) -> T */
     /* fn select<N : num, T : scalar>(vec<N, bool>, vec<N, T>, vec<N, T>) -> vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ OverloadIndex(76),
+    /* overloads */ OverloadIndex(146),
   },
   {
-    /* [26] */
+    /* [31] */
     /* fn vector_times_matrix<T : f32_f16, N : num, M : num>(vec<N, T>, mat<M, N, T>) -> vec<M, T> */
     /* num overloads */ 1,
-    /* overloads */ OverloadIndex(87),
+    /* overloads */ OverloadIndex(157),
   },
   {
-    /* [27] */
+    /* [32] */
     /* fn vector_times_scalar<T : f32_f16, N : num>(vec<N, T>, T) -> vec<N, T> */
     /* num overloads */ 1,
-    /* overloads */ OverloadIndex(88),
+    /* overloads */ OverloadIndex(158),
   },
 };
 
diff --git a/src/tint/lang/spirv/ir/BUILD.bazel b/src/tint/lang/spirv/ir/BUILD.bazel
index 6af9b2c..4bc4c29 100644
--- a/src/tint/lang/spirv/ir/BUILD.bazel
+++ b/src/tint/lang/spirv/ir/BUILD.bazel
@@ -27,34 +27,70 @@
   name = "ir",
   srcs = [
     "builtin_call.cc",
-    "function.cc",
-    "intrinsic.cc",
-    "intrinsic_call.cc",
   ],
   hdrs = [
     "builtin_call.h",
-    "function.h",
-    "intrinsic.h",
-    "intrinsic_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/spirv",
     "//src/tint/lang/spirv/intrinsic/data",
     "//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/intrinsic/data",
+    "//src/tint/lang/core/ir",
+    "//src/tint/lang/core/ir:test",
+    "//src/tint/lang/core/type",
+    "//src/tint/lang/spirv",
+    "//src/tint/lang/spirv/intrinsic/data",
+    "//src/tint/lang/spirv/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/spirv/ir/BUILD.cmake b/src/tint/lang/spirv/ir/BUILD.cmake
index 87563f85..120c578 100644
--- a/src/tint/lang/spirv/ir/BUILD.cmake
+++ b/src/tint/lang/spirv/ir/BUILD.cmake
@@ -28,28 +28,67 @@
 tint_add_target(tint_lang_spirv_ir lib
   lang/spirv/ir/builtin_call.cc
   lang/spirv/ir/builtin_call.h
-  lang/spirv/ir/function.cc
-  lang/spirv/ir/function.h
-  lang/spirv/ir/intrinsic.cc
-  lang/spirv/ir/intrinsic.h
-  lang/spirv/ir/intrinsic_call.cc
-  lang/spirv/ir/intrinsic_call.h
 )
 
 tint_target_add_dependencies(tint_lang_spirv_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_spirv
   tint_lang_spirv_intrinsic_data
   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_spirv_ir_test
+# Kind:      test
+################################################################################
+tint_add_target(tint_lang_spirv_ir_test test
+  lang/spirv/ir/builtin_call_test.cc
+)
+
+tint_target_add_dependencies(tint_lang_spirv_ir_test test
+  tint_api_common
+  tint_lang_core
+  tint_lang_core_constant
+  tint_lang_core_intrinsic
+  tint_lang_core_intrinsic_data
+  tint_lang_core_ir
+  tint_lang_core_ir_test
+  tint_lang_core_type
+  tint_lang_spirv
+  tint_lang_spirv_intrinsic_data
+  tint_lang_spirv_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_spirv_ir_test test
+  "gtest"
+)
diff --git a/src/tint/lang/spirv/ir/BUILD.gn b/src/tint/lang/spirv/ir/BUILD.gn
index 97f694d..b558471 100644
--- a/src/tint/lang/spirv/ir/BUILD.gn
+++ b/src/tint/lang/spirv/ir/BUILD.gn
@@ -25,32 +25,69 @@
 
 import("${tint_src_dir}/tint.gni")
 
+if (tint_build_unittests) {
+  import("//testing/test.gni")
+}
+
 libtint_source_set("ir") {
   sources = [
     "builtin_call.cc",
     "builtin_call.h",
-    "function.cc",
-    "function.h",
-    "intrinsic.cc",
-    "intrinsic.h",
-    "intrinsic_call.cc",
-    "intrinsic_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/spirv",
     "${tint_src_dir}/lang/spirv/intrinsic/data",
     "${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") {
+    testonly = true
+    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/intrinsic/data",
+      "${tint_src_dir}/lang/core/ir",
+      "${tint_src_dir}/lang/core/ir:unittests",
+      "${tint_src_dir}/lang/core/type",
+      "${tint_src_dir}/lang/spirv",
+      "${tint_src_dir}/lang/spirv/intrinsic/data",
+      "${tint_src_dir}/lang/spirv/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/spirv/ir/builtin_call.cc b/src/tint/lang/spirv/ir/builtin_call.cc
index 9cf3f08..2e077fa 100644
--- a/src/tint/lang/spirv/ir/builtin_call.cc
+++ b/src/tint/lang/spirv/ir/builtin_call.cc
@@ -16,6 +16,8 @@
 
 #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::spirv::ir::BuiltinCall);
@@ -23,12 +25,18 @@
 namespace tint::spirv::ir {
 
 BuiltinCall::BuiltinCall(core::ir::InstructionResult* result,
-                         Function func,
+                         BuiltinFn func,
                          VectorRef<core::ir::Value*> arguments)
     : Base(result, arguments), func_(func) {
-    TINT_ASSERT(func != Function::kNone);
+    TINT_ASSERT(func != BuiltinFn::kNone);
 }
 
 BuiltinCall::~BuiltinCall() = default;
 
+BuiltinCall* BuiltinCall::Clone(core::ir::CloneContext& ctx) {
+    auto* new_result = ctx.Clone(Result());
+    auto new_args = ctx.Clone<BuiltinCall::kDefaultNumOperands>(Args());
+    return ctx.ir.instructions.Create<BuiltinCall>(new_result, func_, new_args);
+}
+
 }  // namespace tint::spirv::ir
diff --git a/src/tint/lang/spirv/ir/builtin_call.h b/src/tint/lang/spirv/ir/builtin_call.h
index 5380dbe..4ae0ee6 100644
--- a/src/tint/lang/spirv/ir/builtin_call.h
+++ b/src/tint/lang/spirv/ir/builtin_call.h
@@ -19,26 +19,29 @@
 
 #include "src/tint/lang/core/intrinsic/table_data.h"
 #include "src/tint/lang/core/ir/builtin_call.h"
+#include "src/tint/lang/spirv/builtin_fn.h"
 #include "src/tint/lang/spirv/intrinsic/data/data.h"
-#include "src/tint/lang/spirv/ir/function.h"
 #include "src/tint/utils/rtti/castable.h"
 
 namespace tint::spirv::ir {
 
 /// A spirv builtin call instruction in the IR.
-class BuiltinCall : public Castable<BuiltinCall, core::ir::BuiltinCall> {
+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,
-                Function func,
+                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
-    Function Func() { return func_; }
+    BuiltinFn Func() { return func_; }
 
     /// @returns the identifier for the function
     size_t FuncId() override { return static_cast<size_t>(func_); }
@@ -53,7 +56,7 @@
     const core::intrinsic::TableData& TableData() override { return spirv::intrinsic::data::kData; }
 
   private:
-    Function func_;
+    BuiltinFn func_;
 };
 
 }  // namespace tint::spirv::ir
diff --git a/src/tint/lang/spirv/ir/builtin_call_test.cc b/src/tint/lang/spirv/ir/builtin_call_test.cc
new file mode 100644
index 0000000..d10f5f8
--- /dev/null
+++ b/src/tint/lang/spirv/ir/builtin_call_test.cc
@@ -0,0 +1,62 @@
+// Copyright 2023 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/lang/spirv/ir/builtin_call.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest-spi.h"
+#include "src/tint/lang/core/ir/ir_helper_test.h"
+
+namespace tint::spirv::ir {
+namespace {
+
+using namespace tint::core::number_suffixes;  // NOLINT
+                                              //
+using IR_SpirvBuiltinCallTest = core::ir::IRTestHelper;
+
+TEST_F(IR_SpirvBuiltinCallTest, Clone) {
+    auto* builtin = b.Call<BuiltinCall>(mod.Types().f32(), BuiltinFn::kArrayLength, 1_u, 2_u);
+
+    auto* new_b = clone_ctx.Clone(builtin);
+
+    EXPECT_NE(builtin, new_b);
+    EXPECT_NE(builtin->Result(), new_b->Result());
+    EXPECT_EQ(mod.Types().f32(), new_b->Result()->Type());
+
+    EXPECT_EQ(BuiltinFn::kArrayLength, new_b->Func());
+
+    auto args = new_b->Args();
+    EXPECT_EQ(2u, args.Length());
+
+    auto* val0 = args[0]->As<core::ir::Constant>()->Value();
+    EXPECT_EQ(1_u, val0->As<core::constant::Scalar<core::u32>>()->ValueAs<core::u32>());
+
+    auto* val1 = args[1]->As<core::ir::Constant>()->Value();
+    EXPECT_EQ(2_u, val1->As<core::constant::Scalar<core::u32>>()->ValueAs<core::u32>());
+}
+
+TEST_F(IR_SpirvBuiltinCallTest, CloneNoArgs) {
+    auto* builtin = b.Call<BuiltinCall>(mod.Types().f32(), BuiltinFn::kArrayLength);
+
+    auto* new_b = clone_ctx.Clone(builtin);
+    EXPECT_NE(builtin->Result(), new_b->Result());
+    EXPECT_EQ(mod.Types().f32(), new_b->Result()->Type());
+
+    EXPECT_EQ(BuiltinFn::kArrayLength, new_b->Func());
+
+    auto args = new_b->Args();
+    EXPECT_TRUE(args.IsEmpty());
+}
+
+}  // namespace
+}  // namespace tint::spirv::ir
diff --git a/src/tint/lang/spirv/ir/function.cc b/src/tint/lang/spirv/ir/function.cc
deleted file mode 100644
index bb86b6c..0000000
--- a/src/tint/lang/spirv/ir/function.cc
+++ /dev/null
@@ -1,92 +0,0 @@
-// Copyright 2023 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-////////////////////////////////////////////////////////////////////////////////
-// File generated by 'tools/src/cmd/gen' using the template:
-//   src/tint/lang/spirv/ir/function.cc.tmpl
-//
-// To regenerate run: './tools/run gen'
-//
-//                       Do not modify this file directly
-////////////////////////////////////////////////////////////////////////////////
-
-#include "src/tint/lang/spirv/ir/function.h"
-
-namespace tint::spirv::ir {
-
-const char* str(Function i) {
-    switch (i) {
-        case Function::kNone:
-            return "<none>";
-        case Function::kArrayLength:
-            return "spirv.array_length";
-        case Function::kAtomicAnd:
-            return "spirv.atomic_and";
-        case Function::kAtomicCompareExchange:
-            return "spirv.atomic_compare_exchange";
-        case Function::kAtomicExchange:
-            return "spirv.atomic_exchange";
-        case Function::kAtomicIadd:
-            return "spirv.atomic_iadd";
-        case Function::kAtomicIsub:
-            return "spirv.atomic_isub";
-        case Function::kAtomicLoad:
-            return "spirv.atomic_load";
-        case Function::kAtomicOr:
-            return "spirv.atomic_or";
-        case Function::kAtomicSmax:
-            return "spirv.atomic_smax";
-        case Function::kAtomicSmin:
-            return "spirv.atomic_smin";
-        case Function::kAtomicStore:
-            return "spirv.atomic_store";
-        case Function::kAtomicUmax:
-            return "spirv.atomic_umax";
-        case Function::kAtomicUmin:
-            return "spirv.atomic_umin";
-        case Function::kAtomicXor:
-            return "spirv.atomic_xor";
-        case Function::kDot:
-            return "spirv.dot";
-        case Function::kImageDrefGather:
-            return "spirv.image_dref_gather";
-        case Function::kImageFetch:
-            return "spirv.image_fetch";
-        case Function::kImageGather:
-            return "spirv.image_gather";
-        case Function::kImageQuerySize:
-            return "spirv.image_query_size";
-        case Function::kImageQuerySizeLod:
-            return "spirv.image_query_size_lod";
-        case Function::kImageRead:
-            return "spirv.image_read";
-        case Function::kMatrixTimesMatrix:
-            return "spirv.matrix_times_matrix";
-        case Function::kMatrixTimesScalar:
-            return "spirv.matrix_times_scalar";
-        case Function::kMatrixTimesVector:
-            return "spirv.matrix_times_vector";
-        case Function::kSampledImage:
-            return "spirv.sampled_image";
-        case Function::kSelect:
-            return "spirv.select";
-        case Function::kVectorTimesMatrix:
-            return "spirv.vector_times_matrix";
-        case Function::kVectorTimesScalar:
-            return "spirv.vector_times_scalar";
-    }
-    return "<unknown>";
-}
-
-}  // namespace tint::spirv::ir
diff --git a/src/tint/lang/spirv/ir/intrinsic.cc b/src/tint/lang/spirv/ir/intrinsic.cc
deleted file mode 100644
index 3146175..0000000
--- a/src/tint/lang/spirv/ir/intrinsic.cc
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright 2023 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-////////////////////////////////////////////////////////////////////////////////
-// File generated by 'tools/src/cmd/gen' using the template:
-//   src/tint/lang/spirv/ir/intrinsic.cc.tmpl
-//
-// To regenerate run: './tools/run gen'
-//
-//                       Do not modify this file directly
-////////////////////////////////////////////////////////////////////////////////
-
-#include "src/tint/lang/spirv/ir/intrinsic.h"
-
-namespace tint::spirv::ir {
-
-/// ParseIntrinsic parses a Intrinsic from a string.
-/// @param str the string to parse
-/// @returns the parsed enum, or Intrinsic::kUndefined if the string could not be parsed.
-Intrinsic ParseIntrinsic(std::string_view str) {
-    if (str == "image_sample_dref_explicit_lod") {
-        return Intrinsic::kImageSampleDrefExplicitLod;
-    }
-    if (str == "image_sample_dref_implicit_lod") {
-        return Intrinsic::kImageSampleDrefImplicitLod;
-    }
-    if (str == "image_sample_explicit_lod") {
-        return Intrinsic::kImageSampleExplicitLod;
-    }
-    if (str == "image_sample_implicit_lod") {
-        return Intrinsic::kImageSampleImplicitLod;
-    }
-    if (str == "image_write") {
-        return Intrinsic::kImageWrite;
-    }
-    return Intrinsic::kUndefined;
-}
-
-std::string_view ToString(Intrinsic value) {
-    switch (value) {
-        case Intrinsic::kUndefined:
-            return "undefined";
-        case Intrinsic::kImageSampleDrefExplicitLod:
-            return "image_sample_dref_explicit_lod";
-        case Intrinsic::kImageSampleDrefImplicitLod:
-            return "image_sample_dref_implicit_lod";
-        case Intrinsic::kImageSampleExplicitLod:
-            return "image_sample_explicit_lod";
-        case Intrinsic::kImageSampleImplicitLod:
-            return "image_sample_implicit_lod";
-        case Intrinsic::kImageWrite:
-            return "image_write";
-    }
-    return "<unknown>";
-}
-
-}  // namespace tint::spirv::ir
diff --git a/src/tint/lang/spirv/ir/intrinsic.cc.tmpl b/src/tint/lang/spirv/ir/intrinsic.cc.tmpl
deleted file mode 100644
index 4928c77..0000000
--- a/src/tint/lang/spirv/ir/intrinsic.cc.tmpl
+++ /dev/null
@@ -1,27 +0,0 @@
-{{- /*
---------------------------------------------------------------------------------
-Template file for use with tools/src/cmd/gen to generate intrinsic.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/spirv/spirv.def" -}}
-{{- Import "src/tint/utils/templates/enums.tmpl.inc" -}}
-{{- $enum := ($I.Sem.Enum "intrinsic") -}}
-
-#include "src/tint/lang/spirv/ir/intrinsic.h"
-
-namespace tint::spirv::ir {
-
-{{ Eval "ParseEnum" $enum}}
-
-{{ Eval "EnumOStream" $enum}}
-
-}  // namespace tint::spirv::ir
-
diff --git a/src/tint/lang/spirv/ir/intrinsic.h b/src/tint/lang/spirv/ir/intrinsic.h
deleted file mode 100644
index 0404fba..0000000
--- a/src/tint/lang/spirv/ir/intrinsic.h
+++ /dev/null
@@ -1,71 +0,0 @@
-// Copyright 2023 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-////////////////////////////////////////////////////////////////////////////////
-// File generated by 'tools/src/cmd/gen' using the template:
-//   src/tint/lang/spirv/ir/intrinsic.h.tmpl
-//
-// To regenerate run: './tools/run gen'
-//
-//                       Do not modify this file directly
-////////////////////////////////////////////////////////////////////////////////
-
-#ifndef SRC_TINT_LANG_SPIRV_IR_INTRINSIC_H_
-#define SRC_TINT_LANG_SPIRV_IR_INTRINSIC_H_
-
-#include <cstdint>
-#include <string>
-
-#include "src/tint/utils/traits/traits.h"
-
-namespace tint::spirv::ir {
-
-/// Intrinsic
-enum class Intrinsic : uint8_t {
-    kUndefined,
-    kImageSampleDrefExplicitLod,
-    kImageSampleDrefImplicitLod,
-    kImageSampleExplicitLod,
-    kImageSampleImplicitLod,
-    kImageWrite,
-};
-
-/// @param value the enum value
-/// @returns the string for the given enum value
-std::string_view ToString(Intrinsic value);
-
-/// @param out the stream to write to
-/// @param value the Intrinsic
-/// @returns @p out so calls can be chained
-template <typename STREAM, typename = traits::EnableIfIsOStream<STREAM>>
-auto& operator<<(STREAM& out, Intrinsic value) {
-    return out << ToString(value);
-}
-
-/// ParseIntrinsic parses a Intrinsic from a string.
-/// @param str the string to parse
-/// @returns the parsed enum, or Intrinsic::kUndefined if the string could not be parsed.
-Intrinsic ParseIntrinsic(std::string_view str);
-
-constexpr const char* kIntrinsicStrings[] = {
-    "image_sample_dref_explicit_lod",
-    "image_sample_dref_implicit_lod",
-    "image_sample_explicit_lod",
-    "image_sample_implicit_lod",
-    "image_write",
-};
-
-}  // namespace tint::spirv::ir
-
-#endif  // SRC_TINT_LANG_SPIRV_IR_INTRINSIC_H_
diff --git a/src/tint/lang/spirv/ir/intrinsic.h.tmpl b/src/tint/lang/spirv/ir/intrinsic.h.tmpl
deleted file mode 100644
index 6bd3e67..0000000
--- a/src/tint/lang/spirv/ir/intrinsic.h.tmpl
+++ /dev/null
@@ -1,34 +0,0 @@
-{{- /*
---------------------------------------------------------------------------------
-Template file for use with tools/src/cmd/gen to generate intrinsics.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/spirv/spirv.def" -}}
-{{- Import "src/tint/utils/templates/enums.tmpl.inc" -}}
-{{- $enum := ($I.Sem.Enum "intrinsic") -}}
-
-#ifndef SRC_TINT_LANG_SPIRV_IR_INTRINSIC_H_
-#define SRC_TINT_LANG_SPIRV_IR_INTRINSIC_H_
-
-#include <cstdint>
-#include <string>
-
-#include "src/tint/utils/traits/traits.h"
-
-namespace tint::spirv::ir {
-
-/// Intrinsic
-{{ Eval "DeclareEnum" $enum }}
-
-}  // namespace tint::spirv::ir
-
-#endif  // SRC_TINT_LANG_SPIRV_IR_INTRINSIC_H_
-
diff --git a/src/tint/lang/spirv/ir/intrinsic_call.cc b/src/tint/lang/spirv/ir/intrinsic_call.cc
deleted file mode 100644
index 5f2f1ba..0000000
--- a/src/tint/lang/spirv/ir/intrinsic_call.cc
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2023 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/tint/lang/spirv/ir/intrinsic_call.h"
-
-#include <utility>
-
-TINT_INSTANTIATE_TYPEINFO(tint::spirv::ir::IntrinsicCall);
-
-namespace tint::spirv::ir {
-
-IntrinsicCall::IntrinsicCall(core::ir::InstructionResult* result,
-                             Intrinsic intrinsic,
-                             VectorRef<core::ir::Value*> arguments)
-    : Base(result, arguments), intrinsic_(intrinsic) {}
-
-IntrinsicCall::~IntrinsicCall() = default;
-
-}  // namespace tint::spirv::ir
diff --git a/src/tint/lang/spirv/ir/intrinsic_call.h b/src/tint/lang/spirv/ir/intrinsic_call.h
deleted file mode 100644
index d7c2d43..0000000
--- a/src/tint/lang/spirv/ir/intrinsic_call.h
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright 2023 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_TINT_LANG_SPIRV_IR_INTRINSIC_CALL_H_
-#define SRC_TINT_LANG_SPIRV_IR_INTRINSIC_CALL_H_
-
-#include <string>
-
-#include "src/tint/lang/core/ir/intrinsic_call.h"
-#include "src/tint/lang/spirv/ir/intrinsic.h"
-#include "src/tint/utils/rtti/castable.h"
-
-namespace tint::spirv::ir {
-
-/// A spir-v intrinsic call instruction in the IR.
-class IntrinsicCall : public Castable<IntrinsicCall, core::ir::IntrinsicCall> {
-  public:
-    /// Constructor
-    /// @param result the result value
-    /// @param intrinsic the kind of intrinsic
-    /// @param args the intrinsic call arguments
-    IntrinsicCall(core::ir::InstructionResult* result,
-                  Intrinsic intrinsic,
-                  VectorRef<core::ir::Value*> args = tint::Empty);
-    ~IntrinsicCall() override;
-
-    /// @returns the kind of the intrinsic
-    Intrinsic Kind() const { return intrinsic_; }
-
-    /// @returns the friendly name for the instruction
-    std::string FriendlyName() override { return "spirv." + std::string(ToString(intrinsic_)); }
-
-  private:
-    Intrinsic intrinsic_ = Intrinsic::kUndefined;
-};
-
-}  // namespace tint::spirv::ir
-
-#endif  // SRC_TINT_LANG_SPIRV_IR_INTRINSIC_CALL_H_
diff --git a/src/tint/lang/spirv/reader/ast_lower/atomics.cc b/src/tint/lang/spirv/reader/ast_lower/atomics.cc
index bd2aee4..17ad8a2 100644
--- a/src/tint/lang/spirv/reader/ast_lower/atomics.cc
+++ b/src/tint/lang/spirv/reader/ast_lower/atomics.cc
@@ -53,11 +53,11 @@
     };
 
     /// The source program
-    const Program* const src;
+    const Program& src;
     /// The target program builder
     ProgramBuilder b;
     /// The clone context
-    program::CloneContext ctx = {&b, src, /* auto_clone_symbols */ true};
+    program::CloneContext ctx = {&b, &src, /* auto_clone_symbols */ true};
     std::unordered_map<const core::type::Struct*, ForkedStruct> forked_structs;
     std::unordered_set<const sem::Variable*> atomic_variables;
     UniqueVector<const sem::ValueExpression*, 8> atomic_expressions;
@@ -65,7 +65,7 @@
   public:
     /// Constructor
     /// @param program the source program
-    explicit State(const Program* program) : src(program) {}
+    explicit State(const Program& program) : src(program) {}
 
     /// Runs the transform
     /// @returns the new program or SkipTransform if the transform is not required
@@ -87,7 +87,7 @@
                     out_args[0] = b.AddressOf(out_args[0]);
 
                     // Replace all callsites of this stub to a call to the real builtin
-                    if (stub->builtin == core::Function::kAtomicCompareExchangeWeak) {
+                    if (stub->builtin == core::BuiltinFn::kAtomicCompareExchangeWeak) {
                         // atomicCompareExchangeWeak returns a struct, so insert a call to it above
                         // the current statement, and replace the current call with the struct's
                         // `old_value` member.
@@ -255,7 +255,7 @@
                 if (is_ref_to_atomic_var(load->Reference())) {
                     ctx.Replace(load->Reference()->Declaration(), [=] {
                         auto* expr = ctx.CloneWithoutTransform(load->Reference()->Declaration());
-                        return b.Call(core::str(core::Function::kAtomicLoad), b.AddressOf(expr));
+                        return b.Call(core::str(core::BuiltinFn::kAtomicLoad), b.AddressOf(expr));
                     });
                 }
             } else if (auto* assign = node->As<ast::AssignmentStatement>()) {
@@ -265,7 +265,7 @@
                         auto* lhs = ctx.CloneWithoutTransform(assign->lhs);
                         auto* rhs = ctx.CloneWithoutTransform(assign->rhs);
                         auto* call =
-                            b.Call(core::str(core::Function::kAtomicStore), b.AddressOf(lhs), rhs);
+                            b.Call(core::str(core::BuiltinFn::kAtomicStore), b.AddressOf(lhs), rhs);
                         return b.CallStmt(call);
                     });
                 }
@@ -277,7 +277,7 @@
 Atomics::Atomics() = default;
 Atomics::~Atomics() = default;
 
-Atomics::Stub::Stub(GenerationID pid, ast::NodeID nid, core::Function b)
+Atomics::Stub::Stub(GenerationID pid, ast::NodeID nid, core::BuiltinFn b)
     : Base(pid, nid, tint::Empty), builtin(b) {}
 Atomics::Stub::~Stub() = default;
 std::string Atomics::Stub::InternalName() const {
@@ -289,7 +289,7 @@
                                                      builtin);
 }
 
-ast::transform::Transform::ApplyResult Atomics::Apply(const Program* src,
+ast::transform::Transform::ApplyResult Atomics::Apply(const Program& src,
                                                       const ast::transform::DataMap&,
                                                       ast::transform::DataMap&) const {
     return State{src}.Run();
diff --git a/src/tint/lang/spirv/reader/ast_lower/atomics.h b/src/tint/lang/spirv/reader/ast_lower/atomics.h
index e349095..c74d16d 100644
--- a/src/tint/lang/spirv/reader/ast_lower/atomics.h
+++ b/src/tint/lang/spirv/reader/ast_lower/atomics.h
@@ -17,7 +17,7 @@
 
 #include <string>
 
-#include "src/tint/lang/core/function.h"
+#include "src/tint/lang/core/builtin_fn.h"
 #include "src/tint/lang/wgsl/ast/internal_attribute.h"
 #include "src/tint/lang/wgsl/ast/transform/transform.h"
 
@@ -41,7 +41,7 @@
         /// @param pid the identifier of the program that owns this node
         /// @param nid the unique node identifier
         /// @param builtin the atomic builtin this stub represents
-        Stub(GenerationID pid, ast::NodeID nid, core::Function builtin);
+        Stub(GenerationID pid, ast::NodeID nid, core::BuiltinFn builtin);
         /// Destructor
         ~Stub() override;
 
@@ -55,11 +55,11 @@
         const Stub* Clone(ast::CloneContext& ctx) const override;
 
         /// The type of the intrinsic
-        const core::Function builtin;
+        const core::BuiltinFn builtin;
     };
 
     /// @copydoc ast::transform::Transform::Apply
-    ApplyResult Apply(const Program* program,
+    ApplyResult Apply(const Program& program,
                       const ast::transform::DataMap& inputs,
                       ast::transform::DataMap& outputs) const override;
 
diff --git a/src/tint/lang/spirv/reader/ast_lower/atomics_test.cc b/src/tint/lang/spirv/reader/ast_lower/atomics_test.cc
index 8898c52..6f67a21 100644
--- a/src/tint/lang/spirv/reader/ast_lower/atomics_test.cc
+++ b/src/tint/lang/spirv/reader/ast_lower/atomics_test.cc
@@ -37,10 +37,11 @@
 
         auto& b = parser.builder();
 
-        core::Function two_params[] = {
-            core::Function::kAtomicExchange, core::Function::kAtomicAdd, core::Function::kAtomicSub,
-            core::Function::kAtomicMin,      core::Function::kAtomicMax, core::Function::kAtomicAnd,
-            core::Function::kAtomicOr,       core::Function::kAtomicXor,
+        core::BuiltinFn two_params[] = {
+            core::BuiltinFn::kAtomicExchange, core::BuiltinFn::kAtomicAdd,
+            core::BuiltinFn::kAtomicSub,      core::BuiltinFn::kAtomicMin,
+            core::BuiltinFn::kAtomicMax,      core::BuiltinFn::kAtomicAnd,
+            core::BuiltinFn::kAtomicOr,       core::BuiltinFn::kAtomicXor,
         };
         for (auto& a : two_params) {
             b.Func(std::string{"stub_"} + core::str(a) + "_u32",
@@ -79,7 +80,7 @@
                },
                tint::Vector{
                    b.ASTNodes().Create<Atomics::Stub>(b.ID(), b.AllocateNodeID(),
-                                                      core::Function::kAtomicLoad),
+                                                      core::BuiltinFn::kAtomicLoad),
                });
         b.Func("stub_atomicLoad_i32",
                tint::Vector{
@@ -91,7 +92,7 @@
                },
                tint::Vector{
                    b.ASTNodes().Create<Atomics::Stub>(b.ID(), b.AllocateNodeID(),
-                                                      core::Function::kAtomicLoad),
+                                                      core::BuiltinFn::kAtomicLoad),
                });
 
         b.Func("stub_atomicStore_u32",
@@ -102,7 +103,7 @@
                b.ty.void_(), tint::Empty,
                tint::Vector{
                    b.ASTNodes().Create<Atomics::Stub>(b.ID(), b.AllocateNodeID(),
-                                                      core::Function::kAtomicStore),
+                                                      core::BuiltinFn::kAtomicStore),
                });
         b.Func("stub_atomicStore_i32",
                tint::Vector{
@@ -112,7 +113,7 @@
                b.ty.void_(), tint::Empty,
                tint::Vector{
                    b.ASTNodes().Create<Atomics::Stub>(b.ID(), b.AllocateNodeID(),
-                                                      core::Function::kAtomicStore),
+                                                      core::BuiltinFn::kAtomicStore),
                });
 
         b.Func("stub_atomic_compare_exchange_weak_u32",
@@ -127,7 +128,7 @@
                },
                tint::Vector{
                    b.ASTNodes().Create<Atomics::Stub>(b.ID(), b.AllocateNodeID(),
-                                                      core::Function::kAtomicCompareExchangeWeak),
+                                                      core::BuiltinFn::kAtomicCompareExchangeWeak),
                });
         b.Func("stub_atomic_compare_exchange_weak_i32",
                tint::Vector{b.Param("p0", b.ty.i32()), b.Param("p1", b.ty.i32()),
@@ -138,7 +139,7 @@
                },
                tint::Vector{
                    b.ASTNodes().Create<Atomics::Stub>(b.ID(), b.AllocateNodeID(),
-                                                      core::Function::kAtomicCompareExchangeWeak),
+                                                      core::BuiltinFn::kAtomicCompareExchangeWeak),
                });
 
         // Keep this pointer alive after Transform() returns
diff --git a/src/tint/lang/spirv/reader/ast_lower/decompose_strided_array.cc b/src/tint/lang/spirv/reader/ast_lower/decompose_strided_array.cc
index 1a2ac5e..586eb49 100644
--- a/src/tint/lang/spirv/reader/ast_lower/decompose_strided_array.cc
+++ b/src/tint/lang/spirv/reader/ast_lower/decompose_strided_array.cc
@@ -40,8 +40,8 @@
 
 using DecomposedArrays = std::unordered_map<const core::type::Array*, Symbol>;
 
-bool ShouldRun(const Program* program) {
-    for (auto* node : program->ASTNodes().Objects()) {
+bool ShouldRun(const Program& program) {
+    for (auto* node : program.ASTNodes().Objects()) {
         if (auto* ident = node->As<ast::TemplatedIdentifier>()) {
             if (ast::GetAttribute<ast::StrideAttribute>(ident->attributes)) {
                 return true;
@@ -58,7 +58,7 @@
 DecomposeStridedArray::~DecomposeStridedArray() = default;
 
 ast::transform::Transform::ApplyResult DecomposeStridedArray::Apply(
-    const Program* src,
+    const Program& src,
     const ast::transform::DataMap&,
     ast::transform::DataMap&) const {
     if (!ShouldRun(src)) {
@@ -66,8 +66,8 @@
     }
 
     ProgramBuilder b;
-    program::CloneContext ctx{&b, src, /* auto_clone_symbols */ true};
-    const auto& sem = src->Sem();
+    program::CloneContext ctx{&b, &src, /* auto_clone_symbols */ true};
+    const auto& sem = src.Sem();
 
     static constexpr const char* kMemberName = "el";
 
@@ -129,7 +129,7 @@
     // to insert an additional member accessor for the single structure field.
     // Example: `arr[i]` -> `arr[i].el`
     ctx.ReplaceAll([&](const ast::IndexAccessorExpression* idx) -> const ast::Expression* {
-        if (auto* ty = src->TypeOf(idx->object)) {
+        if (auto* ty = src.TypeOf(idx->object)) {
             if (auto* arr = ty->UnwrapRef()->As<core::type::Array>()) {
                 if (!arr->IsStrideImplicit()) {
                     auto* expr = ctx.CloneWithoutTransform(idx);
diff --git a/src/tint/lang/spirv/reader/ast_lower/decompose_strided_array.h b/src/tint/lang/spirv/reader/ast_lower/decompose_strided_array.h
index 1ab8894..80e5735 100644
--- a/src/tint/lang/spirv/reader/ast_lower/decompose_strided_array.h
+++ b/src/tint/lang/spirv/reader/ast_lower/decompose_strided_array.h
@@ -37,7 +37,7 @@
     ~DecomposeStridedArray() override;
 
     /// @copydoc ast::transform::Transform::Apply
-    ApplyResult Apply(const Program* program,
+    ApplyResult Apply(const Program& program,
                       const ast::transform::DataMap& inputs,
                       ast::transform::DataMap& outputs) const override;
 };
diff --git a/src/tint/lang/spirv/reader/ast_lower/decompose_strided_matrix.cc b/src/tint/lang/spirv/reader/ast_lower/decompose_strided_matrix.cc
index 4ab6afc..c5c2827 100644
--- a/src/tint/lang/spirv/reader/ast_lower/decompose_strided_matrix.cc
+++ b/src/tint/lang/spirv/reader/ast_lower/decompose_strided_matrix.cc
@@ -67,19 +67,19 @@
 DecomposeStridedMatrix::~DecomposeStridedMatrix() = default;
 
 ast::transform::Transform::ApplyResult DecomposeStridedMatrix::Apply(
-    const Program* src,
+    const Program& src,
     const ast::transform::DataMap&,
     ast::transform::DataMap&) const {
     ProgramBuilder b;
-    program::CloneContext ctx{&b, src, /* auto_clone_symbols */ true};
+    program::CloneContext ctx{&b, &src, /* auto_clone_symbols */ true};
 
     // Scan the program for all storage and uniform structure matrix members with
     // a custom stride attribute. Replace these matrices with an equivalent array,
     // and populate the `decomposed` map with the members that have been replaced.
     Hashmap<const core::type::StructMember*, MatrixInfo, 8> decomposed;
-    for (auto* node : src->ASTNodes().Objects()) {
+    for (auto* node : src.ASTNodes().Objects()) {
         if (auto* str = node->As<ast::Struct>()) {
-            auto* str_ty = src->Sem().Get(str);
+            auto* str_ty = src.Sem().Get(str);
             if (!str_ty->UsedAs(core::AddressSpace::kUniform) &&
                 !str_ty->UsedAs(core::AddressSpace::kStorage)) {
                 continue;
@@ -119,7 +119,7 @@
     //   ssbo.mat[2] -> ssbo.mat[2]
     ctx.ReplaceAll(
         [&](const ast::IndexAccessorExpression* expr) -> const ast::IndexAccessorExpression* {
-            if (auto* access = src->Sem().Get<sem::StructMemberAccess>(expr->object)) {
+            if (auto* access = src.Sem().Get<sem::StructMemberAccess>(expr->object)) {
                 if (decomposed.Contains(access->Member())) {
                     auto* obj = ctx.CloneWithoutTransform(expr->object);
                     auto* idx = ctx.Clone(expr->index);
@@ -136,7 +136,7 @@
     //   ssbo.mat = mat_to_arr(m)
     std::unordered_map<MatrixInfo, Symbol, MatrixInfo::Hasher> mat_to_arr;
     ctx.ReplaceAll([&](const ast::AssignmentStatement* stmt) -> const ast::Statement* {
-        if (auto* access = src->Sem().Get<sem::StructMemberAccess>(stmt->lhs)) {
+        if (auto* access = src.Sem().Get<sem::StructMemberAccess>(stmt->lhs)) {
             if (auto info = decomposed.Find(access->Member())) {
                 auto fn = tint::GetOrCreate(mat_to_arr, *info, [&] {
                     auto name =
@@ -175,7 +175,7 @@
     //   m = arr_to_mat(ssbo.mat)
     std::unordered_map<MatrixInfo, Symbol, MatrixInfo::Hasher> arr_to_mat;
     ctx.ReplaceAll([&](const ast::MemberAccessorExpression* expr) -> const ast::Expression* {
-        if (auto* access = src->Sem().Get(expr)->UnwrapLoad()->As<sem::StructMemberAccess>()) {
+        if (auto* access = src.Sem().Get(expr)->UnwrapLoad()->As<sem::StructMemberAccess>()) {
             if (auto info = decomposed.Find(access->Member())) {
                 auto fn = tint::GetOrCreate(arr_to_mat, *info, [&] {
                     auto name =
diff --git a/src/tint/lang/spirv/reader/ast_lower/decompose_strided_matrix.h b/src/tint/lang/spirv/reader/ast_lower/decompose_strided_matrix.h
index 58abe70..6ac7b09 100644
--- a/src/tint/lang/spirv/reader/ast_lower/decompose_strided_matrix.h
+++ b/src/tint/lang/spirv/reader/ast_lower/decompose_strided_matrix.h
@@ -37,7 +37,7 @@
     ~DecomposeStridedMatrix() override;
 
     /// @copydoc ast::transform::Transform::Apply
-    ApplyResult Apply(const Program* program,
+    ApplyResult Apply(const Program& program,
                       const ast::transform::DataMap& inputs,
                       ast::transform::DataMap& outputs) const override;
 };
diff --git a/src/tint/lang/spirv/reader/ast_lower/fold_trivial_lets.cc b/src/tint/lang/spirv/reader/ast_lower/fold_trivial_lets.cc
index 4e65452..39cf8b0 100644
--- a/src/tint/lang/spirv/reader/ast_lower/fold_trivial_lets.cc
+++ b/src/tint/lang/spirv/reader/ast_lower/fold_trivial_lets.cc
@@ -30,17 +30,17 @@
 /// PIMPL state for the transform.
 struct FoldTrivialLets::State {
     /// The source program
-    const Program* const src;
+    const Program& src;
     /// The target program builder
     ProgramBuilder b;
     /// The clone context
-    program::CloneContext ctx = {&b, src, /* auto_clone_symbols */ true};
+    program::CloneContext ctx = {&b, &src, /* auto_clone_symbols */ true};
     /// The semantic info.
-    const sem::Info& sem = {ctx.src->Sem()};
+    const sem::Info& sem = src.Sem();
 
     /// Constructor
     /// @param program the source program
-    explicit State(const Program* program) : src(program) {}
+    explicit State(const Program& program) : src(program) {}
 
     /// Process a block.
     /// @param block the block
@@ -138,7 +138,7 @@
     /// @returns the new program
     ApplyResult Run() {
         // Process all blocks in the module.
-        for (auto* node : src->ASTNodes().Objects()) {
+        for (auto* node : src.ASTNodes().Objects()) {
             if (auto* block = node->As<ast::BlockStatement>()) {
                 ProcessBlock(block);
             }
@@ -152,7 +152,7 @@
 
 FoldTrivialLets::~FoldTrivialLets() = default;
 
-ast::transform::Transform::ApplyResult FoldTrivialLets::Apply(const Program* src,
+ast::transform::Transform::ApplyResult FoldTrivialLets::Apply(const Program& src,
                                                               const ast::transform::DataMap&,
                                                               ast::transform::DataMap&) const {
     return State(src).Run();
diff --git a/src/tint/lang/spirv/reader/ast_lower/fold_trivial_lets.h b/src/tint/lang/spirv/reader/ast_lower/fold_trivial_lets.h
index e11151e..fa6ff17 100644
--- a/src/tint/lang/spirv/reader/ast_lower/fold_trivial_lets.h
+++ b/src/tint/lang/spirv/reader/ast_lower/fold_trivial_lets.h
@@ -31,7 +31,7 @@
     ~FoldTrivialLets() override;
 
     /// @copydoc ast::transform::Transform::Apply
-    ApplyResult Apply(const Program* program,
+    ApplyResult Apply(const Program& program,
                       const ast::transform::DataMap& inputs,
                       ast::transform::DataMap& outputs) const override;
 
diff --git a/src/tint/lang/spirv/reader/ast_parser/barrier_test.cc b/src/tint/lang/spirv/reader/ast_parser/barrier_test.cc
index 66247db..502d2ba 100644
--- a/src/tint/lang/spirv/reader/ast_parser/barrier_test.cc
+++ b/src/tint/lang/spirv/reader/ast_parser/barrier_test.cc
@@ -62,7 +62,7 @@
                OpReturn
                OpFunctionEnd
   )");
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
     auto* helper = program.AST().Functions().Find(program.Symbols().Get("helper"));
     ASSERT_NE(helper, nullptr);
     ASSERT_GT(helper->body->statements.Length(), 0u);
@@ -71,9 +71,9 @@
     EXPECT_EQ(call->expr->args.Length(), 0u);
     auto* sem_call = program.Sem().Get<sem::Call>(call->expr);
     ASSERT_NE(sem_call, nullptr);
-    auto* builtin = sem_call->Target()->As<sem::Builtin>();
+    auto* builtin = sem_call->Target()->As<sem::BuiltinFn>();
     ASSERT_NE(builtin, nullptr);
-    EXPECT_EQ(builtin->Type(), core::Function::kWorkgroupBarrier);
+    EXPECT_EQ(builtin->Fn(), core::BuiltinFn::kWorkgroupBarrier);
 }
 
 TEST_F(SpirvASTParserTest, StorageBarrier) {
@@ -95,7 +95,7 @@
                OpReturn
                OpFunctionEnd
   )");
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
     auto* helper = program.AST().Functions().Find(program.Symbols().Get("helper"));
     ASSERT_NE(helper, nullptr);
     ASSERT_GT(helper->body->statements.Length(), 0u);
@@ -104,9 +104,9 @@
     EXPECT_EQ(call->expr->args.Length(), 0u);
     auto* sem_call = program.Sem().Get<sem::Call>(call->expr);
     ASSERT_NE(sem_call, nullptr);
-    auto* builtin = sem_call->Target()->As<sem::Builtin>();
+    auto* builtin = sem_call->Target()->As<sem::BuiltinFn>();
     ASSERT_NE(builtin, nullptr);
-    EXPECT_EQ(builtin->Type(), core::Function::kStorageBarrier);
+    EXPECT_EQ(builtin->Fn(), core::BuiltinFn::kStorageBarrier);
 }
 
 TEST_F(SpirvASTParserTest, TextureBarrier) {
@@ -128,7 +128,7 @@
                OpReturn
                OpFunctionEnd
   )");
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
     auto* helper = program.AST().Functions().Find(program.Symbols().Get("helper"));
     ASSERT_NE(helper, nullptr);
     ASSERT_GT(helper->body->statements.Length(), 0u);
@@ -137,9 +137,9 @@
     EXPECT_EQ(call->expr->args.Length(), 0u);
     auto* sem_call = program.Sem().Get<sem::Call>(call->expr);
     ASSERT_NE(sem_call, nullptr);
-    auto* builtin = sem_call->Target()->As<sem::Builtin>();
+    auto* builtin = sem_call->Target()->As<sem::BuiltinFn>();
     ASSERT_NE(builtin, nullptr);
-    EXPECT_EQ(builtin->Type(), core::Function::kTextureBarrier);
+    EXPECT_EQ(builtin->Fn(), core::BuiltinFn::kTextureBarrier);
 }
 
 TEST_F(SpirvASTParserTest, WorkgroupAndTextureAndStorageBarrier) {
@@ -162,7 +162,7 @@
                OpReturn
                OpFunctionEnd
   )");
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
     auto* helper = program.AST().Functions().Find(program.Symbols().Get("helper"));
     ASSERT_NE(helper, nullptr);
     ASSERT_GT(helper->body->statements.Length(), 2u);
@@ -173,9 +173,9 @@
         EXPECT_EQ(call->expr->args.Length(), 0u);
         auto* sem_call = program.Sem().Get<sem::Call>(call->expr);
         ASSERT_NE(sem_call, nullptr);
-        auto* builtin = sem_call->Target()->As<sem::Builtin>();
+        auto* builtin = sem_call->Target()->As<sem::BuiltinFn>();
         ASSERT_NE(builtin, nullptr);
-        EXPECT_EQ(builtin->Type(), core::Function::kWorkgroupBarrier);
+        EXPECT_EQ(builtin->Fn(), core::BuiltinFn::kWorkgroupBarrier);
     }
     {
         auto* call = helper->body->statements[1]->As<ast::CallStatement>();
@@ -183,9 +183,9 @@
         EXPECT_EQ(call->expr->args.Length(), 0u);
         auto* sem_call = program.Sem().Get<sem::Call>(call->expr);
         ASSERT_NE(sem_call, nullptr);
-        auto* builtin = sem_call->Target()->As<sem::Builtin>();
+        auto* builtin = sem_call->Target()->As<sem::BuiltinFn>();
         ASSERT_NE(builtin, nullptr);
-        EXPECT_EQ(builtin->Type(), core::Function::kStorageBarrier);
+        EXPECT_EQ(builtin->Fn(), core::BuiltinFn::kStorageBarrier);
     }
     {
         auto* call = helper->body->statements[2]->As<ast::CallStatement>();
@@ -193,9 +193,9 @@
         EXPECT_EQ(call->expr->args.Length(), 0u);
         auto* sem_call = program.Sem().Get<sem::Call>(call->expr);
         ASSERT_NE(sem_call, nullptr);
-        auto* builtin = sem_call->Target()->As<sem::Builtin>();
+        auto* builtin = sem_call->Target()->As<sem::BuiltinFn>();
         ASSERT_NE(builtin, nullptr);
-        EXPECT_EQ(builtin->Type(), core::Function::kTextureBarrier);
+        EXPECT_EQ(builtin->Fn(), core::BuiltinFn::kTextureBarrier);
     }
 }
 
diff --git a/src/tint/lang/spirv/reader/ast_parser/function.cc b/src/tint/lang/spirv/reader/ast_parser/function.cc
index 754fff5..311f9b2 100644
--- a/src/tint/lang/spirv/reader/ast_parser/function.cc
+++ b/src/tint/lang/spirv/reader/ast_parser/function.cc
@@ -17,9 +17,9 @@
 #include <algorithm>
 #include <array>
 
+#include "src/tint/lang/core/builtin_fn.h"
 #include "src/tint/lang/core/builtin_value.h"
 #include "src/tint/lang/core/fluent_types.h"
-#include "src/tint/lang/core/function.h"
 #include "src/tint/lang/core/type/depth_texture.h"
 #include "src/tint/lang/core/type/sampled_texture.h"
 #include "src/tint/lang/core/type/texture_dimension.h"
@@ -456,42 +456,42 @@
 }
 
 // Returns the WGSL standard library function builtin for the
-// given instruction, or core::Function::kNone
-core::Function GetBuiltin(spv::Op opcode) {
+// given instruction, or core::BuiltinFn::kNone
+core::BuiltinFn GetBuiltin(spv::Op opcode) {
     switch (opcode) {
         case spv::Op::OpBitCount:
-            return core::Function::kCountOneBits;
+            return core::BuiltinFn::kCountOneBits;
         case spv::Op::OpBitFieldInsert:
-            return core::Function::kInsertBits;
+            return core::BuiltinFn::kInsertBits;
         case spv::Op::OpBitFieldSExtract:
         case spv::Op::OpBitFieldUExtract:
-            return core::Function::kExtractBits;
+            return core::BuiltinFn::kExtractBits;
         case spv::Op::OpBitReverse:
-            return core::Function::kReverseBits;
+            return core::BuiltinFn::kReverseBits;
         case spv::Op::OpDot:
-            return core::Function::kDot;
+            return core::BuiltinFn::kDot;
         case spv::Op::OpDPdx:
-            return core::Function::kDpdx;
+            return core::BuiltinFn::kDpdx;
         case spv::Op::OpDPdy:
-            return core::Function::kDpdy;
+            return core::BuiltinFn::kDpdy;
         case spv::Op::OpFwidth:
-            return core::Function::kFwidth;
+            return core::BuiltinFn::kFwidth;
         case spv::Op::OpDPdxFine:
-            return core::Function::kDpdxFine;
+            return core::BuiltinFn::kDpdxFine;
         case spv::Op::OpDPdyFine:
-            return core::Function::kDpdyFine;
+            return core::BuiltinFn::kDpdyFine;
         case spv::Op::OpFwidthFine:
-            return core::Function::kFwidthFine;
+            return core::BuiltinFn::kFwidthFine;
         case spv::Op::OpDPdxCoarse:
-            return core::Function::kDpdxCoarse;
+            return core::BuiltinFn::kDpdxCoarse;
         case spv::Op::OpDPdyCoarse:
-            return core::Function::kDpdyCoarse;
+            return core::BuiltinFn::kDpdyCoarse;
         case spv::Op::OpFwidthCoarse:
-            return core::Function::kFwidthCoarse;
+            return core::BuiltinFn::kFwidthCoarse;
         default:
             break;
     }
-    return core::Function::kNone;
+    return core::BuiltinFn::kNone;
 }
 
 // @param opcode a SPIR-V opcode
@@ -3819,11 +3819,11 @@
     }
 
     const auto builtin = GetBuiltin(op);
-    if (builtin != core::Function::kNone) {
+    if (builtin != core::BuiltinFn::kNone) {
         switch (builtin) {
-            case core::Function::kExtractBits:
+            case core::BuiltinFn::kExtractBits:
                 return MakeExtractBitsCall(inst);
-            case core::Function::kInsertBits:
+            case core::BuiltinFn::kInsertBits:
                 return MakeInsertBitsCall(inst);
             default:
                 return MakeBuiltinCall(inst);
@@ -5852,7 +5852,7 @@
 }
 
 bool FunctionEmitter::EmitAtomicOp(const spvtools::opt::Instruction& inst) {
-    auto emit_atomic = [&](core::Function builtin, std::initializer_list<TypedExpression> args) {
+    auto emit_atomic = [&](core::BuiltinFn builtin, std::initializer_list<TypedExpression> args) {
         // Split args into params and expressions
         ParameterList params;
         params.Reserve(args.size());
@@ -5911,38 +5911,38 @@
 
     switch (opcode(inst)) {
         case spv::Op::OpAtomicLoad:
-            return emit_atomic(core::Function::kAtomicLoad, {oper(/*ptr*/ 0)});
+            return emit_atomic(core::BuiltinFn::kAtomicLoad, {oper(/*ptr*/ 0)});
         case spv::Op::OpAtomicStore:
-            return emit_atomic(core::Function::kAtomicStore, {oper(/*ptr*/ 0), oper(/*value*/ 3)});
+            return emit_atomic(core::BuiltinFn::kAtomicStore, {oper(/*ptr*/ 0), oper(/*value*/ 3)});
         case spv::Op::OpAtomicExchange:
-            return emit_atomic(core::Function::kAtomicExchange,
+            return emit_atomic(core::BuiltinFn::kAtomicExchange,
                                {oper(/*ptr*/ 0), oper(/*value*/ 3)});
         case spv::Op::OpAtomicCompareExchange:
         case spv::Op::OpAtomicCompareExchangeWeak:
-            return emit_atomic(core::Function::kAtomicCompareExchangeWeak,
+            return emit_atomic(core::BuiltinFn::kAtomicCompareExchangeWeak,
                                {oper(/*ptr*/ 0), /*value*/ oper(5), /*comparator*/ oper(4)});
         case spv::Op::OpAtomicIIncrement:
-            return emit_atomic(core::Function::kAtomicAdd, {oper(/*ptr*/ 0), lit(1)});
+            return emit_atomic(core::BuiltinFn::kAtomicAdd, {oper(/*ptr*/ 0), lit(1)});
         case spv::Op::OpAtomicIDecrement:
-            return emit_atomic(core::Function::kAtomicSub, {oper(/*ptr*/ 0), lit(1)});
+            return emit_atomic(core::BuiltinFn::kAtomicSub, {oper(/*ptr*/ 0), lit(1)});
         case spv::Op::OpAtomicIAdd:
-            return emit_atomic(core::Function::kAtomicAdd, {oper(/*ptr*/ 0), oper(/*value*/ 3)});
+            return emit_atomic(core::BuiltinFn::kAtomicAdd, {oper(/*ptr*/ 0), oper(/*value*/ 3)});
         case spv::Op::OpAtomicISub:
-            return emit_atomic(core::Function::kAtomicSub, {oper(/*ptr*/ 0), oper(/*value*/ 3)});
+            return emit_atomic(core::BuiltinFn::kAtomicSub, {oper(/*ptr*/ 0), oper(/*value*/ 3)});
         case spv::Op::OpAtomicSMin:
-            return emit_atomic(core::Function::kAtomicMin, {oper(/*ptr*/ 0), oper(/*value*/ 3)});
+            return emit_atomic(core::BuiltinFn::kAtomicMin, {oper(/*ptr*/ 0), oper(/*value*/ 3)});
         case spv::Op::OpAtomicUMin:
-            return emit_atomic(core::Function::kAtomicMin, {oper(/*ptr*/ 0), oper(/*value*/ 3)});
+            return emit_atomic(core::BuiltinFn::kAtomicMin, {oper(/*ptr*/ 0), oper(/*value*/ 3)});
         case spv::Op::OpAtomicSMax:
-            return emit_atomic(core::Function::kAtomicMax, {oper(/*ptr*/ 0), oper(/*value*/ 3)});
+            return emit_atomic(core::BuiltinFn::kAtomicMax, {oper(/*ptr*/ 0), oper(/*value*/ 3)});
         case spv::Op::OpAtomicUMax:
-            return emit_atomic(core::Function::kAtomicMax, {oper(/*ptr*/ 0), oper(/*value*/ 3)});
+            return emit_atomic(core::BuiltinFn::kAtomicMax, {oper(/*ptr*/ 0), oper(/*value*/ 3)});
         case spv::Op::OpAtomicAnd:
-            return emit_atomic(core::Function::kAtomicAnd, {oper(/*ptr*/ 0), oper(/*value*/ 3)});
+            return emit_atomic(core::BuiltinFn::kAtomicAnd, {oper(/*ptr*/ 0), oper(/*value*/ 3)});
         case spv::Op::OpAtomicOr:
-            return emit_atomic(core::Function::kAtomicOr, {oper(/*ptr*/ 0), oper(/*value*/ 3)});
+            return emit_atomic(core::BuiltinFn::kAtomicOr, {oper(/*ptr*/ 0), oper(/*value*/ 3)});
         case spv::Op::OpAtomicXor:
-            return emit_atomic(core::Function::kAtomicXor, {oper(/*ptr*/ 0), oper(/*value*/ 3)});
+            return emit_atomic(core::BuiltinFn::kAtomicXor, {oper(/*ptr*/ 0), oper(/*value*/ 3)});
         case spv::Op::OpAtomicFlagTestAndSet:
         case spv::Op::OpAtomicFlagClear:
         case spv::Op::OpAtomicFMinEXT:
diff --git a/src/tint/lang/spirv/reader/ast_parser/helper_test.cc b/src/tint/lang/spirv/reader/ast_parser/helper_test.cc
index 7e5ed82..ab887b8 100644
--- a/src/tint/lang/spirv/reader/ast_parser/helper_test.cc
+++ b/src/tint/lang/spirv/reader/ast_parser/helper_test.cc
@@ -36,7 +36,7 @@
 }
 
 std::string ToString(const Program& program) {
-    wgsl::writer::ASTPrinter writer(&program);
+    wgsl::writer::ASTPrinter writer(program);
     writer.Generate();
 
     if (!writer.Diagnostics().empty()) {
@@ -46,7 +46,7 @@
 }
 
 std::string ToString(const Program& program, VectorRef<const ast::Statement*> stmts) {
-    wgsl::writer::ASTPrinter writer(&program);
+    wgsl::writer::ASTPrinter writer(program);
     for (const auto* stmt : stmts) {
         writer.EmitStatement(stmt);
     }
@@ -57,7 +57,7 @@
 }
 
 std::string ToString(const Program& program, const ast::Node* node) {
-    wgsl::writer::ASTPrinter writer(&program);
+    wgsl::writer::ASTPrinter writer(program);
     return Switch(
         node,
         [&](const ast::Expression* expr) {
diff --git a/src/tint/lang/spirv/reader/ast_parser/namer.cc b/src/tint/lang/spirv/reader/ast_parser/namer.cc
index aaa1ba6..6039a55 100644
--- a/src/tint/lang/spirv/reader/ast_parser/namer.cc
+++ b/src/tint/lang/spirv/reader/ast_parser/namer.cc
@@ -17,7 +17,7 @@
 #include <algorithm>
 #include <unordered_set>
 
-#include "src/tint/lang/core/function.h"
+#include "src/tint/lang/core/builtin_fn.h"
 #include "src/tint/utils/ice/ice.h"
 #include "src/tint/utils/text/string_stream.h"
 
@@ -176,7 +176,7 @@
     for (const auto* reserved : kWGSLReservedWords) {
         name_to_id_[std::string(reserved)] = 0;
     }
-    for (const auto* builtin_function : core::kFunctionStrings) {
+    for (const auto* builtin_function : core::kBuiltinFnStrings) {
         name_to_id_[std::string(builtin_function)] = 0;
     }
 }
diff --git a/src/tint/lang/spirv/reader/ast_parser/namer_test.cc b/src/tint/lang/spirv/reader/ast_parser/namer_test.cc
index f6af2c7..14ff1dc 100644
--- a/src/tint/lang/spirv/reader/ast_parser/namer_test.cc
+++ b/src/tint/lang/spirv/reader/ast_parser/namer_test.cc
@@ -15,7 +15,7 @@
 #include "src/tint/lang/spirv/reader/ast_parser/namer.h"
 
 #include "gmock/gmock.h"
-#include "src/tint/lang/core/function.h"
+#include "src/tint/lang/core/builtin_fn.h"
 #include "src/tint/utils/text/string_stream.h"
 
 namespace tint::spirv::reader::ast_parser {
@@ -408,7 +408,7 @@
 
 INSTANTIATE_TEST_SUITE_P(SpirvASTParserTest_BuiltinFunctions,
                          SpvNamerBuiltinFunctionTest,
-                         ::testing::ValuesIn(core::kFunctionStrings));
+                         ::testing::ValuesIn(core::kBuiltinFnStrings));
 
 }  // namespace
 }  // namespace tint::spirv::reader::ast_parser
diff --git a/src/tint/lang/spirv/reader/ast_parser/parse.cc b/src/tint/lang/spirv/reader/ast_parser/parse.cc
index abef728..d0d147f 100644
--- a/src/tint/lang/spirv/reader/ast_parser/parse.cc
+++ b/src/tint/lang/spirv/reader/ast_parser/parse.cc
@@ -95,7 +95,7 @@
     manager.Add<DecomposeStridedArray>();
     manager.Add<ast::transform::RemoveUnreachableStatements>();
     manager.Add<Atomics>();
-    return manager.Run(&program, {}, outputs);
+    return manager.Run(program, {}, outputs);
 }
 
 }  // namespace tint::spirv::reader::ast_parser
diff --git a/src/tint/lang/spirv/spirv.def b/src/tint/lang/spirv/spirv.def
index 814352c..bc5d60c 100644
--- a/src/tint/lang/spirv/spirv.def
+++ b/src/tint/lang/spirv/spirv.def
@@ -16,8 +16,9 @@
 // Spirv builtin definition file                                              //
 ////////////////////////////////////////////////////////////////////////////////
 
-// TODO(crbug.com/2036): add an include facility and move these duplicate match and type lines
-// into a common file.
+import "src/tint/lang/core/address_space.def"
+import "src/tint/lang/core/access.def"
+import "src/tint/lang/core/texel_format.def"
 
 type bool
 type f32
@@ -62,42 +63,6 @@
 type struct_with_runtime_array
 type sampled_image<T>
 
-enum address_space {
-  function
-  private
-  workgroup
-  uniform
-  storage
-  push_constant
-  pixel_local
-}
-
-enum access {
-  read
-  write
-  read_write
-}
-
-enum texel_format {
-  bgra8unorm
-  rgba8unorm
-  rgba8snorm
-  rgba8uint
-  rgba8sint
-  rgba16uint
-  rgba16sint
-  rgba16float
-  r32uint
-  r32sint
-  r32float
-  rg32uint
-  rg32sint
-  rg32float
-  rgba32uint
-  rgba32sint
-  rgba32float
-}
-
 match f32_f16: f32 | f16
 match iu32: i32 | u32
 match fiu32: f32 | i32 | u32
@@ -138,18 +103,9 @@
 match readable
   : access.read
   | access.read_write
-
-////////////////////////////////////////////////////////////////////////////////
-// Enumerators                                                                //
-////////////////////////////////////////////////////////////////////////////////
-
-enum intrinsic {
-  image_sample_implicit_lod
-  image_sample_explicit_lod
-  image_sample_dref_implicit_lod
-  image_sample_dref_explicit_lod
-  image_write
-}
+match writable
+  : access.write
+  | access.read_write
 
 ////////////////////////////////////////////////////////////////////////////////
 // Builtin Functions                                                          //
@@ -244,6 +200,81 @@
 fn image_read<F: i32_texel_format, A: readable, C: iu32, S: iu32>(texture_storage_3d<F, A>, vec3<C>, S) -> vec4<i32>
 fn image_read<F: u32_texel_format, A: readable, C: iu32, S: iu32>(texture_storage_3d<F, A>, vec3<C>, S) -> vec4<u32>
 
+fn image_sample_implicit_lod<T: fiu32, C: iu32>(sampled_image<texture_1d<T> >, f32, C) -> vec4<f32>
+fn image_sample_implicit_lod<T: fiu32, C: iu32>(sampled_image<texture_2d<T> >, vec2<f32>, C) -> vec4<f32>
+fn image_sample_implicit_lod<T: fiu32, C: iu32>(sampled_image<texture_2d<T> >, vec2<f32>, C, f32) -> vec4<f32>
+fn image_sample_implicit_lod<T: fiu32, C: iu32, D: iu32>(sampled_image<texture_2d<T> >, vec2<f32>, C, f32, vec2<D>) -> vec4<f32>
+fn image_sample_implicit_lod<T: fiu32, C: iu32, D: iu32>(sampled_image<texture_2d<T> >, vec2<f32>, C, vec2<D>) -> vec4<f32>
+fn image_sample_implicit_lod<T: fiu32, C: iu32>(sampled_image<texture_2d_array<T> >, vec3<f32>, C) -> vec4<f32>
+fn image_sample_implicit_lod<T: fiu32, C: iu32>(sampled_image<texture_2d_array<T> >, vec3<f32>, C, f32) -> vec4<f32>
+fn image_sample_implicit_lod<T: fiu32, C: iu32, D: iu32>(sampled_image<texture_2d_array<T> >, vec3<f32>, C, vec2<D>) -> vec4<f32>
+fn image_sample_implicit_lod<T: fiu32, C: iu32, D: iu32>(sampled_image<texture_2d_array<T> >, vec3<f32>, C, f32, vec2<D>) -> vec4<f32>
+fn image_sample_implicit_lod<T: fiu32, C: iu32>(sampled_image<texture_3d<T> >, vec3<f32>, C) -> vec4<f32>
+fn image_sample_implicit_lod<T: fiu32, C: iu32>(sampled_image<texture_3d<T> >, vec3<f32>, C, f32) -> vec4<f32>
+fn image_sample_implicit_lod<T: fiu32, C: iu32, D: iu32>(sampled_image<texture_3d<T> >, vec3<f32>, C, f32, vec3<D>) -> vec4<f32>
+fn image_sample_implicit_lod<T: fiu32, C: iu32, D: iu32>(sampled_image<texture_3d<T> >, vec3<f32>, C, vec3<D>) -> vec4<f32>
+fn image_sample_implicit_lod<T: fiu32, C: iu32>(sampled_image<texture_cube<T> >, vec3<f32>, C) -> vec4<f32>
+fn image_sample_implicit_lod<T: fiu32, C: iu32>(sampled_image<texture_cube<T> >, vec3<f32>, C, f32) -> vec4<f32>
+fn image_sample_implicit_lod<T: fiu32, C: iu32>(sampled_image<texture_cube_array<T> >, vec4<f32>, C) -> vec4<f32>
+fn image_sample_implicit_lod<T: fiu32, C: iu32>(sampled_image<texture_cube_array<T> >, vec4<f32>, C, f32) -> vec4<f32>
+fn image_sample_implicit_lod<C: iu32>(sampled_image<texture_depth_2d>, vec2<f32>, C) -> vec4<f32>
+fn image_sample_implicit_lod<C: iu32, D: iu32>(sampled_image<texture_depth_2d>, vec2<f32>, C, vec2<D>) -> vec4<f32>
+fn image_sample_implicit_lod<C: iu32>(sampled_image<texture_depth_cube>, vec3<f32>, C) -> vec4<f32>
+fn image_sample_implicit_lod<C: iu32>(sampled_image<texture_depth_2d_array>, vec3<f32>, C) -> vec4<f32>
+fn image_sample_implicit_lod<C: iu32, D: iu32>(sampled_image<texture_depth_2d_array>, vec3<f32>, C, vec2<D>) -> vec4<f32>
+fn image_sample_implicit_lod<C: iu32>(sampled_image<texture_depth_cube_array>, vec4<f32>, C) -> vec4<f32>
+
+fn image_sample_explicit_lod<T: fiu32, C: iu32>(sampled_image<texture_2d<T> >, vec2<f32>, C, f32) -> vec4<f32>
+fn image_sample_explicit_lod<T: fiu32, C: iu32>(sampled_image<texture_2d<T> >, vec2<f32>, C, vec2<f32>, vec2<f32>) -> vec4<f32>
+fn image_sample_explicit_lod<T: fiu32, C: iu32, D: iu32>(sampled_image<texture_2d<T> >, vec2<f32>, C, f32, vec2<D>) -> vec4<f32>
+fn image_sample_explicit_lod<T: fiu32, C: iu32, D: iu32>(sampled_image<texture_2d<T> >, vec2<f32>, C, vec2<f32>, vec2<D>) -> vec4<f32>
+fn image_sample_explicit_lod<T: fiu32, C: iu32, D: iu32>(sampled_image<texture_2d<T> >, vec2<f32>, C, vec2<f32>, vec2<f32>, vec2<D>) -> vec4<f32>
+fn image_sample_explicit_lod<T: fiu32, C: iu32>(sampled_image<texture_2d_array<T> >, vec3<f32>, C, f32) -> vec4<f32>
+fn image_sample_explicit_lod<T: fiu32, C: iu32>(sampled_image<texture_2d_array<T> >, vec3<f32>, C, vec2<f32>, vec2<f32>) -> vec4<f32>
+fn image_sample_explicit_lod<T: fiu32, C: iu32, D: iu32>(sampled_image<texture_2d_array<T> >, vec3<f32>, C, f32, vec2<D>) -> vec4<f32>
+fn image_sample_explicit_lod<T: fiu32, C: iu32, D: iu32>(sampled_image<texture_2d_array<T> >, vec3<f32>, C, vec2<f32>, vec2<f32>, vec2<D>) -> vec4<f32>
+fn image_sample_explicit_lod<T: fiu32, C: iu32>(sampled_image<texture_3d<T> >, vec3<f32>, C, f32) -> vec4<f32>
+fn image_sample_explicit_lod<T: fiu32, C: iu32>(sampled_image<texture_3d<T> >, vec3<f32>, C, vec3<f32>, vec3<f32>) -> vec4<f32>
+fn image_sample_explicit_lod<T: fiu32, C: iu32, D: iu32>(sampled_image<texture_3d<T> >, vec3<f32>, C, f32, vec3<D>) -> vec4<f32>
+fn image_sample_explicit_lod<T: fiu32, C: iu32, D: iu32>(sampled_image<texture_3d<T> >, vec3<f32>, C, vec3<f32>, vec3<f32>, vec3<D>) -> vec4<f32>
+fn image_sample_explicit_lod<T: fiu32, C: iu32>(sampled_image<texture_cube<T> >, vec3<f32>, C, f32) -> vec4<f32>
+fn image_sample_explicit_lod<T: fiu32, C: iu32>(sampled_image<texture_cube<T> >, vec3<f32>, C, vec3<f32>, vec3<f32>) -> vec4<f32>
+fn image_sample_explicit_lod<T: fiu32, C: iu32>(sampled_image<texture_cube_array<T> >, vec4<f32>, C, f32) -> vec4<f32>
+fn image_sample_explicit_lod<T: fiu32, C: iu32>(sampled_image<texture_cube_array<T> >, vec4<f32>, C, vec3<f32>, vec3<f32>) -> vec4<f32>
+fn image_sample_explicit_lod<C: iu32>(sampled_image<texture_depth_2d>, vec2<f32>, C, f32) -> vec4<f32>
+fn image_sample_explicit_lod<C: iu32, D: iu32>(sampled_image<texture_depth_2d>, vec2<f32>, C, f32, vec2<D>) -> vec4<f32>
+fn image_sample_explicit_lod<C: iu32>(sampled_image<texture_depth_2d_array>, vec3<f32>, C, f32) -> vec4<f32>
+fn image_sample_explicit_lod<C: iu32, D: iu32>(sampled_image<texture_depth_2d_array>, vec3<f32>, C, f32, vec2<D>) -> vec4<f32>
+fn image_sample_explicit_lod<C: iu32>(sampled_image<texture_depth_cube>, vec3<f32>, C, f32) -> vec4<f32>
+fn image_sample_explicit_lod<C: iu32>(sampled_image<texture_depth_cube_array>, vec4<f32>, C, f32) -> vec4<f32>
+
+fn image_sample_dref_implicit_lod<C: iu32>(sampled_image<texture_depth_2d>, vec2<f32>, f32, C) -> f32
+fn image_sample_dref_implicit_lod<C: iu32, D: iu32>(sampled_image<texture_depth_2d>, vec2<f32>, f32, C, vec2<D>) -> f32
+fn image_sample_dref_implicit_lod<C: iu32>(sampled_image<texture_depth_2d_array>, vec3<f32>, f32, C) -> f32
+fn image_sample_dref_implicit_lod<C: iu32, D: iu32>(sampled_image<texture_depth_2d_array>, vec3<f32>, f32, C, vec2<D>) -> f32
+fn image_sample_dref_implicit_lod<C: iu32>(sampled_image<texture_depth_cube>, vec3<f32>, f32, C) -> f32
+fn image_sample_dref_implicit_lod<C: iu32>(sampled_image<texture_depth_cube_array>, vec4<f32>, f32, C) -> f32
+
+fn image_sample_dref_explicit_lod<C: iu32>(sampled_image<texture_depth_2d>, vec2<f32>, f32, C, f32) -> f32
+fn image_sample_dref_explicit_lod<C: iu32, D: iu32>(sampled_image<texture_depth_2d>, vec2<f32>, f32, C, f32, vec2<D>) -> f32
+fn image_sample_dref_explicit_lod<C: iu32>(sampled_image<texture_depth_2d_array>, vec3<f32>, f32, C, f32) -> f32
+fn image_sample_dref_explicit_lod<C: iu32, D: iu32>(sampled_image<texture_depth_2d_array>, vec3<f32>, f32, C, f32, vec2<D>) -> f32
+fn image_sample_dref_explicit_lod<C: iu32>(sampled_image<texture_depth_cube>, vec3<f32>, f32, C, f32) -> f32
+fn image_sample_dref_explicit_lod<C: iu32>(sampled_image<texture_depth_cube_array>, vec4<f32>, f32, C, f32) -> f32
+
+fn image_write<C: iu32, D: iu32>(texture_storage_1d<f32_texel_format, writable>, C, vec4<f32>, D)
+fn image_write<C: iu32, D: iu32>(texture_storage_1d<i32_texel_format, writable>, C, vec4<i32>, D)
+fn image_write<C: iu32, D: iu32>(texture_storage_1d<u32_texel_format, writable>, C, vec4<u32>, D)
+fn image_write<C: iu32, D: iu32>(texture_storage_2d<f32_texel_format, writable>, vec2<C>, vec4<f32>, D)
+fn image_write<C: iu32, D: iu32>(texture_storage_2d<i32_texel_format, writable>, vec2<C>, vec4<i32>, D)
+fn image_write<C: iu32, D: iu32>(texture_storage_2d<u32_texel_format, writable>, vec2<C>, vec4<u32>, D)
+fn image_write<C: iu32, D: iu32>(texture_storage_2d_array<f32_texel_format, writable>, vec3<C>, vec4<f32>, D)
+fn image_write<C: iu32, D: iu32>(texture_storage_2d_array<i32_texel_format, writable>, vec3<C>, vec4<i32>, D)
+fn image_write<C: iu32, D: iu32>(texture_storage_2d_array<u32_texel_format, writable>, vec3<C>, vec4<u32>, D)
+fn image_write<C: iu32, D: iu32>(texture_storage_3d<f32_texel_format, writable>, vec3<C>, vec4<f32>, D)
+fn image_write<C: iu32, D: iu32>(texture_storage_3d<i32_texel_format, writable>, vec3<C>, vec4<i32>, D)
+fn image_write<C: iu32, D: iu32>(texture_storage_3d<u32_texel_format, writable>, vec3<C>, vec4<u32>, D)
+
 fn matrix_times_matrix<T: f32_f16, K: num, C: num, R: num>(mat<K, R, T>, mat<C, K, T>) -> mat<C, R, T>
 fn matrix_times_scalar<T: f32_f16, N: num, M: num>(mat<N, M, T>, T) -> mat<N, M, T>
 fn matrix_times_vector<T: f32_f16, N: num, M: num>(mat<N, M, T>, vec<N, T>) -> vec<M, T>
diff --git a/src/tint/lang/spirv/writer/BUILD.bazel b/src/tint/lang/spirv/writer/BUILD.bazel
index e42bedf..4123059 100644
--- a/src/tint/lang/spirv/writer/BUILD.bazel
+++ b/src/tint/lang/spirv/writer/BUILD.bazel
@@ -42,6 +42,7 @@
     "//src/tint/lang/core/ir",
     "//src/tint/lang/core/ir/transform",
     "//src/tint/lang/core/type",
+    "//src/tint/lang/spirv",
     "//src/tint/lang/spirv/intrinsic/data",
     "//src/tint/lang/spirv/ir",
     "//src/tint/lang/wgsl",
@@ -113,6 +114,7 @@
     "//src/tint/lang/core/intrinsic/data",
     "//src/tint/lang/core/ir",
     "//src/tint/lang/core/type",
+    "//src/tint/lang/spirv",
     "//src/tint/lang/spirv/intrinsic/data",
     "//src/tint/lang/spirv/ir",
     "//src/tint/utils/containers",
diff --git a/src/tint/lang/spirv/writer/BUILD.cmake b/src/tint/lang/spirv/writer/BUILD.cmake
index b47843a..d62c4a1 100644
--- a/src/tint/lang/spirv/writer/BUILD.cmake
+++ b/src/tint/lang/spirv/writer/BUILD.cmake
@@ -49,6 +49,7 @@
   tint_lang_core_ir
   tint_lang_core_ir_transform
   tint_lang_core_type
+  tint_lang_spirv
   tint_lang_spirv_intrinsic_data
   tint_lang_spirv_ir
   tint_lang_wgsl
@@ -125,6 +126,7 @@
   tint_lang_core_intrinsic_data
   tint_lang_core_ir
   tint_lang_core_type
+  tint_lang_spirv
   tint_lang_spirv_intrinsic_data
   tint_lang_spirv_ir
   tint_utils_containers
diff --git a/src/tint/lang/spirv/writer/BUILD.gn b/src/tint/lang/spirv/writer/BUILD.gn
index a3dd0b8..a7dd432 100644
--- a/src/tint/lang/spirv/writer/BUILD.gn
+++ b/src/tint/lang/spirv/writer/BUILD.gn
@@ -45,6 +45,7 @@
       "${tint_src_dir}/lang/core/ir",
       "${tint_src_dir}/lang/core/ir/transform",
       "${tint_src_dir}/lang/core/type",
+      "${tint_src_dir}/lang/spirv",
       "${tint_src_dir}/lang/spirv/intrinsic/data",
       "${tint_src_dir}/lang/spirv/ir",
       "${tint_src_dir}/lang/wgsl",
@@ -117,6 +118,7 @@
         "${tint_src_dir}/lang/core/intrinsic/data",
         "${tint_src_dir}/lang/core/ir",
         "${tint_src_dir}/lang/core/type",
+        "${tint_src_dir}/lang/spirv",
         "${tint_src_dir}/lang/spirv/intrinsic/data",
         "${tint_src_dir}/lang/spirv/ir",
         "${tint_src_dir}/utils/containers",
diff --git a/src/tint/lang/spirv/writer/ast_printer/assign_test.cc b/src/tint/lang/spirv/writer/ast_printer/assign_test.cc
index 084c8f9..3b12896 100644
--- a/src/tint/lang/spirv/writer/ast_printer/assign_test.cc
+++ b/src/tint/lang/spirv/writer/ast_printer/assign_test.cc
@@ -64,11 +64,11 @@
 
             pb.WrapInFunction(assign);
 
-            auto program = std::make_unique<Program>(resolver::Resolve(pb));
-            auto b = std::make_unique<Builder>(program.get());
+            auto program = resolver::Resolve(pb);
+            Builder b(program);
 
-            b->GenerateGlobalVariable(v);
-            b->GenerateAssignStatement(assign);
+            b.GenerateGlobalVariable(v);
+            b.GenerateAssignStatement(assign);
         },
         "trying to add SPIR-V instruction 62 outside a function");
 }
diff --git a/src/tint/lang/spirv/writer/ast_printer/ast_if_test.cc b/src/tint/lang/spirv/writer/ast_printer/ast_if_test.cc
index 944240a..3637111 100644
--- a/src/tint/lang/spirv/writer/ast_printer/ast_if_test.cc
+++ b/src/tint/lang/spirv/writer/ast_printer/ast_if_test.cc
@@ -60,10 +60,10 @@
             auto* expr = pb.If(true, block);
             pb.WrapInFunction(expr);
 
-            auto program = std::make_unique<Program>(resolver::Resolve(pb));
-            auto b = std::make_unique<Builder>(program.get());
+            auto program = resolver::Resolve(pb);
+            Builder b(program);
 
-            b->GenerateIfStatement(expr);
+            b.GenerateIfStatement(expr);
         },
         "Internal error: trying to add SPIR-V instruction 247 outside a function");
 }
diff --git a/src/tint/lang/spirv/writer/ast_printer/ast_printer.cc b/src/tint/lang/spirv/writer/ast_printer/ast_printer.cc
index c36f0bc..1affcf3 100644
--- a/src/tint/lang/spirv/writer/ast_printer/ast_printer.cc
+++ b/src/tint/lang/spirv/writer/ast_printer/ast_printer.cc
@@ -47,7 +47,7 @@
 
 namespace tint::spirv::writer {
 
-SanitizedResult Sanitize(const Program* in, const Options& options) {
+SanitizedResult Sanitize(const Program& in, const Options& options) {
     ast::transform::Manager manager;
     ast::transform::DataMap data;
 
@@ -179,7 +179,7 @@
     return result;
 }
 
-ASTPrinter::ASTPrinter(const Program* program,
+ASTPrinter::ASTPrinter(const Program& program,
                        bool zero_initialize_workgroup_memory,
                        bool experimental_require_subgroup_uniform_control_flow)
     : builder_(program,
diff --git a/src/tint/lang/spirv/writer/ast_printer/ast_printer.h b/src/tint/lang/spirv/writer/ast_printer/ast_printer.h
index 7c69ef6..8ad5859 100644
--- a/src/tint/lang/spirv/writer/ast_printer/ast_printer.h
+++ b/src/tint/lang/spirv/writer/ast_printer/ast_printer.h
@@ -34,7 +34,7 @@
 /// Sanitize a program in preparation for generating SPIR-V.
 /// @program The program to sanitize
 /// @param options The SPIR-V generator options.
-SanitizedResult Sanitize(const Program* program, const Options& options);
+SanitizedResult Sanitize(const Program& program, const Options& options);
 
 /// Implementation class for SPIR-V generator
 class ASTPrinter {
@@ -46,7 +46,7 @@
     /// @param experimental_require_subgroup_uniform_control_flow `true` to require
     /// `SPV_KHR_subgroup_uniform_control_flow` extension and `SubgroupUniformControlFlowKHR`
     /// execution mode for compute stage entry points.
-    ASTPrinter(const Program* program,
+    ASTPrinter(const Program& program,
                bool zero_initialize_workgroup_memory,
                bool experimental_require_subgroup_uniform_control_flow);
 
diff --git a/src/tint/lang/spirv/writer/ast_printer/ast_printer_test.cc b/src/tint/lang/spirv/writer/ast_printer/ast_printer_test.cc
index bcf0633..da60198 100644
--- a/src/tint/lang/spirv/writer/ast_printer/ast_printer_test.cc
+++ b/src/tint/lang/spirv/writer/ast_printer/ast_printer_test.cc
@@ -23,9 +23,9 @@
 TEST_F(SpirvASTPrinterTest, InvalidProgram) {
     Diagnostics().add_error(diag::System::Writer, "make the program invalid");
     ASSERT_FALSE(IsValid());
-    auto program = std::make_unique<Program>(resolver::Resolve(*this));
-    ASSERT_FALSE(program->IsValid());
-    auto result = Generate(program.get(), Options{});
+    auto program = resolver::Resolve(*this);
+    ASSERT_FALSE(program.IsValid());
+    auto result = Generate(program, Options{});
     EXPECT_FALSE(result);
     EXPECT_EQ(result.Failure(), "input program is not valid");
 }
@@ -33,8 +33,8 @@
 TEST_F(SpirvASTPrinterTest, UnsupportedExtension) {
     Enable(Source{{12, 34}}, wgsl::Extension::kUndefined);
 
-    auto program = std::make_unique<Program>(resolver::Resolve(*this));
-    auto result = Generate(program.get(), Options{});
+    auto program = resolver::Resolve(*this);
+    auto result = Generate(program, Options{});
     EXPECT_FALSE(result);
     EXPECT_EQ(result.Failure(),
               R"(12:34 error: SPIR-V backend does not support extension 'undefined')");
diff --git a/src/tint/lang/spirv/writer/ast_printer/builder.cc b/src/tint/lang/spirv/writer/ast_printer/builder.cc
index 9756664..76bfba3 100644
--- a/src/tint/lang/spirv/writer/ast_printer/builder.cc
+++ b/src/tint/lang/spirv/writer/ast_printer/builder.cc
@@ -36,7 +36,7 @@
 #include "src/tint/lang/wgsl/ast/traverse_expressions.h"
 #include "src/tint/lang/wgsl/helpers/append_vector.h"
 #include "src/tint/lang/wgsl/helpers/check_supported_extensions.h"
-#include "src/tint/lang/wgsl/sem/builtin.h"
+#include "src/tint/lang/wgsl/sem/builtin_fn.h"
 #include "src/tint/lang/wgsl/sem/call.h"
 #include "src/tint/lang/wgsl/sem/function.h"
 #include "src/tint/lang/wgsl/sem/load.h"
@@ -92,25 +92,25 @@
     return type->As<core::type::Matrix>();
 }
 
-uint32_t builtin_to_glsl_method(const sem::Builtin* builtin) {
-    switch (builtin->Type()) {
-        case core::Function::kAcos:
+uint32_t builtin_to_glsl_method(const sem::BuiltinFn* builtin) {
+    switch (builtin->Fn()) {
+        case core::BuiltinFn::kAcos:
             return GLSLstd450Acos;
-        case core::Function::kAcosh:
+        case core::BuiltinFn::kAcosh:
             return GLSLstd450Acosh;
-        case core::Function::kAsin:
+        case core::BuiltinFn::kAsin:
             return GLSLstd450Asin;
-        case core::Function::kAsinh:
+        case core::BuiltinFn::kAsinh:
             return GLSLstd450Asinh;
-        case core::Function::kAtan:
+        case core::BuiltinFn::kAtan:
             return GLSLstd450Atan;
-        case core::Function::kAtan2:
+        case core::BuiltinFn::kAtan2:
             return GLSLstd450Atan2;
-        case core::Function::kAtanh:
+        case core::BuiltinFn::kAtanh:
             return GLSLstd450Atanh;
-        case core::Function::kCeil:
+        case core::BuiltinFn::kCeil:
             return GLSLstd450Ceil;
-        case core::Function::kClamp:
+        case core::BuiltinFn::kClamp:
             if (builtin->ReturnType()->is_float_scalar_or_vector()) {
                 return GLSLstd450NClamp;
             } else if (builtin->ReturnType()->is_unsigned_integer_scalar_or_vector()) {
@@ -118,43 +118,43 @@
             } else {
                 return GLSLstd450SClamp;
             }
-        case core::Function::kCos:
+        case core::BuiltinFn::kCos:
             return GLSLstd450Cos;
-        case core::Function::kCosh:
+        case core::BuiltinFn::kCosh:
             return GLSLstd450Cosh;
-        case core::Function::kCross:
+        case core::BuiltinFn::kCross:
             return GLSLstd450Cross;
-        case core::Function::kDegrees:
+        case core::BuiltinFn::kDegrees:
             return GLSLstd450Degrees;
-        case core::Function::kDeterminant:
+        case core::BuiltinFn::kDeterminant:
             return GLSLstd450Determinant;
-        case core::Function::kDistance:
+        case core::BuiltinFn::kDistance:
             return GLSLstd450Distance;
-        case core::Function::kExp:
+        case core::BuiltinFn::kExp:
             return GLSLstd450Exp;
-        case core::Function::kExp2:
+        case core::BuiltinFn::kExp2:
             return GLSLstd450Exp2;
-        case core::Function::kFaceForward:
+        case core::BuiltinFn::kFaceForward:
             return GLSLstd450FaceForward;
-        case core::Function::kFloor:
+        case core::BuiltinFn::kFloor:
             return GLSLstd450Floor;
-        case core::Function::kFma:
+        case core::BuiltinFn::kFma:
             return GLSLstd450Fma;
-        case core::Function::kFract:
+        case core::BuiltinFn::kFract:
             return GLSLstd450Fract;
-        case core::Function::kFrexp:
+        case core::BuiltinFn::kFrexp:
             return GLSLstd450FrexpStruct;
-        case core::Function::kInverseSqrt:
+        case core::BuiltinFn::kInverseSqrt:
             return GLSLstd450InverseSqrt;
-        case core::Function::kLdexp:
+        case core::BuiltinFn::kLdexp:
             return GLSLstd450Ldexp;
-        case core::Function::kLength:
+        case core::BuiltinFn::kLength:
             return GLSLstd450Length;
-        case core::Function::kLog:
+        case core::BuiltinFn::kLog:
             return GLSLstd450Log;
-        case core::Function::kLog2:
+        case core::BuiltinFn::kLog2:
             return GLSLstd450Log2;
-        case core::Function::kMax:
+        case core::BuiltinFn::kMax:
             if (builtin->ReturnType()->is_float_scalar_or_vector()) {
                 return GLSLstd450NMax;
             } else if (builtin->ReturnType()->is_unsigned_integer_scalar_or_vector()) {
@@ -162,7 +162,7 @@
             } else {
                 return GLSLstd450SMax;
             }
-        case core::Function::kMin:
+        case core::BuiltinFn::kMin:
             if (builtin->ReturnType()->is_float_scalar_or_vector()) {
                 return GLSLstd450NMin;
             } else if (builtin->ReturnType()->is_unsigned_integer_scalar_or_vector()) {
@@ -170,63 +170,63 @@
             } else {
                 return GLSLstd450SMin;
             }
-        case core::Function::kMix:
+        case core::BuiltinFn::kMix:
             return GLSLstd450FMix;
-        case core::Function::kModf:
+        case core::BuiltinFn::kModf:
             return GLSLstd450ModfStruct;
-        case core::Function::kNormalize:
+        case core::BuiltinFn::kNormalize:
             return GLSLstd450Normalize;
-        case core::Function::kPack4X8Snorm:
+        case core::BuiltinFn::kPack4X8Snorm:
             return GLSLstd450PackSnorm4x8;
-        case core::Function::kPack4X8Unorm:
+        case core::BuiltinFn::kPack4X8Unorm:
             return GLSLstd450PackUnorm4x8;
-        case core::Function::kPack2X16Snorm:
+        case core::BuiltinFn::kPack2X16Snorm:
             return GLSLstd450PackSnorm2x16;
-        case core::Function::kPack2X16Unorm:
+        case core::BuiltinFn::kPack2X16Unorm:
             return GLSLstd450PackUnorm2x16;
-        case core::Function::kPack2X16Float:
+        case core::BuiltinFn::kPack2X16Float:
             return GLSLstd450PackHalf2x16;
-        case core::Function::kPow:
+        case core::BuiltinFn::kPow:
             return GLSLstd450Pow;
-        case core::Function::kRadians:
+        case core::BuiltinFn::kRadians:
             return GLSLstd450Radians;
-        case core::Function::kReflect:
+        case core::BuiltinFn::kReflect:
             return GLSLstd450Reflect;
-        case core::Function::kRefract:
+        case core::BuiltinFn::kRefract:
             return GLSLstd450Refract;
-        case core::Function::kRound:
+        case core::BuiltinFn::kRound:
             return GLSLstd450RoundEven;
-        case core::Function::kSign:
+        case core::BuiltinFn::kSign:
             if (builtin->ReturnType()->is_signed_integer_scalar_or_vector()) {
                 return GLSLstd450SSign;
             } else {
                 return GLSLstd450FSign;
             }
-        case core::Function::kSin:
+        case core::BuiltinFn::kSin:
             return GLSLstd450Sin;
-        case core::Function::kSinh:
+        case core::BuiltinFn::kSinh:
             return GLSLstd450Sinh;
-        case core::Function::kSmoothstep:
+        case core::BuiltinFn::kSmoothstep:
             return GLSLstd450SmoothStep;
-        case core::Function::kSqrt:
+        case core::BuiltinFn::kSqrt:
             return GLSLstd450Sqrt;
-        case core::Function::kStep:
+        case core::BuiltinFn::kStep:
             return GLSLstd450Step;
-        case core::Function::kTan:
+        case core::BuiltinFn::kTan:
             return GLSLstd450Tan;
-        case core::Function::kTanh:
+        case core::BuiltinFn::kTanh:
             return GLSLstd450Tanh;
-        case core::Function::kTrunc:
+        case core::BuiltinFn::kTrunc:
             return GLSLstd450Trunc;
-        case core::Function::kUnpack4X8Snorm:
+        case core::BuiltinFn::kUnpack4X8Snorm:
             return GLSLstd450UnpackSnorm4x8;
-        case core::Function::kUnpack4X8Unorm:
+        case core::BuiltinFn::kUnpack4X8Unorm:
             return GLSLstd450UnpackUnorm4x8;
-        case core::Function::kUnpack2X16Snorm:
+        case core::BuiltinFn::kUnpack2X16Snorm:
             return GLSLstd450UnpackSnorm2x16;
-        case core::Function::kUnpack2X16Unorm:
+        case core::BuiltinFn::kUnpack2X16Unorm:
             return GLSLstd450UnpackUnorm2x16;
-        case core::Function::kUnpack2X16Float:
+        case core::BuiltinFn::kUnpack2X16Float:
             return GLSLstd450UnpackHalf2x16;
         default:
             break;
@@ -248,7 +248,7 @@
 
 Builder::AccessorInfo::~AccessorInfo() {}
 
-Builder::Builder(const Program* program,
+Builder::Builder(const Program& program,
                  bool zero_initialize_workgroup_memory,
                  bool experimental_require_subgroup_uniform_control_flow)
     : builder_(ProgramBuilder::Wrap(program)),
@@ -2213,7 +2213,7 @@
     return Switch(
         target,  //
         [&](const sem::Function* func) { return GenerateFunctionCall(call, func); },
-        [&](const sem::Builtin* builtin) { return GenerateBuiltinCall(call, builtin); },
+        [&](const sem::BuiltinFn* builtin) { return GenerateBuiltinCall(call, builtin); },
         [&](const sem::ValueConversion*) {
             return GenerateValueConstructorOrConversion(call, nullptr);
         },
@@ -2262,7 +2262,7 @@
     return result_id;
 }
 
-uint32_t Builder::GenerateBuiltinCall(const sem::Call* call, const sem::Builtin* builtin) {
+uint32_t Builder::GenerateBuiltinCall(const sem::Call* call, const sem::BuiltinFn* builtin) {
     auto result = result_op();
     auto result_id = std::get<uint32_t>(result);
 
@@ -2328,22 +2328,22 @@
         op = spv::Op::OpExtInst;
     };
 
-    switch (builtin->Type()) {
-        case core::Function::kAny:
+    switch (builtin->Fn()) {
+        case core::BuiltinFn::kAny:
             if (builtin->Parameters()[0]->Type()->Is<core::type::Bool>()) {
                 // any(v: bool) just resolves to v.
                 return get_arg_as_value_id(0);
             }
             op = spv::Op::OpAny;
             break;
-        case core::Function::kAll:
+        case core::BuiltinFn::kAll:
             if (builtin->Parameters()[0]->Type()->Is<core::type::Bool>()) {
                 // all(v: bool) just resolves to v.
                 return get_arg_as_value_id(0);
             }
             op = spv::Op::OpAll;
             break;
-        case core::Function::kArrayLength: {
+        case core::BuiltinFn::kArrayLength: {
             auto* address_of = call->Arguments()[0]->Declaration()->As<ast::UnaryOpExpression>();
             if (!address_of || address_of->op != core::UnaryOp::kAddressOf) {
                 TINT_ICE() << "arrayLength() expected pointer to member access, got " +
@@ -2380,10 +2380,10 @@
             }
             return result_id;
         }
-        case core::Function::kCountOneBits:
+        case core::BuiltinFn::kCountOneBits:
             op = spv::Op::OpBitCount;
             break;
-        case core::Function::kDot: {
+        case core::BuiltinFn::kDot: {
             op = spv::Op::OpDot;
             auto* vec_ty = builtin->Parameters()[0]->Type()->As<core::type::Vector>();
             if (vec_ty->type()->is_integer_scalar()) {
@@ -2424,42 +2424,42 @@
             }
             break;
         }
-        case core::Function::kDpdx:
+        case core::BuiltinFn::kDpdx:
             op = spv::Op::OpDPdx;
             break;
-        case core::Function::kDpdxCoarse:
+        case core::BuiltinFn::kDpdxCoarse:
             op = spv::Op::OpDPdxCoarse;
             break;
-        case core::Function::kDpdxFine:
+        case core::BuiltinFn::kDpdxFine:
             op = spv::Op::OpDPdxFine;
             break;
-        case core::Function::kDpdy:
+        case core::BuiltinFn::kDpdy:
             op = spv::Op::OpDPdy;
             break;
-        case core::Function::kDpdyCoarse:
+        case core::BuiltinFn::kDpdyCoarse:
             op = spv::Op::OpDPdyCoarse;
             break;
-        case core::Function::kDpdyFine:
+        case core::BuiltinFn::kDpdyFine:
             op = spv::Op::OpDPdyFine;
             break;
-        case core::Function::kExtractBits:
+        case core::BuiltinFn::kExtractBits:
             op = builtin->Parameters()[0]->Type()->is_unsigned_integer_scalar_or_vector()
                      ? spv::Op::OpBitFieldUExtract
                      : spv::Op::OpBitFieldSExtract;
             break;
-        case core::Function::kFwidth:
+        case core::BuiltinFn::kFwidth:
             op = spv::Op::OpFwidth;
             break;
-        case core::Function::kFwidthCoarse:
+        case core::BuiltinFn::kFwidthCoarse:
             op = spv::Op::OpFwidthCoarse;
             break;
-        case core::Function::kFwidthFine:
+        case core::BuiltinFn::kFwidthFine:
             op = spv::Op::OpFwidthFine;
             break;
-        case core::Function::kInsertBits:
+        case core::BuiltinFn::kInsertBits:
             op = spv::Op::OpBitFieldInsert;
             break;
-        case core::Function::kMix: {
+        case core::BuiltinFn::kMix: {
             auto std450 = Operand(GetGLSLstd450Import());
 
             auto a_id = get_arg_as_value_id(0);
@@ -2486,13 +2486,13 @@
             }
             return result_id;
         }
-        case core::Function::kQuantizeToF16:
+        case core::BuiltinFn::kQuantizeToF16:
             op = spv::Op::OpQuantizeToF16;
             break;
-        case core::Function::kReverseBits:
+        case core::BuiltinFn::kReverseBits:
             op = spv::Op::OpBitReverse;
             break;
-        case core::Function::kSelect: {
+        case core::BuiltinFn::kSelect: {
             // Note: Argument order is different in WGSL and SPIR-V
             auto cond_id = get_arg_as_value_id(2);
             auto true_id = get_arg_as_value_id(1);
@@ -2524,10 +2524,10 @@
             }
             return result_id;
         }
-        case core::Function::kTranspose:
+        case core::BuiltinFn::kTranspose:
             op = spv::Op::OpTranspose;
             break;
-        case core::Function::kAbs:
+        case core::BuiltinFn::kAbs:
             if (builtin->ReturnType()->is_unsigned_integer_scalar_or_vector()) {
                 // abs() only operates on *signed* integers.
                 // This is a no-op for unsigned integers.
@@ -2539,7 +2539,7 @@
                 glsl_std450(GLSLstd450SAbs);
             }
             break;
-        case core::Function::kDot4I8Packed: {
+        case core::BuiltinFn::kDot4I8Packed: {
             auto first_param_id = get_arg_as_value_id(0);
             auto second_param_id = get_arg_as_value_id(1);
             if (!push_function_inst(spv::Op::OpSDotKHR,
@@ -2551,7 +2551,7 @@
             }
             return result_id;
         }
-        case core::Function::kDot4U8Packed: {
+        case core::BuiltinFn::kDot4U8Packed: {
             auto first_param_id = get_arg_as_value_id(0);
             auto second_param_id = get_arg_as_value_id(1);
             if (!push_function_inst(spv::Op::OpUDotKHR,
@@ -2563,7 +2563,7 @@
             }
             return result_id;
         }
-        case core::Function::kSubgroupBallot: {
+        case core::BuiltinFn::kSubgroupBallot: {
             module_.PushCapability(SpvCapabilityGroupNonUniformBallot);
             if (!push_function_inst(
                     spv::Op::OpGroupNonUniformBallot,
@@ -2574,7 +2574,7 @@
             }
             return result_id;
         }
-        case core::Function::kSubgroupBroadcast: {
+        case core::BuiltinFn::kSubgroupBroadcast: {
             module_.PushCapability(SpvCapabilityGroupNonUniformBallot);
             auto first_param_id = get_arg_as_value_id(0);
             auto second_param_id = get_arg_as_value_id(1);
@@ -2619,7 +2619,7 @@
 }
 
 bool Builder::GenerateTextureBuiltin(const sem::Call* call,
-                                     const sem::Builtin* builtin,
+                                     const sem::BuiltinFn* builtin,
                                      Operand result_type,
                                      Operand result_id) {
     using Usage = core::ParameterUsage;
@@ -2777,8 +2777,8 @@
         return append_coords_to_spirv_params();
     };
 
-    switch (builtin->Type()) {
-        case core::Function::kTextureDimensions: {
+    switch (builtin->Fn()) {
+        case core::BuiltinFn::kTextureDimensions: {
             // Number of returned elements from OpImageQuerySize[Lod] may not match
             // those of textureDimensions().
             // This might be due to an extra vector scalar describing the number of
@@ -2823,7 +2823,7 @@
             }
             break;
         }
-        case core::Function::kTextureNumLayers: {
+        case core::BuiltinFn::kTextureNumLayers: {
             uint32_t spirv_dims = 0;
             switch (texture_type->dim()) {
                 default:
@@ -2853,19 +2853,19 @@
             }
             break;
         }
-        case core::Function::kTextureNumLevels: {
+        case core::BuiltinFn::kTextureNumLevels: {
             op = spv::Op::OpImageQueryLevels;
             append_result_type_and_id_to_spirv_params();
             spirv_params.emplace_back(gen_arg(Usage::kTexture));
             break;
         }
-        case core::Function::kTextureNumSamples: {
+        case core::BuiltinFn::kTextureNumSamples: {
             op = spv::Op::OpImageQuerySamples;
             append_result_type_and_id_to_spirv_params();
             spirv_params.emplace_back(gen_arg(Usage::kTexture));
             break;
         }
-        case core::Function::kTextureLoad: {
+        case core::BuiltinFn::kTextureLoad: {
             op = texture_type->Is<core::type::StorageTexture>() ? spv::Op::OpImageRead
                                                                 : spv::Op::OpImageFetch;
             append_result_type_and_id_to_spirv_params_for_read();
@@ -2885,7 +2885,7 @@
 
             break;
         }
-        case core::Function::kTextureStore: {
+        case core::BuiltinFn::kTextureStore: {
             op = spv::Op::OpImageWrite;
             spirv_params.emplace_back(gen_arg(Usage::kTexture));
             if (!append_coords_to_spirv_params()) {
@@ -2894,7 +2894,7 @@
             spirv_params.emplace_back(gen_arg(Usage::kValue));
             break;
         }
-        case core::Function::kTextureGather: {
+        case core::BuiltinFn::kTextureGather: {
             op = spv::Op::OpImageGather;
             append_result_type_and_id_to_spirv_params();
             if (!append_image_and_coords_to_spirv_params()) {
@@ -2908,7 +2908,7 @@
             }
             break;
         }
-        case core::Function::kTextureGatherCompare: {
+        case core::BuiltinFn::kTextureGatherCompare: {
             op = spv::Op::OpImageDrefGather;
             append_result_type_and_id_to_spirv_params();
             if (!append_image_and_coords_to_spirv_params()) {
@@ -2917,7 +2917,7 @@
             spirv_params.emplace_back(gen_arg(Usage::kDepthRef));
             break;
         }
-        case core::Function::kTextureSample: {
+        case core::BuiltinFn::kTextureSample: {
             op = spv::Op::OpImageSampleImplicitLod;
             append_result_type_and_id_to_spirv_params_for_read();
             if (!append_image_and_coords_to_spirv_params()) {
@@ -2925,7 +2925,7 @@
             }
             break;
         }
-        case core::Function::kTextureSampleBias: {
+        case core::BuiltinFn::kTextureSampleBias: {
             op = spv::Op::OpImageSampleImplicitLod;
             append_result_type_and_id_to_spirv_params_for_read();
             if (!append_image_and_coords_to_spirv_params()) {
@@ -2935,7 +2935,7 @@
                 ImageOperand{SpvImageOperandsBiasMask, gen_arg(Usage::kBias)});
             break;
         }
-        case core::Function::kTextureSampleLevel: {
+        case core::BuiltinFn::kTextureSampleLevel: {
             op = spv::Op::OpImageSampleExplicitLod;
             append_result_type_and_id_to_spirv_params_for_read();
             if (!append_image_and_coords_to_spirv_params()) {
@@ -2963,7 +2963,7 @@
             image_operands.emplace_back(ImageOperand{SpvImageOperandsLodMask, level});
             break;
         }
-        case core::Function::kTextureSampleGrad: {
+        case core::BuiltinFn::kTextureSampleGrad: {
             op = spv::Op::OpImageSampleExplicitLod;
             append_result_type_and_id_to_spirv_params_for_read();
             if (!append_image_and_coords_to_spirv_params()) {
@@ -2975,7 +2975,7 @@
                 ImageOperand{SpvImageOperandsGradMask, gen_arg(Usage::kDdy)});
             break;
         }
-        case core::Function::kTextureSampleCompare: {
+        case core::BuiltinFn::kTextureSampleCompare: {
             op = spv::Op::OpImageSampleDrefImplicitLod;
             append_result_type_and_id_to_spirv_params();
             if (!append_image_and_coords_to_spirv_params()) {
@@ -2984,7 +2984,7 @@
             spirv_params.emplace_back(gen_arg(Usage::kDepthRef));
             break;
         }
-        case core::Function::kTextureSampleCompareLevel: {
+        case core::BuiltinFn::kTextureSampleCompareLevel: {
             op = spv::Op::OpImageSampleDrefExplicitLod;
             append_result_type_and_id_to_spirv_params();
             if (!append_image_and_coords_to_spirv_params()) {
@@ -3031,7 +3031,7 @@
     return post_emission();
 }
 
-bool Builder::GenerateControlBarrierBuiltin(const sem::Builtin* builtin) {
+bool Builder::GenerateControlBarrierBuiltin(const sem::BuiltinFn* builtin) {
     auto const op = spv::Op::OpControlBarrier;
     uint32_t execution = 0;
     uint32_t memory = 0;
@@ -3039,23 +3039,23 @@
 
     // TODO(crbug.com/tint/661): Combine sequential barriers to a single
     // instruction.
-    if (builtin->Type() == core::Function::kWorkgroupBarrier) {
+    if (builtin->Fn() == core::BuiltinFn::kWorkgroupBarrier) {
         execution = static_cast<uint32_t>(spv::Scope::Workgroup);
         memory = static_cast<uint32_t>(spv::Scope::Workgroup);
         semantics = static_cast<uint32_t>(spv::MemorySemanticsMask::AcquireRelease) |
                     static_cast<uint32_t>(spv::MemorySemanticsMask::WorkgroupMemory);
-    } else if (builtin->Type() == core::Function::kStorageBarrier) {
+    } else if (builtin->Fn() == core::BuiltinFn::kStorageBarrier) {
         execution = static_cast<uint32_t>(spv::Scope::Workgroup);
         memory = static_cast<uint32_t>(spv::Scope::Workgroup);
         semantics = static_cast<uint32_t>(spv::MemorySemanticsMask::AcquireRelease) |
                     static_cast<uint32_t>(spv::MemorySemanticsMask::UniformMemory);
-    } else if (builtin->Type() == core::Function::kTextureBarrier) {
+    } else if (builtin->Fn() == core::BuiltinFn::kTextureBarrier) {
         execution = static_cast<uint32_t>(spv::Scope::Workgroup);
         memory = static_cast<uint32_t>(spv::Scope::Workgroup);
         semantics = static_cast<uint32_t>(spv::MemorySemanticsMask::AcquireRelease) |
                     static_cast<uint32_t>(spv::MemorySemanticsMask::ImageMemory);
     } else {
-        TINT_ICE() << "unexpected barrier builtin type " << core::str(builtin->Type());
+        TINT_ICE() << "unexpected barrier builtin type " << core::str(builtin->Fn());
         return false;
     }
 
@@ -3074,7 +3074,7 @@
 }
 
 bool Builder::GenerateAtomicBuiltin(const sem::Call* call,
-                                    const sem::Builtin* builtin,
+                                    const sem::BuiltinFn* builtin,
                                     Operand result_type,
                                     Operand result_id) {
     auto is_value_signed = [&] { return builtin->Parameters()[1]->Type()->Is<core::type::I32>(); };
@@ -3124,8 +3124,8 @@
     Operand memory = Operand(memory_id);
     Operand semantics = Operand(semantics_id);
 
-    switch (builtin->Type()) {
-        case core::Function::kAtomicLoad:
+    switch (builtin->Fn()) {
+        case core::BuiltinFn::kAtomicLoad:
             return push_function_inst(spv::Op::OpAtomicLoad, {
                                                                  result_type,
                                                                  result_id,
@@ -3133,14 +3133,14 @@
                                                                  memory,
                                                                  semantics,
                                                              });
-        case core::Function::kAtomicStore:
+        case core::BuiltinFn::kAtomicStore:
             return push_function_inst(spv::Op::OpAtomicStore, {
                                                                   pointer,
                                                                   memory,
                                                                   semantics,
                                                                   value,
                                                               });
-        case core::Function::kAtomicAdd:
+        case core::BuiltinFn::kAtomicAdd:
             return push_function_inst(spv::Op::OpAtomicIAdd, {
                                                                  result_type,
                                                                  result_id,
@@ -3149,7 +3149,7 @@
                                                                  semantics,
                                                                  value,
                                                              });
-        case core::Function::kAtomicSub:
+        case core::BuiltinFn::kAtomicSub:
             return push_function_inst(spv::Op::OpAtomicISub, {
                                                                  result_type,
                                                                  result_id,
@@ -3158,7 +3158,7 @@
                                                                  semantics,
                                                                  value,
                                                              });
-        case core::Function::kAtomicMax:
+        case core::BuiltinFn::kAtomicMax:
             return push_function_inst(
                 is_value_signed() ? spv::Op::OpAtomicSMax : spv::Op::OpAtomicUMax, {
                                                                                        result_type,
@@ -3168,7 +3168,7 @@
                                                                                        semantics,
                                                                                        value,
                                                                                    });
-        case core::Function::kAtomicMin:
+        case core::BuiltinFn::kAtomicMin:
             return push_function_inst(
                 is_value_signed() ? spv::Op::OpAtomicSMin : spv::Op::OpAtomicUMin, {
                                                                                        result_type,
@@ -3178,7 +3178,7 @@
                                                                                        semantics,
                                                                                        value,
                                                                                    });
-        case core::Function::kAtomicAnd:
+        case core::BuiltinFn::kAtomicAnd:
             return push_function_inst(spv::Op::OpAtomicAnd, {
                                                                 result_type,
                                                                 result_id,
@@ -3187,7 +3187,7 @@
                                                                 semantics,
                                                                 value,
                                                             });
-        case core::Function::kAtomicOr:
+        case core::BuiltinFn::kAtomicOr:
             return push_function_inst(spv::Op::OpAtomicOr, {
                                                                result_type,
                                                                result_id,
@@ -3196,7 +3196,7 @@
                                                                semantics,
                                                                value,
                                                            });
-        case core::Function::kAtomicXor:
+        case core::BuiltinFn::kAtomicXor:
             return push_function_inst(spv::Op::OpAtomicXor, {
                                                                 result_type,
                                                                 result_id,
@@ -3205,7 +3205,7 @@
                                                                 semantics,
                                                                 value,
                                                             });
-        case core::Function::kAtomicExchange:
+        case core::BuiltinFn::kAtomicExchange:
             return push_function_inst(spv::Op::OpAtomicExchange, {
                                                                      result_type,
                                                                      result_id,
@@ -3214,7 +3214,7 @@
                                                                      semantics,
                                                                      value,
                                                                  });
-        case core::Function::kAtomicCompareExchangeWeak: {
+        case core::BuiltinFn::kAtomicCompareExchangeWeak: {
             auto comparator = GenerateExpression(call->Arguments()[1]);
             if (comparator == 0) {
                 return false;
@@ -3275,7 +3275,7 @@
                                                                      });
         }
         default:
-            TINT_UNREACHABLE() << "unhandled atomic builtin " << builtin->Type();
+            TINT_UNREACHABLE() << "unhandled atomic builtin " << builtin->Fn();
             return false;
     }
 }
diff --git a/src/tint/lang/spirv/writer/ast_printer/builder.h b/src/tint/lang/spirv/writer/ast_printer/builder.h
index c736697..ae4b639 100644
--- a/src/tint/lang/spirv/writer/ast_printer/builder.h
+++ b/src/tint/lang/spirv/writer/ast_printer/builder.h
@@ -40,7 +40,7 @@
 #include "src/tint/lang/wgsl/ast/unary_op_expression.h"
 #include "src/tint/lang/wgsl/ast/variable_decl_statement.h"
 #include "src/tint/lang/wgsl/program/program_builder.h"
-#include "src/tint/lang/wgsl/sem/builtin.h"
+#include "src/tint/lang/wgsl/sem/builtin_fn.h"
 #include "src/tint/utils/containers/scope_stack.h"
 
 // Forward declarations
@@ -85,7 +85,7 @@
     /// @param experimental_require_subgroup_uniform_control_flow `true` to require
     /// `SPV_KHR_subgroup_uniform_control_flow` extension and `SubgroupUniformControlFlowKHR`
     /// execution mode for compute stage entry points.
-    explicit Builder(const Program* program,
+    explicit Builder(const Program& program,
                      bool zero_initialize_workgroup_memory = false,
                      bool experimental_require_subgroup_uniform_control_flow = false);
     ~Builder();
@@ -296,7 +296,7 @@
     /// @param call the call expression
     /// @param builtin the builtin being called
     /// @returns the expression ID on success or 0 otherwise
-    uint32_t GenerateBuiltinCall(const sem::Call* call, const sem::Builtin* builtin);
+    uint32_t GenerateBuiltinCall(const sem::Call* call, const sem::BuiltinFn* builtin);
     /// Handles generating a value constructor or value conversion expression
     /// @param call the call expression
     /// @param var the variable that is being initialized. May be null.
@@ -311,13 +311,13 @@
     /// parameters
     /// @returns true on success
     bool GenerateTextureBuiltin(const sem::Call* call,
-                                const sem::Builtin* builtin,
+                                const sem::BuiltinFn* builtin,
                                 Operand result_type,
                                 Operand result_id);
     /// Generates a control barrier statement.
     /// @param builtin the semantic information for the barrier builtin call
     /// @returns true on success
-    bool GenerateControlBarrierBuiltin(const sem::Builtin* builtin);
+    bool GenerateControlBarrierBuiltin(const sem::BuiltinFn* builtin);
     /// Generates an atomic builtin call.
     /// @param call the call expression
     /// @param builtin the semantic information for the atomic builtin call
@@ -325,7 +325,7 @@
     /// @param result_id result identifier operand of the texture instruction
     /// @returns true on success
     bool GenerateAtomicBuiltin(const sem::Call* call,
-                               const sem::Builtin* builtin,
+                               const sem::BuiltinFn* builtin,
                                Operand result_type,
                                Operand result_id);
     /// Generates a sampled image
diff --git a/src/tint/lang/spirv/writer/ast_printer/builtin_texture_test.cc b/src/tint/lang/spirv/writer/ast_printer/builtin_texture_test.cc
index 55033a9..e86ce06 100644
--- a/src/tint/lang/spirv/writer/ast_printer/builtin_texture_test.cc
+++ b/src/tint/lang/spirv/writer/ast_printer/builtin_texture_test.cc
@@ -3786,12 +3786,12 @@
                         pb.Stage(ast::PipelineStage::kFragment),
                     });
 
-            auto program = std::make_unique<Program>(resolver::Resolve(pb));
-            auto b = std::make_unique<Builder>(program.get());
+            auto program = resolver::Resolve(pb);
+            Builder b(program);
 
-            b->GenerateGlobalVariable(texture);
-            b->GenerateGlobalVariable(sampler);
-            b->GenerateExpression(call);
+            b.GenerateGlobalVariable(texture);
+            b.GenerateGlobalVariable(sampler);
+            b.GenerateExpression(call);
         },
         "Internal error: trying to add SPIR-V instruction ");
 }
diff --git a/src/tint/lang/spirv/writer/ast_printer/function_attribute_test.cc b/src/tint/lang/spirv/writer/ast_printer/function_attribute_test.cc
index d89bba8..aef2cf4 100644
--- a/src/tint/lang/spirv/writer/ast_printer/function_attribute_test.cc
+++ b/src/tint/lang/spirv/writer/ast_printer/function_attribute_test.cc
@@ -164,10 +164,10 @@
                                      pb.WorkgroupSize("width", "height", "depth"),
                                      pb.Stage(ast::PipelineStage::kCompute),
                                  });
-            auto program = std::make_unique<Program>(resolver::Resolve(pb));
-            auto b = std::make_unique<Builder>(program.get());
+            auto program = resolver::Resolve(pb);
+            Builder b(program);
 
-            b->GenerateExecutionModes(func, 3);
+            b.GenerateExecutionModes(func, 3);
         },
         "override-expressions should have been removed with the SubstituteOverride transform");
 }
@@ -185,10 +185,10 @@
                                      pb.Stage(ast::PipelineStage::kCompute),
                                  });
 
-            auto program = std::make_unique<Program>(resolver::Resolve(pb));
-            auto b = std::make_unique<Builder>(program.get());
+            auto program = resolver::Resolve(pb);
+            Builder b(program);
 
-            b->GenerateExecutionModes(func, 3);
+            b.GenerateExecutionModes(func, 3);
         },
         "override-expressions should have been removed with the SubstituteOverride transform");
 }
diff --git a/src/tint/lang/spirv/writer/ast_printer/global_variable_test.cc b/src/tint/lang/spirv/writer/ast_printer/global_variable_test.cc
index 97ea5db..a29cf99 100644
--- a/src/tint/lang/spirv/writer/ast_printer/global_variable_test.cc
+++ b/src/tint/lang/spirv/writer/ast_printer/global_variable_test.cc
@@ -513,7 +513,7 @@
 
     constexpr bool kZeroInitializeWorkgroupMemory = true;
     std::unique_ptr<Builder> b =
-        std::make_unique<Builder>(program.get(), kZeroInitializeWorkgroupMemory);
+        std::make_unique<Builder>(*program, kZeroInitializeWorkgroupMemory);
 
     EXPECT_TRUE(b->GenerateGlobalVariable(var_scalar)) << b->Diagnostics();
     EXPECT_TRUE(b->GenerateGlobalVariable(var_array)) << b->Diagnostics();
diff --git a/src/tint/lang/spirv/writer/ast_printer/helper_test.h b/src/tint/lang/spirv/writer/ast_printer/helper_test.h
index e97bb77..28ac79b 100644
--- a/src/tint/lang/spirv/writer/ast_printer/helper_test.h
+++ b/src/tint/lang/spirv/writer/ast_printer/helper_test.h
@@ -53,12 +53,14 @@
         if (spirv_builder) {
             return *spirv_builder;
         }
-        [&] {
-            ASSERT_TRUE(IsValid()) << "Builder program is not valid\n" << Diagnostics().str();
-        }();
+        if (!IsValid()) {
+            ADD_FAILURE() << "ProgramBuilder is not valid: " << Diagnostics();
+        }
         program = std::make_unique<Program>(resolver::Resolve(*this));
-        [&] { ASSERT_TRUE(program->IsValid()) << program->Diagnostics().str(); }();
-        spirv_builder = std::make_unique<Builder>(program.get());
+        if (!program->IsValid()) {
+            ADD_FAILURE() << program->Diagnostics();
+        }
+        spirv_builder = std::make_unique<Builder>(*program);
         return *spirv_builder;
     }
 
@@ -72,19 +74,23 @@
         if (spirv_builder) {
             return *spirv_builder;
         }
-        [&] {
-            ASSERT_TRUE(IsValid()) << "Builder program is not valid\n" << Diagnostics().str();
-        }();
+        if (!IsValid()) {
+            ADD_FAILURE() << "ProgramBuilder is not valid: " << Diagnostics();
+        }
         program = std::make_unique<Program>(resolver::Resolve(*this));
-        [&] { ASSERT_TRUE(program->IsValid()) << program->Diagnostics().str(); }();
-        auto result = Sanitize(program.get(), options);
-        [&] { ASSERT_TRUE(result.program.IsValid()) << result.program.Diagnostics().str(); }();
+        if (!program->IsValid()) {
+            ADD_FAILURE() << program->Diagnostics();
+        }
+        auto result = Sanitize(*program, options);
+        if (!result.program.IsValid()) {
+            ADD_FAILURE() << result.program.Diagnostics();
+        }
         *program = std::move(result.program);
         bool zero_initialize_workgroup_memory =
             !options.disable_workgroup_init &&
             options.use_zero_initialize_workgroup_memory_extension;
         spirv_builder =
-            std::make_unique<Builder>(program.get(), zero_initialize_workgroup_memory,
+            std::make_unique<Builder>(*program, zero_initialize_workgroup_memory,
                                       options.experimental_require_subgroup_uniform_control_flow);
         return *spirv_builder;
     }
diff --git a/src/tint/lang/spirv/writer/ast_printer/ident_expression_test.cc b/src/tint/lang/spirv/writer/ast_printer/ident_expression_test.cc
index fafc79b..6e3b3ca 100644
--- a/src/tint/lang/spirv/writer/ast_printer/ident_expression_test.cc
+++ b/src/tint/lang/spirv/writer/ast_printer/ident_expression_test.cc
@@ -36,11 +36,11 @@
             auto* expr = pb.Expr("c");
             pb.WrapInFunction(expr);
 
-            auto program = std::make_unique<Program>(resolver::Resolve(pb));
-            auto b = std::make_unique<Builder>(program.get());
+            auto program = resolver::Resolve(pb);
+            Builder b(program);
 
-            b->GenerateGlobalVariable(v);
-            b->GenerateIdentifierExpression(expr);
+            b.GenerateGlobalVariable(v);
+            b.GenerateIdentifierExpression(expr);
         },
         "internal compiler error: unable to find ID for variable: c");
 }
diff --git a/src/tint/lang/spirv/writer/ast_raise/clamp_frag_depth.cc b/src/tint/lang/spirv/writer/ast_raise/clamp_frag_depth.cc
index e49c951..f049300 100644
--- a/src/tint/lang/spirv/writer/ast_raise/clamp_frag_depth.cc
+++ b/src/tint/lang/spirv/writer/ast_raise/clamp_frag_depth.cc
@@ -38,23 +38,23 @@
 /// PIMPL state for the transform
 struct ClampFragDepth::State {
     /// The source program
-    const Program* const src;
+    const Program& src;
     /// The target program builder
     ProgramBuilder b{};
     /// The clone context
-    program::CloneContext ctx = {&b, src, /* auto_clone_symbols */ true};
+    program::CloneContext ctx = {&b, &src, /* auto_clone_symbols */ true};
     /// The sem::Info of the program
-    const sem::Info& sem = src->Sem();
+    const sem::Info& sem = src.Sem();
     /// The symbols of the program
-    const SymbolTable& sym = src->Symbols();
+    const SymbolTable& sym = src.Symbols();
 
     /// Runs the transform
     /// @returns the new program or SkipTransform if the transform is not required
     Transform::ApplyResult Run() {
         // Abort on any use of push constants in the module.
-        for (auto* global : src->AST().GlobalVariables()) {
+        for (auto* global : src.AST().GlobalVariables()) {
             if (auto* var = global->As<ast::Var>()) {
-                auto* v = src->Sem().Get(var);
+                auto* v = src.Sem().Get(var);
                 if (TINT_UNLIKELY(v->AddressSpace() == core::AddressSpace::kPushConstant)) {
                     TINT_ICE()
                         << "ClampFragDepth doesn't know how to handle module that already use push "
@@ -174,7 +174,7 @@
   private:
     /// @returns true if the transform should run
     bool ShouldRun() {
-        for (auto* fn : src->AST().Functions()) {
+        for (auto* fn : src.AST().Functions()) {
             if (fn->PipelineStage() == ast::PipelineStage::kFragment &&
                 (ReturnsFragDepthAsValue(fn) || ReturnsFragDepthInStruct(fn))) {
                 return true;
@@ -223,7 +223,7 @@
 ClampFragDepth::ClampFragDepth() = default;
 ClampFragDepth::~ClampFragDepth() = default;
 
-ast::transform::Transform::ApplyResult ClampFragDepth::Apply(const Program* src,
+ast::transform::Transform::ApplyResult ClampFragDepth::Apply(const Program& src,
                                                              const ast::transform::DataMap&,
                                                              ast::transform::DataMap&) const {
     return State{src}.Run();
diff --git a/src/tint/lang/spirv/writer/ast_raise/clamp_frag_depth.h b/src/tint/lang/spirv/writer/ast_raise/clamp_frag_depth.h
index cb3ce85..86b8ee2 100644
--- a/src/tint/lang/spirv/writer/ast_raise/clamp_frag_depth.h
+++ b/src/tint/lang/spirv/writer/ast_raise/clamp_frag_depth.h
@@ -57,7 +57,7 @@
     ~ClampFragDepth() override;
 
     /// @copydoc ast::transform::Transform::Apply
-    ApplyResult Apply(const Program* program,
+    ApplyResult Apply(const Program& program,
                       const ast::transform::DataMap& inputs,
                       ast::transform::DataMap& outputs) const override;
 
diff --git a/src/tint/lang/spirv/writer/ast_raise/for_loop_to_loop.cc b/src/tint/lang/spirv/writer/ast_raise/for_loop_to_loop.cc
index 36fc251..619b6dd 100644
--- a/src/tint/lang/spirv/writer/ast_raise/for_loop_to_loop.cc
+++ b/src/tint/lang/spirv/writer/ast_raise/for_loop_to_loop.cc
@@ -26,8 +26,8 @@
 namespace tint::spirv::writer {
 namespace {
 
-bool ShouldRun(const Program* program) {
-    for (auto* node : program->ASTNodes().Objects()) {
+bool ShouldRun(const Program& program) {
+    for (auto* node : program.ASTNodes().Objects()) {
         if (node->Is<ast::ForLoopStatement>()) {
             return true;
         }
@@ -41,7 +41,7 @@
 
 ForLoopToLoop::~ForLoopToLoop() = default;
 
-ast::transform::Transform::ApplyResult ForLoopToLoop::Apply(const Program* src,
+ast::transform::Transform::ApplyResult ForLoopToLoop::Apply(const Program& src,
                                                             const ast::transform::DataMap&,
                                                             ast::transform::DataMap&) const {
     if (!ShouldRun(src)) {
@@ -49,7 +49,7 @@
     }
 
     ProgramBuilder b;
-    program::CloneContext ctx{&b, src, /* auto_clone_symbols */ true};
+    program::CloneContext ctx{&b, &src, /* auto_clone_symbols */ true};
 
     ctx.ReplaceAll([&](const ast::ForLoopStatement* for_loop) -> const ast::Statement* {
         tint::Vector<const ast::Statement*, 8> stmts;
diff --git a/src/tint/lang/spirv/writer/ast_raise/for_loop_to_loop.h b/src/tint/lang/spirv/writer/ast_raise/for_loop_to_loop.h
index 776e1f3..7ca201a 100644
--- a/src/tint/lang/spirv/writer/ast_raise/for_loop_to_loop.h
+++ b/src/tint/lang/spirv/writer/ast_raise/for_loop_to_loop.h
@@ -29,7 +29,7 @@
     ~ForLoopToLoop() override;
 
     /// @copydoc ast::transform::Transform::Apply
-    ApplyResult Apply(const Program* program,
+    ApplyResult Apply(const Program& program,
                       const ast::transform::DataMap& inputs,
                       ast::transform::DataMap& outputs) const override;
 };
diff --git a/src/tint/lang/spirv/writer/ast_raise/merge_return.cc b/src/tint/lang/spirv/writer/ast_raise/merge_return.cc
index 2d30235..7656750 100644
--- a/src/tint/lang/spirv/writer/ast_raise/merge_return.cc
+++ b/src/tint/lang/spirv/writer/ast_raise/merge_return.cc
@@ -32,12 +32,12 @@
 namespace {
 
 /// Returns `true` if `stmt` has the behavior `behavior`.
-bool HasBehavior(const Program* program, const ast::Statement* stmt, sem::Behavior behavior) {
-    return program->Sem().Get(stmt)->Behaviors().Contains(behavior);
+bool HasBehavior(const Program& program, const ast::Statement* stmt, sem::Behavior behavior) {
+    return program.Sem().Get(stmt)->Behaviors().Contains(behavior);
 }
 
 /// Returns `true` if `func` needs to be transformed.
-bool NeedsTransform(const Program* program, const ast::Function* func) {
+bool NeedsTransform(const Program& program, const ast::Function* func) {
     // Entry points and intrinsic declarations never need transforming.
     if (func->IsEntryPoint() || func->body == nullptr) {
         return false;
@@ -99,7 +99,7 @@
 
     /// Process a statement (recursively).
     void ProcessStatement(const ast::Statement* stmt) {
-        if (stmt == nullptr || !HasBehavior(ctx.src, stmt, sem::Behavior::kReturn)) {
+        if (stmt == nullptr || !HasBehavior(*ctx.src, stmt, sem::Behavior::kReturn)) {
             return;
         }
 
@@ -171,7 +171,7 @@
             // Check if the statement is or contains a return statement.
             // We need to make sure any statements that follow this one do not get executed if the
             // return flag has been set.
-            if (HasBehavior(ctx.src, s, sem::Behavior::kReturn)) {
+            if (HasBehavior(*ctx.src, s, sem::Behavior::kReturn)) {
                 if (is_in_loop_or_switch) {
                     // We're in a loop/switch, and so we would have inserted a `break`.
                     // If we've just come out of a loop/switch statement, we need to `break` again.
@@ -179,7 +179,7 @@
                                    ast::SwitchStatement>()) {
                         // If the loop only has the 'Return' behavior, we can just unconditionally
                         // break. Otherwise check the return flag.
-                        if (HasBehavior(ctx.src, s, sem::Behavior::kNext)) {
+                        if (HasBehavior(*ctx.src, s, sem::Behavior::kNext)) {
                             new_stmts.Back().Push(b.If(b.Expr(flag), b.Block(Vector{b.Break()})));
                         } else {
                             new_stmts.Back().Push(b.Break());
@@ -216,16 +216,16 @@
 
 }  // namespace
 
-ast::transform::Transform::ApplyResult MergeReturn::Apply(const Program* src,
+ast::transform::Transform::ApplyResult MergeReturn::Apply(const Program& src,
                                                           const ast::transform::DataMap&,
                                                           ast::transform::DataMap&) const {
     ProgramBuilder b;
-    program::CloneContext ctx{&b, src, /* auto_clone_symbols */ true};
+    program::CloneContext ctx{&b, &src, /* auto_clone_symbols */ true};
 
     bool made_changes = false;
 
     for (auto* func : ctx.src->AST().Functions()) {
-        if (!NeedsTransform(ctx.src, func)) {
+        if (!NeedsTransform(src, func)) {
             continue;
         }
 
diff --git a/src/tint/lang/spirv/writer/ast_raise/merge_return.h b/src/tint/lang/spirv/writer/ast_raise/merge_return.h
index 4a5d324..124f07d 100644
--- a/src/tint/lang/spirv/writer/ast_raise/merge_return.h
+++ b/src/tint/lang/spirv/writer/ast_raise/merge_return.h
@@ -28,7 +28,7 @@
     ~MergeReturn() override;
 
     /// @copydoc ast::transform::Transform::Apply
-    ApplyResult Apply(const Program* program,
+    ApplyResult Apply(const Program& program,
                       const ast::transform::DataMap& inputs,
                       ast::transform::DataMap& outputs) const override;
 };
diff --git a/src/tint/lang/spirv/writer/ast_raise/var_for_dynamic_index.cc b/src/tint/lang/spirv/writer/ast_raise/var_for_dynamic_index.cc
index e16172f..17fb1bd 100644
--- a/src/tint/lang/spirv/writer/ast_raise/var_for_dynamic_index.cc
+++ b/src/tint/lang/spirv/writer/ast_raise/var_for_dynamic_index.cc
@@ -29,11 +29,11 @@
 
 VarForDynamicIndex::~VarForDynamicIndex() = default;
 
-ast::transform::Transform::ApplyResult VarForDynamicIndex::Apply(const Program* src,
+ast::transform::Transform::ApplyResult VarForDynamicIndex::Apply(const Program& src,
                                                                  const ast::transform::DataMap&,
                                                                  ast::transform::DataMap&) const {
     ProgramBuilder b;
-    program::CloneContext ctx{&b, src, /* auto_clone_symbols */ true};
+    program::CloneContext ctx{&b, &src, /* auto_clone_symbols */ true};
 
     ast::transform::HoistToDeclBefore hoist_to_decl_before(ctx);
 
@@ -42,7 +42,7 @@
     auto dynamic_index_to_var = [&](const ast::IndexAccessorExpression* access_expr) {
         auto* index_expr = access_expr->index;
         auto* object_expr = access_expr->object;
-        auto& sem = src->Sem();
+        auto& sem = src.Sem();
 
         auto stage = sem.GetVal(index_expr)->Stage();
         if (stage == core::EvaluationStage::kConstant ||
@@ -66,7 +66,7 @@
     };
 
     bool index_accessor_found = false;
-    for (auto* node : src->ASTNodes().Objects()) {
+    for (auto* node : src.ASTNodes().Objects()) {
         if (auto* access_expr = node->As<ast::IndexAccessorExpression>()) {
             if (!dynamic_index_to_var(access_expr)) {
                 return resolver::Resolve(b);
diff --git a/src/tint/lang/spirv/writer/ast_raise/var_for_dynamic_index.h b/src/tint/lang/spirv/writer/ast_raise/var_for_dynamic_index.h
index 44b1409..effacc1 100644
--- a/src/tint/lang/spirv/writer/ast_raise/var_for_dynamic_index.h
+++ b/src/tint/lang/spirv/writer/ast_raise/var_for_dynamic_index.h
@@ -32,7 +32,7 @@
     ~VarForDynamicIndex() override;
 
     /// @copydoc ast::transform::Transform::Apply
-    ApplyResult Apply(const Program* program,
+    ApplyResult Apply(const Program& program,
                       const ast::transform::DataMap& inputs,
                       ast::transform::DataMap& outputs) const override;
 };
diff --git a/src/tint/lang/spirv/writer/ast_raise/vectorize_matrix_conversions.cc b/src/tint/lang/spirv/writer/ast_raise/vectorize_matrix_conversions.cc
index 13ab74b..867b639 100644
--- a/src/tint/lang/spirv/writer/ast_raise/vectorize_matrix_conversions.cc
+++ b/src/tint/lang/spirv/writer/ast_raise/vectorize_matrix_conversions.cc
@@ -36,9 +36,9 @@
 namespace tint::spirv::writer {
 namespace {
 
-bool ShouldRun(const Program* program) {
-    for (auto* node : program->ASTNodes().Objects()) {
-        if (auto* sem = program->Sem().GetVal(node)) {
+bool ShouldRun(const Program& program) {
+    for (auto* node : program.ASTNodes().Objects()) {
+        if (auto* sem = program.Sem().GetVal(node)) {
             if (auto* call = sem->UnwrapMaterialize()->As<sem::Call>()) {
                 if (call->Target()->Is<sem::ValueConversion>() &&
                     call->Type()->Is<core::type::Matrix>()) {
@@ -60,7 +60,7 @@
 VectorizeMatrixConversions::~VectorizeMatrixConversions() = default;
 
 ast::transform::Transform::ApplyResult VectorizeMatrixConversions::Apply(
-    const Program* src,
+    const Program& src,
     const ast::transform::DataMap&,
     ast::transform::DataMap&) const {
     if (!ShouldRun(src)) {
@@ -68,7 +68,7 @@
     }
 
     ProgramBuilder b;
-    program::CloneContext ctx{&b, src, /* auto_clone_symbols */ true};
+    program::CloneContext ctx{&b, &src, /* auto_clone_symbols */ true};
 
     using HelperFunctionKey =
         tint::UnorderedKeyWrapper<std::tuple<const core::type::Matrix*, const core::type::Matrix*>>;
@@ -76,7 +76,7 @@
     std::unordered_map<HelperFunctionKey, Symbol> matrix_convs;
 
     ctx.ReplaceAll([&](const ast::CallExpression* expr) -> const ast::CallExpression* {
-        auto* call = src->Sem().Get(expr)->UnwrapMaterialize()->As<sem::Call>();
+        auto* call = src.Sem().Get(expr)->UnwrapMaterialize()->As<sem::Call>();
         auto* ty_conv = call->Target()->As<sem::ValueConversion>();
         if (!ty_conv) {
             return nullptr;
diff --git a/src/tint/lang/spirv/writer/ast_raise/vectorize_matrix_conversions.h b/src/tint/lang/spirv/writer/ast_raise/vectorize_matrix_conversions.h
index 8e4e795..f822417 100644
--- a/src/tint/lang/spirv/writer/ast_raise/vectorize_matrix_conversions.h
+++ b/src/tint/lang/spirv/writer/ast_raise/vectorize_matrix_conversions.h
@@ -30,7 +30,7 @@
     ~VectorizeMatrixConversions() override;
 
     /// @copydoc ast::transform::Transform::Apply
-    ApplyResult Apply(const Program* program,
+    ApplyResult Apply(const Program& program,
                       const ast::transform::DataMap& inputs,
                       ast::transform::DataMap& outputs) const override;
 };
diff --git a/src/tint/lang/spirv/writer/ast_raise/while_to_loop.cc b/src/tint/lang/spirv/writer/ast_raise/while_to_loop.cc
index 942214f..c4be205 100644
--- a/src/tint/lang/spirv/writer/ast_raise/while_to_loop.cc
+++ b/src/tint/lang/spirv/writer/ast_raise/while_to_loop.cc
@@ -26,8 +26,8 @@
 namespace tint::spirv::writer {
 namespace {
 
-bool ShouldRun(const Program* program) {
-    for (auto* node : program->ASTNodes().Objects()) {
+bool ShouldRun(const Program& program) {
+    for (auto* node : program.ASTNodes().Objects()) {
         if (node->Is<ast::WhileStatement>()) {
             return true;
         }
@@ -41,7 +41,7 @@
 
 WhileToLoop::~WhileToLoop() = default;
 
-ast::transform::Transform::ApplyResult WhileToLoop::Apply(const Program* src,
+ast::transform::Transform::ApplyResult WhileToLoop::Apply(const Program& src,
                                                           const ast::transform::DataMap&,
                                                           ast::transform::DataMap&) const {
     if (!ShouldRun(src)) {
@@ -49,7 +49,7 @@
     }
 
     ProgramBuilder b;
-    program::CloneContext ctx{&b, src, /* auto_clone_symbols */ true};
+    program::CloneContext ctx{&b, &src, /* auto_clone_symbols */ true};
 
     ctx.ReplaceAll([&](const ast::WhileStatement* w) -> const ast::Statement* {
         tint::Vector<const ast::Statement*, 16> stmts;
diff --git a/src/tint/lang/spirv/writer/ast_raise/while_to_loop.h b/src/tint/lang/spirv/writer/ast_raise/while_to_loop.h
index e86fa6e..0e99378 100644
--- a/src/tint/lang/spirv/writer/ast_raise/while_to_loop.h
+++ b/src/tint/lang/spirv/writer/ast_raise/while_to_loop.h
@@ -30,7 +30,7 @@
     ~WhileToLoop() override;
 
     /// @copydoc ast::transform::Transform::Apply
-    ApplyResult Apply(const Program* program,
+    ApplyResult Apply(const Program& program,
                       const ast::transform::DataMap& inputs,
                       ast::transform::DataMap& outputs) const override;
 };
diff --git a/src/tint/lang/spirv/writer/atomic_builtin_test.cc b/src/tint/lang/spirv/writer/atomic_builtin_test.cc
index 5352fd1..2cbc65f 100644
--- a/src/tint/lang/spirv/writer/atomic_builtin_test.cc
+++ b/src/tint/lang/spirv/writer/atomic_builtin_test.cc
@@ -15,7 +15,7 @@
 #include "src/tint/lang/core/type/builtin_structs.h"
 #include "src/tint/lang/spirv/writer/common/helper_test.h"
 
-#include "src/tint/lang/core/function.h"
+#include "src/tint/lang/core/builtin_fn.h"
 
 using namespace tint::core::fluent_types;     // NOLINT
 using namespace tint::core::number_suffixes;  // NOLINT
@@ -34,7 +34,7 @@
 
     b.Append(func->Block(), [&] {
         auto* ptr = b.Let("ptr", var);
-        auto* result = b.Call(ty.i32(), core::Function::kAtomicAdd, ptr, arg1);
+        auto* result = b.Call(ty.i32(), core::BuiltinFn::kAtomicAdd, ptr, arg1);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -51,7 +51,7 @@
     func->SetParams({arg1});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.i32(), core::Function::kAtomicAdd, var, arg1);
+        auto* result = b.Call(ty.i32(), core::BuiltinFn::kAtomicAdd, var, arg1);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -68,7 +68,7 @@
     func->SetParams({arg1});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.i32(), core::Function::kAtomicAnd, var, arg1);
+        auto* result = b.Call(ty.i32(), core::BuiltinFn::kAtomicAnd, var, arg1);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -87,7 +87,8 @@
 
     b.Append(func->Block(), [&] {
         auto* result_ty = core::type::CreateAtomicCompareExchangeResult(ty, mod.symbols, ty.i32());
-        auto* result = b.Call(result_ty, core::Function::kAtomicCompareExchangeWeak, var, cmp, val);
+        auto* result =
+            b.Call(result_ty, core::BuiltinFn::kAtomicCompareExchangeWeak, var, cmp, val);
         auto* original = b.Access(ty.i32(), result, 0_u);
         b.Return(func, original);
         mod.SetName(result, "result");
@@ -109,7 +110,7 @@
     func->SetParams({arg1});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.i32(), core::Function::kAtomicExchange, var, arg1);
+        auto* result = b.Call(ty.i32(), core::BuiltinFn::kAtomicExchange, var, arg1);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -124,7 +125,7 @@
     auto* func = b.Function("foo", ty.i32());
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.i32(), core::Function::kAtomicLoad, var);
+        auto* result = b.Call(ty.i32(), core::BuiltinFn::kAtomicLoad, var);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -141,7 +142,7 @@
     func->SetParams({arg1});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.i32(), core::Function::kAtomicMax, var, arg1);
+        auto* result = b.Call(ty.i32(), core::BuiltinFn::kAtomicMax, var, arg1);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -158,7 +159,7 @@
     func->SetParams({arg1});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.u32(), core::Function::kAtomicMax, var, arg1);
+        auto* result = b.Call(ty.u32(), core::BuiltinFn::kAtomicMax, var, arg1);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -175,7 +176,7 @@
     func->SetParams({arg1});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.i32(), core::Function::kAtomicMin, var, arg1);
+        auto* result = b.Call(ty.i32(), core::BuiltinFn::kAtomicMin, var, arg1);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -192,7 +193,7 @@
     func->SetParams({arg1});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.u32(), core::Function::kAtomicMin, var, arg1);
+        auto* result = b.Call(ty.u32(), core::BuiltinFn::kAtomicMin, var, arg1);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -209,7 +210,7 @@
     func->SetParams({arg1});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.i32(), core::Function::kAtomicOr, var, arg1);
+        auto* result = b.Call(ty.i32(), core::BuiltinFn::kAtomicOr, var, arg1);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -226,7 +227,7 @@
     func->SetParams({arg1});
 
     b.Append(func->Block(), [&] {
-        b.Call(ty.void_(), core::Function::kAtomicStore, var, arg1);
+        b.Call(ty.void_(), core::BuiltinFn::kAtomicStore, var, arg1);
         b.Return(func);
     });
 
@@ -242,7 +243,7 @@
     func->SetParams({arg1});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.i32(), core::Function::kAtomicSub, var, arg1);
+        auto* result = b.Call(ty.i32(), core::BuiltinFn::kAtomicSub, var, arg1);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -259,7 +260,7 @@
     func->SetParams({arg1});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.i32(), core::Function::kAtomicXor, var, arg1);
+        auto* result = b.Call(ty.i32(), core::BuiltinFn::kAtomicXor, var, arg1);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
diff --git a/src/tint/lang/spirv/writer/builtin_test.cc b/src/tint/lang/spirv/writer/builtin_test.cc
index 7e51a65..3b84143 100644
--- a/src/tint/lang/spirv/writer/builtin_test.cc
+++ b/src/tint/lang/spirv/writer/builtin_test.cc
@@ -14,7 +14,7 @@
 
 #include "src/tint/lang/spirv/writer/common/helper_test.h"
 
-#include "src/tint/lang/core/function.h"
+#include "src/tint/lang/core/builtin_fn.h"
 #include "src/tint/lang/core/type/builtin_structs.h"
 
 using namespace tint::core::number_suffixes;  // NOLINT
@@ -28,7 +28,7 @@
     /// The element type to test.
     TestElementType type;
     /// The builtin function.
-    enum core::Function function;
+    enum core::BuiltinFn function;
     /// The expected SPIR-V instruction string.
     std::string spirv_inst;
 };
@@ -62,83 +62,83 @@
 INSTANTIATE_TEST_SUITE_P(
     SpirvWriterTest,
     Builtin_1arg,
-    testing::Values(BuiltinTestCase{kI32, core::Function::kAbs, "SAbs"},
-                    BuiltinTestCase{kF32, core::Function::kAbs, "FAbs"},
-                    BuiltinTestCase{kF16, core::Function::kAbs, "FAbs"},
-                    BuiltinTestCase{kF32, core::Function::kAcos, "Acos"},
-                    BuiltinTestCase{kF16, core::Function::kAcos, "Acos"},
-                    BuiltinTestCase{kF32, core::Function::kAsinh, "Asinh"},
-                    BuiltinTestCase{kF16, core::Function::kAsinh, "Asinh"},
-                    BuiltinTestCase{kF32, core::Function::kAcos, "Acos"},
-                    BuiltinTestCase{kF16, core::Function::kAcos, "Acos"},
-                    BuiltinTestCase{kF32, core::Function::kAsinh, "Asinh"},
-                    BuiltinTestCase{kF16, core::Function::kAsinh, "Asinh"},
-                    BuiltinTestCase{kF32, core::Function::kAtan, "Atan"},
-                    BuiltinTestCase{kF16, core::Function::kAtan, "Atan"},
-                    BuiltinTestCase{kF32, core::Function::kAtanh, "Atanh"},
-                    BuiltinTestCase{kF16, core::Function::kAtanh, "Atanh"},
-                    BuiltinTestCase{kF32, core::Function::kCeil, "Ceil"},
-                    BuiltinTestCase{kF16, core::Function::kCeil, "Ceil"},
-                    BuiltinTestCase{kF32, core::Function::kCos, "Cos"},
-                    BuiltinTestCase{kF16, core::Function::kCos, "Cos"},
-                    BuiltinTestCase{kI32, core::Function::kCountOneBits, "OpBitCount"},
-                    BuiltinTestCase{kU32, core::Function::kCountOneBits, "OpBitCount"},
-                    BuiltinTestCase{kF32, core::Function::kDpdx, "OpDPdx"},
-                    BuiltinTestCase{kF32, core::Function::kDpdxCoarse, "OpDPdxCoarse"},
-                    BuiltinTestCase{kF32, core::Function::kDpdxFine, "OpDPdxFine"},
-                    BuiltinTestCase{kF32, core::Function::kDpdy, "OpDPdy"},
-                    BuiltinTestCase{kF32, core::Function::kDpdyCoarse, "OpDPdyCoarse"},
-                    BuiltinTestCase{kF32, core::Function::kDpdyFine, "OpDPdyFine"},
-                    BuiltinTestCase{kF32, core::Function::kDegrees, "Degrees"},
-                    BuiltinTestCase{kF16, core::Function::kDegrees, "Degrees"},
-                    BuiltinTestCase{kF32, core::Function::kExp, "Exp"},
-                    BuiltinTestCase{kF16, core::Function::kExp, "Exp"},
-                    BuiltinTestCase{kF32, core::Function::kExp2, "Exp2"},
-                    BuiltinTestCase{kF16, core::Function::kExp2, "Exp2"},
-                    BuiltinTestCase{kF32, core::Function::kFloor, "Floor"},
-                    BuiltinTestCase{kF16, core::Function::kFloor, "Floor"},
-                    BuiltinTestCase{kF32, core::Function::kFract, "Fract"},
-                    BuiltinTestCase{kF16, core::Function::kFract, "Fract"},
-                    BuiltinTestCase{kF32, core::Function::kFwidth, "OpFwidth"},
-                    BuiltinTestCase{kF32, core::Function::kFwidthCoarse, "OpFwidthCoarse"},
-                    BuiltinTestCase{kF32, core::Function::kFwidthFine, "OpFwidthFine"},
-                    BuiltinTestCase{kF32, core::Function::kInverseSqrt, "InverseSqrt"},
-                    BuiltinTestCase{kF16, core::Function::kInverseSqrt, "InverseSqrt"},
-                    BuiltinTestCase{kF32, core::Function::kLog, "Log"},
-                    BuiltinTestCase{kF16, core::Function::kLog, "Log"},
-                    BuiltinTestCase{kF32, core::Function::kLog2, "Log2"},
-                    BuiltinTestCase{kF16, core::Function::kLog2, "Log2"},
-                    BuiltinTestCase{kF32, core::Function::kQuantizeToF16, "OpQuantizeToF16"},
-                    BuiltinTestCase{kF32, core::Function::kRadians, "Radians"},
-                    BuiltinTestCase{kF16, core::Function::kRadians, "Radians"},
-                    BuiltinTestCase{kI32, core::Function::kReverseBits, "OpBitReverse"},
-                    BuiltinTestCase{kU32, core::Function::kReverseBits, "OpBitReverse"},
-                    BuiltinTestCase{kF32, core::Function::kRound, "RoundEven"},
-                    BuiltinTestCase{kF16, core::Function::kRound, "RoundEven"},
-                    BuiltinTestCase{kF32, core::Function::kSign, "FSign"},
-                    BuiltinTestCase{kF16, core::Function::kSign, "FSign"},
-                    BuiltinTestCase{kI32, core::Function::kSign, "SSign"},
-                    BuiltinTestCase{kF32, core::Function::kSin, "Sin"},
-                    BuiltinTestCase{kF16, core::Function::kSin, "Sin"},
-                    BuiltinTestCase{kF32, core::Function::kSqrt, "Sqrt"},
-                    BuiltinTestCase{kF16, core::Function::kSqrt, "Sqrt"},
-                    BuiltinTestCase{kF32, core::Function::kTan, "Tan"},
-                    BuiltinTestCase{kF16, core::Function::kTan, "Tan"},
-                    BuiltinTestCase{kF32, core::Function::kTrunc, "Trunc"},
-                    BuiltinTestCase{kF16, core::Function::kTrunc, "Trunc"},
-                    BuiltinTestCase{kF32, core::Function::kCosh, "Cosh"},
-                    BuiltinTestCase{kF16, core::Function::kCosh, "Cosh"},
-                    BuiltinTestCase{kF32, core::Function::kSinh, "Sinh"},
-                    BuiltinTestCase{kF16, core::Function::kSinh, "Sinh"},
-                    BuiltinTestCase{kF32, core::Function::kTanh, "Tanh"},
-                    BuiltinTestCase{kF16, core::Function::kTanh, "Tanh"}));
+    testing::Values(BuiltinTestCase{kI32, core::BuiltinFn::kAbs, "SAbs"},
+                    BuiltinTestCase{kF32, core::BuiltinFn::kAbs, "FAbs"},
+                    BuiltinTestCase{kF16, core::BuiltinFn::kAbs, "FAbs"},
+                    BuiltinTestCase{kF32, core::BuiltinFn::kAcos, "Acos"},
+                    BuiltinTestCase{kF16, core::BuiltinFn::kAcos, "Acos"},
+                    BuiltinTestCase{kF32, core::BuiltinFn::kAsinh, "Asinh"},
+                    BuiltinTestCase{kF16, core::BuiltinFn::kAsinh, "Asinh"},
+                    BuiltinTestCase{kF32, core::BuiltinFn::kAcos, "Acos"},
+                    BuiltinTestCase{kF16, core::BuiltinFn::kAcos, "Acos"},
+                    BuiltinTestCase{kF32, core::BuiltinFn::kAsinh, "Asinh"},
+                    BuiltinTestCase{kF16, core::BuiltinFn::kAsinh, "Asinh"},
+                    BuiltinTestCase{kF32, core::BuiltinFn::kAtan, "Atan"},
+                    BuiltinTestCase{kF16, core::BuiltinFn::kAtan, "Atan"},
+                    BuiltinTestCase{kF32, core::BuiltinFn::kAtanh, "Atanh"},
+                    BuiltinTestCase{kF16, core::BuiltinFn::kAtanh, "Atanh"},
+                    BuiltinTestCase{kF32, core::BuiltinFn::kCeil, "Ceil"},
+                    BuiltinTestCase{kF16, core::BuiltinFn::kCeil, "Ceil"},
+                    BuiltinTestCase{kF32, core::BuiltinFn::kCos, "Cos"},
+                    BuiltinTestCase{kF16, core::BuiltinFn::kCos, "Cos"},
+                    BuiltinTestCase{kI32, core::BuiltinFn::kCountOneBits, "OpBitCount"},
+                    BuiltinTestCase{kU32, core::BuiltinFn::kCountOneBits, "OpBitCount"},
+                    BuiltinTestCase{kF32, core::BuiltinFn::kDpdx, "OpDPdx"},
+                    BuiltinTestCase{kF32, core::BuiltinFn::kDpdxCoarse, "OpDPdxCoarse"},
+                    BuiltinTestCase{kF32, core::BuiltinFn::kDpdxFine, "OpDPdxFine"},
+                    BuiltinTestCase{kF32, core::BuiltinFn::kDpdy, "OpDPdy"},
+                    BuiltinTestCase{kF32, core::BuiltinFn::kDpdyCoarse, "OpDPdyCoarse"},
+                    BuiltinTestCase{kF32, core::BuiltinFn::kDpdyFine, "OpDPdyFine"},
+                    BuiltinTestCase{kF32, core::BuiltinFn::kDegrees, "Degrees"},
+                    BuiltinTestCase{kF16, core::BuiltinFn::kDegrees, "Degrees"},
+                    BuiltinTestCase{kF32, core::BuiltinFn::kExp, "Exp"},
+                    BuiltinTestCase{kF16, core::BuiltinFn::kExp, "Exp"},
+                    BuiltinTestCase{kF32, core::BuiltinFn::kExp2, "Exp2"},
+                    BuiltinTestCase{kF16, core::BuiltinFn::kExp2, "Exp2"},
+                    BuiltinTestCase{kF32, core::BuiltinFn::kFloor, "Floor"},
+                    BuiltinTestCase{kF16, core::BuiltinFn::kFloor, "Floor"},
+                    BuiltinTestCase{kF32, core::BuiltinFn::kFract, "Fract"},
+                    BuiltinTestCase{kF16, core::BuiltinFn::kFract, "Fract"},
+                    BuiltinTestCase{kF32, core::BuiltinFn::kFwidth, "OpFwidth"},
+                    BuiltinTestCase{kF32, core::BuiltinFn::kFwidthCoarse, "OpFwidthCoarse"},
+                    BuiltinTestCase{kF32, core::BuiltinFn::kFwidthFine, "OpFwidthFine"},
+                    BuiltinTestCase{kF32, core::BuiltinFn::kInverseSqrt, "InverseSqrt"},
+                    BuiltinTestCase{kF16, core::BuiltinFn::kInverseSqrt, "InverseSqrt"},
+                    BuiltinTestCase{kF32, core::BuiltinFn::kLog, "Log"},
+                    BuiltinTestCase{kF16, core::BuiltinFn::kLog, "Log"},
+                    BuiltinTestCase{kF32, core::BuiltinFn::kLog2, "Log2"},
+                    BuiltinTestCase{kF16, core::BuiltinFn::kLog2, "Log2"},
+                    BuiltinTestCase{kF32, core::BuiltinFn::kQuantizeToF16, "OpQuantizeToF16"},
+                    BuiltinTestCase{kF32, core::BuiltinFn::kRadians, "Radians"},
+                    BuiltinTestCase{kF16, core::BuiltinFn::kRadians, "Radians"},
+                    BuiltinTestCase{kI32, core::BuiltinFn::kReverseBits, "OpBitReverse"},
+                    BuiltinTestCase{kU32, core::BuiltinFn::kReverseBits, "OpBitReverse"},
+                    BuiltinTestCase{kF32, core::BuiltinFn::kRound, "RoundEven"},
+                    BuiltinTestCase{kF16, core::BuiltinFn::kRound, "RoundEven"},
+                    BuiltinTestCase{kF32, core::BuiltinFn::kSign, "FSign"},
+                    BuiltinTestCase{kF16, core::BuiltinFn::kSign, "FSign"},
+                    BuiltinTestCase{kI32, core::BuiltinFn::kSign, "SSign"},
+                    BuiltinTestCase{kF32, core::BuiltinFn::kSin, "Sin"},
+                    BuiltinTestCase{kF16, core::BuiltinFn::kSin, "Sin"},
+                    BuiltinTestCase{kF32, core::BuiltinFn::kSqrt, "Sqrt"},
+                    BuiltinTestCase{kF16, core::BuiltinFn::kSqrt, "Sqrt"},
+                    BuiltinTestCase{kF32, core::BuiltinFn::kTan, "Tan"},
+                    BuiltinTestCase{kF16, core::BuiltinFn::kTan, "Tan"},
+                    BuiltinTestCase{kF32, core::BuiltinFn::kTrunc, "Trunc"},
+                    BuiltinTestCase{kF16, core::BuiltinFn::kTrunc, "Trunc"},
+                    BuiltinTestCase{kF32, core::BuiltinFn::kCosh, "Cosh"},
+                    BuiltinTestCase{kF16, core::BuiltinFn::kCosh, "Cosh"},
+                    BuiltinTestCase{kF32, core::BuiltinFn::kSinh, "Sinh"},
+                    BuiltinTestCase{kF16, core::BuiltinFn::kSinh, "Sinh"},
+                    BuiltinTestCase{kF32, core::BuiltinFn::kTanh, "Tanh"},
+                    BuiltinTestCase{kF16, core::BuiltinFn::kTanh, "Tanh"}));
 
 // Test that abs of an unsigned value just folds away.
 TEST_F(SpirvWriterTest, Builtin_Abs_u32) {
     auto* func = b.Function("foo", MakeScalarType(kU32));
     b.Append(func->Block(), [&] {
         auto* arg = MakeScalarValue(kU32);
-        auto* result = b.Call(MakeScalarType(kU32), core::Function::kAbs, arg);
+        auto* result = b.Call(MakeScalarType(kU32), core::BuiltinFn::kAbs, arg);
         b.Return(func, result);
         mod.SetName(arg, "arg");
     });
@@ -156,7 +156,7 @@
     auto* func = b.Function("foo", MakeVectorType(kU32));
     b.Append(func->Block(), [&] {
         auto* arg = MakeVectorValue(kU32);
-        auto* result = b.Call(MakeVectorType(kU32), core::Function::kAbs, arg);
+        auto* result = b.Call(MakeVectorType(kU32), core::BuiltinFn::kAbs, arg);
         b.Return(func, result);
         mod.SetName(arg, "arg");
     });
@@ -176,7 +176,7 @@
     auto* func = b.Function("foo", ty.bool_());
     func->SetParams({arg});
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.bool_(), core::Function::kAll, arg);
+        auto* result = b.Call(ty.bool_(), core::BuiltinFn::kAll, arg);
         b.Return(func, result);
     });
 
@@ -189,7 +189,7 @@
     auto* func = b.Function("foo", ty.bool_());
     func->SetParams({arg});
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.bool_(), core::Function::kAll, arg);
+        auto* result = b.Call(ty.bool_(), core::BuiltinFn::kAll, arg);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -204,7 +204,7 @@
     auto* func = b.Function("foo", ty.bool_());
     func->SetParams({arg});
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.bool_(), core::Function::kAny, arg);
+        auto* result = b.Call(ty.bool_(), core::BuiltinFn::kAny, arg);
         b.Return(func, result);
     });
 
@@ -217,7 +217,7 @@
     auto* func = b.Function("foo", ty.bool_());
     func->SetParams({arg});
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.bool_(), core::Function::kAny, arg);
+        auto* result = b.Call(ty.bool_(), core::BuiltinFn::kAny, arg);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -231,7 +231,7 @@
     auto* func = b.Function("foo", ty.f32());
     func->SetParams({arg});
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.f32(), core::Function::kDeterminant, arg);
+        auto* result = b.Call(ty.f32(), core::BuiltinFn::kDeterminant, arg);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -245,7 +245,7 @@
     auto* func = b.Function("foo", ty.f16());
     func->SetParams({arg});
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.f16(), core::Function::kDeterminant, arg);
+        auto* result = b.Call(ty.f16(), core::BuiltinFn::kDeterminant, arg);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -260,7 +260,7 @@
     auto* func = b.Function("foo", str);
     func->SetParams({arg});
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(str, core::Function::kFrexp, arg);
+        auto* result = b.Call(str, core::BuiltinFn::kFrexp, arg);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -275,7 +275,7 @@
     auto* func = b.Function("foo", str);
     func->SetParams({arg});
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(str, core::Function::kFrexp, arg);
+        auto* result = b.Call(str, core::BuiltinFn::kFrexp, arg);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -290,7 +290,7 @@
     auto* func = b.Function("foo", str);
     func->SetParams({arg});
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(str, core::Function::kFrexp, arg);
+        auto* result = b.Call(str, core::BuiltinFn::kFrexp, arg);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -305,7 +305,7 @@
     auto* func = b.Function("foo", str);
     func->SetParams({arg});
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(str, core::Function::kFrexp, arg);
+        auto* result = b.Call(str, core::BuiltinFn::kFrexp, arg);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -319,7 +319,7 @@
     auto* func = b.Function("foo", ty.f32());
     func->SetParams({arg});
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.f32(), core::Function::kLength, arg);
+        auto* result = b.Call(ty.f32(), core::BuiltinFn::kLength, arg);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -334,7 +334,7 @@
     auto* func = b.Function("foo", str);
     func->SetParams({arg});
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(str, core::Function::kModf, arg);
+        auto* result = b.Call(str, core::BuiltinFn::kModf, arg);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -349,7 +349,7 @@
     auto* func = b.Function("foo", str);
     func->SetParams({arg});
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(str, core::Function::kModf, arg);
+        auto* result = b.Call(str, core::BuiltinFn::kModf, arg);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -364,7 +364,7 @@
     auto* func = b.Function("foo", str);
     func->SetParams({arg});
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(str, core::Function::kModf, arg);
+        auto* result = b.Call(str, core::BuiltinFn::kModf, arg);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -379,7 +379,7 @@
     auto* func = b.Function("foo", str);
     func->SetParams({arg});
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(str, core::Function::kModf, arg);
+        auto* result = b.Call(str, core::BuiltinFn::kModf, arg);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -393,7 +393,7 @@
     auto* func = b.Function("foo", ty.vec4<f32>());
     func->SetParams({arg});
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.vec4<f32>(), core::Function::kNormalize, arg);
+        auto* result = b.Call(ty.vec4<f32>(), core::BuiltinFn::kNormalize, arg);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -407,7 +407,7 @@
     auto* func = b.Function("foo", ty.mat3x2<f32>());
     func->SetParams({arg});
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.mat3x2<f32>(), core::Function::kTranspose, arg);
+        auto* result = b.Call(ty.mat3x2<f32>(), core::BuiltinFn::kTranspose, arg);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -421,7 +421,7 @@
     auto* func = b.Function("foo", ty.mat4x4<f32>());
     func->SetParams({arg});
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.mat4x4<f32>(), core::Function::kTranspose, arg);
+        auto* result = b.Call(ty.mat4x4<f32>(), core::BuiltinFn::kTranspose, arg);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -435,7 +435,7 @@
     auto* func = b.Function("foo", ty.mat3x4<f16>());
     func->SetParams({arg});
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.mat3x4<f16>(), core::Function::kTranspose, arg);
+        auto* result = b.Call(ty.mat3x4<f16>(), core::BuiltinFn::kTranspose, arg);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -449,7 +449,7 @@
     auto* func = b.Function("foo", ty.mat2x2<f16>());
     func->SetParams({arg});
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.mat2x2<f16>(), core::Function::kTranspose, arg);
+        auto* result = b.Call(ty.mat2x2<f16>(), core::BuiltinFn::kTranspose, arg);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -463,7 +463,7 @@
     auto* func = b.Function("foo", ty.u32());
     func->SetParams({arg});
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.u32(), core::Function::kPack2X16Float, arg);
+        auto* result = b.Call(ty.u32(), core::BuiltinFn::kPack2X16Float, arg);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -477,7 +477,7 @@
     auto* func = b.Function("foo", ty.u32());
     func->SetParams({arg});
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.u32(), core::Function::kPack2X16Snorm, arg);
+        auto* result = b.Call(ty.u32(), core::BuiltinFn::kPack2X16Snorm, arg);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -491,7 +491,7 @@
     auto* func = b.Function("foo", ty.u32());
     func->SetParams({arg});
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.u32(), core::Function::kPack2X16Unorm, arg);
+        auto* result = b.Call(ty.u32(), core::BuiltinFn::kPack2X16Unorm, arg);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -505,7 +505,7 @@
     auto* func = b.Function("foo", ty.u32());
     func->SetParams({arg});
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.u32(), core::Function::kPack4X8Snorm, arg);
+        auto* result = b.Call(ty.u32(), core::BuiltinFn::kPack4X8Snorm, arg);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -519,7 +519,7 @@
     auto* func = b.Function("foo", ty.u32());
     func->SetParams({arg});
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.u32(), core::Function::kPack4X8Unorm, arg);
+        auto* result = b.Call(ty.u32(), core::BuiltinFn::kPack4X8Unorm, arg);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -533,7 +533,7 @@
     auto* func = b.Function("foo", ty.vec2<f32>());
     func->SetParams({arg});
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.vec2<f32>(), core::Function::kUnpack2X16Float, arg);
+        auto* result = b.Call(ty.vec2<f32>(), core::BuiltinFn::kUnpack2X16Float, arg);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -547,7 +547,7 @@
     auto* func = b.Function("foo", ty.vec2<f32>());
     func->SetParams({arg});
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.vec2<f32>(), core::Function::kUnpack2X16Snorm, arg);
+        auto* result = b.Call(ty.vec2<f32>(), core::BuiltinFn::kUnpack2X16Snorm, arg);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -561,7 +561,7 @@
     auto* func = b.Function("foo", ty.vec2<f32>());
     func->SetParams({arg});
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.vec2<f32>(), core::Function::kUnpack2X16Unorm, arg);
+        auto* result = b.Call(ty.vec2<f32>(), core::BuiltinFn::kUnpack2X16Unorm, arg);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -575,7 +575,7 @@
     auto* func = b.Function("foo", ty.vec4<f32>());
     func->SetParams({arg});
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.vec4<f32>(), core::Function::kUnpack4X8Snorm, arg);
+        auto* result = b.Call(ty.vec4<f32>(), core::BuiltinFn::kUnpack4X8Snorm, arg);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -589,7 +589,7 @@
     auto* func = b.Function("foo", ty.vec4<f32>());
     func->SetParams({arg});
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.vec4<f32>(), core::Function::kUnpack4X8Unorm, arg);
+        auto* result = b.Call(ty.vec4<f32>(), core::BuiltinFn::kUnpack4X8Unorm, arg);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -603,7 +603,7 @@
     auto* func = b.Function("foo", ty.u32());
     func->SetParams({arg});
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.u32(), core::Function::kCountLeadingZeros, arg);
+        auto* result = b.Call(ty.u32(), core::BuiltinFn::kCountLeadingZeros, arg);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -640,7 +640,7 @@
     auto* func = b.Function("foo", ty.i32());
     func->SetParams({arg});
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.i32(), core::Function::kCountLeadingZeros, arg);
+        auto* result = b.Call(ty.i32(), core::BuiltinFn::kCountLeadingZeros, arg);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -679,7 +679,7 @@
     auto* func = b.Function("foo", ty.vec2<u32>());
     func->SetParams({arg});
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.vec2<u32>(), core::Function::kCountLeadingZeros, arg);
+        auto* result = b.Call(ty.vec2<u32>(), core::BuiltinFn::kCountLeadingZeros, arg);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -727,7 +727,7 @@
     auto* func = b.Function("foo", ty.u32());
     func->SetParams({arg});
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.u32(), core::Function::kCountTrailingZeros, arg);
+        auto* result = b.Call(ty.u32(), core::BuiltinFn::kCountTrailingZeros, arg);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -768,7 +768,7 @@
     auto* func = b.Function("foo", ty.i32());
     func->SetParams({arg});
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.i32(), core::Function::kCountTrailingZeros, arg);
+        auto* result = b.Call(ty.i32(), core::BuiltinFn::kCountTrailingZeros, arg);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -811,7 +811,7 @@
     auto* func = b.Function("foo", ty.vec2<u32>());
     func->SetParams({arg});
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.vec2<u32>(), core::Function::kCountTrailingZeros, arg);
+        auto* result = b.Call(ty.vec2<u32>(), core::BuiltinFn::kCountTrailingZeros, arg);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -862,7 +862,7 @@
     auto* func = b.Function("foo", ty.u32());
     func->SetParams({arg});
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.u32(), core::Function::kFirstLeadingBit, arg);
+        auto* result = b.Call(ty.u32(), core::BuiltinFn::kFirstLeadingBit, arg);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -902,7 +902,7 @@
     auto* func = b.Function("foo", ty.i32());
     func->SetParams({arg});
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.i32(), core::Function::kFirstLeadingBit, arg);
+        auto* result = b.Call(ty.i32(), core::BuiltinFn::kFirstLeadingBit, arg);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -947,7 +947,7 @@
     auto* func = b.Function("foo", ty.vec2<u32>());
     func->SetParams({arg});
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.vec2<u32>(), core::Function::kFirstLeadingBit, arg);
+        auto* result = b.Call(ty.vec2<u32>(), core::BuiltinFn::kFirstLeadingBit, arg);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -998,7 +998,7 @@
     auto* func = b.Function("foo", ty.u32());
     func->SetParams({arg});
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.u32(), core::Function::kFirstTrailingBit, arg);
+        auto* result = b.Call(ty.u32(), core::BuiltinFn::kFirstTrailingBit, arg);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -1038,7 +1038,7 @@
     auto* func = b.Function("foo", ty.i32());
     func->SetParams({arg});
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.i32(), core::Function::kFirstTrailingBit, arg);
+        auto* result = b.Call(ty.i32(), core::BuiltinFn::kFirstTrailingBit, arg);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -1080,7 +1080,7 @@
     auto* func = b.Function("foo", ty.vec2<u32>());
     func->SetParams({arg});
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.vec2<u32>(), core::Function::kFirstTrailingBit, arg);
+        auto* result = b.Call(ty.vec2<u32>(), core::BuiltinFn::kFirstTrailingBit, arg);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -1131,7 +1131,7 @@
     auto* func = b.Function("foo", ty.f32());
     func->SetParams({arg});
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.f32(), core::Function::kSaturate, arg);
+        auto* result = b.Call(ty.f32(), core::BuiltinFn::kSaturate, arg);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -1145,7 +1145,7 @@
     auto* func = b.Function("foo", ty.vec4<f16>());
     func->SetParams({arg});
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.vec4<f16>(), core::Function::kSaturate, arg);
+        auto* result = b.Call(ty.vec4<f16>(), core::BuiltinFn::kSaturate, arg);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -1188,17 +1188,17 @@
 }
 INSTANTIATE_TEST_SUITE_P(SpirvWriterTest,
                          Builtin_2arg,
-                         testing::Values(BuiltinTestCase{kF32, core::Function::kAtan2, "Atan2"},
-                                         BuiltinTestCase{kF32, core::Function::kMax, "FMax"},
-                                         BuiltinTestCase{kI32, core::Function::kMax, "SMax"},
-                                         BuiltinTestCase{kU32, core::Function::kMax, "UMax"},
-                                         BuiltinTestCase{kF32, core::Function::kMin, "FMin"},
-                                         BuiltinTestCase{kI32, core::Function::kMin, "SMin"},
-                                         BuiltinTestCase{kU32, core::Function::kMin, "UMin"},
-                                         BuiltinTestCase{kF32, core::Function::kPow, "Pow"},
-                                         BuiltinTestCase{kF16, core::Function::kPow, "Pow"},
-                                         BuiltinTestCase{kF32, core::Function::kStep, "Step"},
-                                         BuiltinTestCase{kF16, core::Function::kStep, "Step"}));
+                         testing::Values(BuiltinTestCase{kF32, core::BuiltinFn::kAtan2, "Atan2"},
+                                         BuiltinTestCase{kF32, core::BuiltinFn::kMax, "FMax"},
+                                         BuiltinTestCase{kI32, core::BuiltinFn::kMax, "SMax"},
+                                         BuiltinTestCase{kU32, core::BuiltinFn::kMax, "UMax"},
+                                         BuiltinTestCase{kF32, core::BuiltinFn::kMin, "FMin"},
+                                         BuiltinTestCase{kI32, core::BuiltinFn::kMin, "SMin"},
+                                         BuiltinTestCase{kU32, core::BuiltinFn::kMin, "UMin"},
+                                         BuiltinTestCase{kF32, core::BuiltinFn::kPow, "Pow"},
+                                         BuiltinTestCase{kF16, core::BuiltinFn::kPow, "Pow"},
+                                         BuiltinTestCase{kF32, core::BuiltinFn::kStep, "Step"},
+                                         BuiltinTestCase{kF16, core::BuiltinFn::kStep, "Step"}));
 
 TEST_F(SpirvWriterTest, Builtin_Cross_vec3f) {
     auto* arg1 = b.FunctionParam("arg1", ty.vec3<f32>());
@@ -1206,7 +1206,7 @@
     auto* func = b.Function("foo", ty.vec3<f32>());
     func->SetParams({arg1, arg2});
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.vec3<f32>(), core::Function::kCross, arg1, arg2);
+        auto* result = b.Call(ty.vec3<f32>(), core::BuiltinFn::kCross, arg1, arg2);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -1221,7 +1221,7 @@
     auto* func = b.Function("foo", MakeScalarType(kF32));
     func->SetParams({arg1, arg2});
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(MakeScalarType(kF32), core::Function::kDistance, arg1, arg2);
+        auto* result = b.Call(MakeScalarType(kF32), core::BuiltinFn::kDistance, arg1, arg2);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -1236,7 +1236,7 @@
     auto* func = b.Function("foo", MakeScalarType(kF16));
     func->SetParams({arg1, arg2});
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(MakeScalarType(kF16), core::Function::kDistance, arg1, arg2);
+        auto* result = b.Call(MakeScalarType(kF16), core::BuiltinFn::kDistance, arg1, arg2);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -1251,7 +1251,7 @@
     auto* func = b.Function("foo", ty.f32());
     func->SetParams({arg1, arg2});
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.f32(), core::Function::kDot, arg1, arg2);
+        auto* result = b.Call(ty.f32(), core::BuiltinFn::kDot, arg1, arg2);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -1266,7 +1266,7 @@
     auto* func = b.Function("foo", ty.i32());
     func->SetParams({arg1, arg2});
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.i32(), core::Function::kDot, arg1, arg2);
+        auto* result = b.Call(ty.i32(), core::BuiltinFn::kDot, arg1, arg2);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -1289,7 +1289,7 @@
     auto* func = b.Function("foo", ty.u32());
     func->SetParams({arg1, arg2});
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.u32(), core::Function::kDot, arg1, arg2);
+        auto* result = b.Call(ty.u32(), core::BuiltinFn::kDot, arg1, arg2);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -1320,7 +1320,7 @@
     auto* func = b.Function("foo", ty.f32());
     func->SetParams({arg1, arg2});
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.f32(), core::Function::kLdexp, arg1, arg2);
+        auto* result = b.Call(ty.f32(), core::BuiltinFn::kLdexp, arg1, arg2);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -1335,7 +1335,7 @@
     auto* func = b.Function("foo", ty.f16());
     func->SetParams({arg1, arg2});
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.f16(), core::Function::kLdexp, arg1, arg2);
+        auto* result = b.Call(ty.f16(), core::BuiltinFn::kLdexp, arg1, arg2);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -1350,7 +1350,7 @@
     auto* func = b.Function("foo", ty.vec2<f32>());
     func->SetParams({arg1, arg2});
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.vec2<f32>(), core::Function::kLdexp, arg1, arg2);
+        auto* result = b.Call(ty.vec2<f32>(), core::BuiltinFn::kLdexp, arg1, arg2);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -1365,7 +1365,7 @@
     auto* func = b.Function("foo", ty.vec3<f16>());
     func->SetParams({arg1, arg2});
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.vec3<f16>(), core::Function::kLdexp, arg1, arg2);
+        auto* result = b.Call(ty.vec3<f16>(), core::BuiltinFn::kLdexp, arg1, arg2);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -1380,7 +1380,7 @@
     auto* func = b.Function("foo", ty.vec3<f32>());
     func->SetParams({arg1, arg2});
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.vec3<f32>(), core::Function::kReflect, arg1, arg2);
+        auto* result = b.Call(ty.vec3<f32>(), core::BuiltinFn::kReflect, arg1, arg2);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -1395,7 +1395,7 @@
     auto* func = b.Function("foo", ty.vec4<f16>());
     func->SetParams({arg1, arg2});
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.vec4<f16>(), core::Function::kReflect, arg1, arg2);
+        auto* result = b.Call(ty.vec4<f16>(), core::BuiltinFn::kReflect, arg1, arg2);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -1435,15 +1435,15 @@
 INSTANTIATE_TEST_SUITE_P(
     SpirvWriterTest,
     Builtin_3arg,
-    testing::Values(BuiltinTestCase{kF32, core::Function::kClamp, "NClamp"},
-                    BuiltinTestCase{kI32, core::Function::kClamp, "SClamp"},
-                    BuiltinTestCase{kU32, core::Function::kClamp, "UClamp"},
-                    BuiltinTestCase{kF32, core::Function::kFma, "Fma"},
-                    BuiltinTestCase{kF16, core::Function::kFma, "Fma"},
-                    BuiltinTestCase{kF32, core::Function::kMix, "Mix"},
-                    BuiltinTestCase{kF16, core::Function::kMix, "Mix"},
-                    BuiltinTestCase{kF32, core::Function::kSmoothstep, "SmoothStep"},
-                    BuiltinTestCase{kF16, core::Function::kSmoothstep, "SmoothStep"}));
+    testing::Values(BuiltinTestCase{kF32, core::BuiltinFn::kClamp, "NClamp"},
+                    BuiltinTestCase{kI32, core::BuiltinFn::kClamp, "SClamp"},
+                    BuiltinTestCase{kU32, core::BuiltinFn::kClamp, "UClamp"},
+                    BuiltinTestCase{kF32, core::BuiltinFn::kFma, "Fma"},
+                    BuiltinTestCase{kF16, core::BuiltinFn::kFma, "Fma"},
+                    BuiltinTestCase{kF32, core::BuiltinFn::kMix, "Mix"},
+                    BuiltinTestCase{kF16, core::BuiltinFn::kMix, "Mix"},
+                    BuiltinTestCase{kF32, core::BuiltinFn::kSmoothstep, "SmoothStep"},
+                    BuiltinTestCase{kF16, core::BuiltinFn::kSmoothstep, "SmoothStep"}));
 
 TEST_F(SpirvWriterTest, Builtin_ExtractBits_Scalar_I32) {
     auto* arg = b.FunctionParam("arg", ty.i32());
@@ -1453,7 +1453,7 @@
     func->SetParams({arg, offset, count});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.i32(), core::Function::kExtractBits, arg, offset, count);
+        auto* result = b.Call(ty.i32(), core::BuiltinFn::kExtractBits, arg, offset, count);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -1470,7 +1470,7 @@
     func->SetParams({arg, offset, count});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.u32(), core::Function::kExtractBits, arg, offset, count);
+        auto* result = b.Call(ty.u32(), core::BuiltinFn::kExtractBits, arg, offset, count);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -1487,7 +1487,7 @@
     func->SetParams({arg, offset, count});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.vec4<i32>(), core::Function::kExtractBits, arg, offset, count);
+        auto* result = b.Call(ty.vec4<i32>(), core::BuiltinFn::kExtractBits, arg, offset, count);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -1504,7 +1504,7 @@
     func->SetParams({arg, offset, count});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.vec2<u32>(), core::Function::kExtractBits, arg, offset, count);
+        auto* result = b.Call(ty.vec2<u32>(), core::BuiltinFn::kExtractBits, arg, offset, count);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -1522,7 +1522,7 @@
     func->SetParams({arg, newbits, offset, count});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.i32(), core::Function::kInsertBits, arg, newbits, offset, count);
+        auto* result = b.Call(ty.i32(), core::BuiltinFn::kInsertBits, arg, newbits, offset, count);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -1540,7 +1540,7 @@
     func->SetParams({arg, newbits, offset, count});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.u32(), core::Function::kInsertBits, arg, newbits, offset, count);
+        auto* result = b.Call(ty.u32(), core::BuiltinFn::kInsertBits, arg, newbits, offset, count);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -1559,7 +1559,7 @@
 
     b.Append(func->Block(), [&] {
         auto* result =
-            b.Call(ty.vec4<i32>(), core::Function::kInsertBits, arg, newbits, offset, count);
+            b.Call(ty.vec4<i32>(), core::BuiltinFn::kInsertBits, arg, newbits, offset, count);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -1578,7 +1578,7 @@
 
     b.Append(func->Block(), [&] {
         auto* result =
-            b.Call(ty.vec2<u32>(), core::Function::kInsertBits, arg, newbits, offset, count);
+            b.Call(ty.vec2<u32>(), core::BuiltinFn::kInsertBits, arg, newbits, offset, count);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -1594,7 +1594,7 @@
     auto* func = b.Function("foo", ty.vec3<f32>());
     func->SetParams({arg1, arg2, arg3});
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.vec3<f32>(), core::Function::kFaceForward, arg1, arg2, arg3);
+        auto* result = b.Call(ty.vec3<f32>(), core::BuiltinFn::kFaceForward, arg1, arg2, arg3);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -1610,7 +1610,7 @@
     auto* func = b.Function("foo", ty.vec4<f16>());
     func->SetParams({arg1, arg2, arg3});
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.vec4<f16>(), core::Function::kFaceForward, arg1, arg2, arg3);
+        auto* result = b.Call(ty.vec4<f16>(), core::BuiltinFn::kFaceForward, arg1, arg2, arg3);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -1627,7 +1627,7 @@
     func->SetParams({arg1, arg2, factor});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.vec4<f32>(), core::Function::kMix, arg1, arg2, factor);
+        auto* result = b.Call(ty.vec4<f32>(), core::BuiltinFn::kMix, arg1, arg2, factor);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -1645,7 +1645,7 @@
     func->SetParams({arg1, arg2, factor});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.vec4<f32>(), core::Function::kMix, arg1, arg2, factor);
+        auto* result = b.Call(ty.vec4<f32>(), core::BuiltinFn::kMix, arg1, arg2, factor);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -1662,7 +1662,7 @@
     func->SetParams({arg1, arg2, i});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.vec4<f32>(), core::Function::kRefract, arg1, arg2, i);
+        auto* result = b.Call(ty.vec4<f32>(), core::BuiltinFn::kRefract, arg1, arg2, i);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -1679,7 +1679,7 @@
     func->SetParams({arg1, arg2, i});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.vec4<f16>(), core::Function::kRefract, arg1, arg2, i);
+        auto* result = b.Call(ty.vec4<f16>(), core::BuiltinFn::kRefract, arg1, arg2, i);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -1696,7 +1696,7 @@
     func->SetParams({argf, argt, cond});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.i32(), core::Function::kSelect, argf, argt, cond);
+        auto* result = b.Call(ty.i32(), core::BuiltinFn::kSelect, argf, argt, cond);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -1713,7 +1713,7 @@
     func->SetParams({argf, argt, cond});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.vec4<i32>(), core::Function::kSelect, argf, argt, cond);
+        auto* result = b.Call(ty.vec4<i32>(), core::BuiltinFn::kSelect, argf, argt, cond);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -1730,7 +1730,7 @@
     func->SetParams({argf, argt, cond});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.vec4<i32>(), core::Function::kSelect, argf, argt, cond);
+        auto* result = b.Call(ty.vec4<i32>(), core::BuiltinFn::kSelect, argf, argt, cond);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -1743,7 +1743,7 @@
 TEST_F(SpirvWriterTest, Builtin_StorageBarrier) {
     auto* func = b.Function("foo", ty.void_());
     b.Append(func->Block(), [&] {
-        b.Call(ty.void_(), core::Function::kStorageBarrier);
+        b.Call(ty.void_(), core::BuiltinFn::kStorageBarrier);
         b.Return(func);
     });
 
@@ -1754,7 +1754,7 @@
 TEST_F(SpirvWriterTest, Builtin_TextureBarrier) {
     auto* func = b.Function("foo", ty.void_());
     b.Append(func->Block(), [&] {
-        b.Call(ty.void_(), core::Function::kTextureBarrier);
+        b.Call(ty.void_(), core::BuiltinFn::kTextureBarrier);
         b.Return(func);
     });
 
@@ -1765,7 +1765,7 @@
 TEST_F(SpirvWriterTest, Builtin_WorkgroupBarrier) {
     auto* func = b.Function("foo", ty.void_());
     b.Append(func->Block(), [&] {
-        b.Call(ty.void_(), core::Function::kWorkgroupBarrier);
+        b.Call(ty.void_(), core::BuiltinFn::kWorkgroupBarrier);
         b.Return(func);
     });
 
@@ -1776,7 +1776,7 @@
 TEST_F(SpirvWriterTest, Builtin_SubgroupBallot) {
     auto* func = b.Function("foo", ty.vec4<u32>());
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.vec4<u32>(), core::Function::kSubgroupBallot);
+        auto* result = b.Call(ty.vec4<u32>(), core::BuiltinFn::kSubgroupBallot);
         mod.SetName(result, "result");
         b.Return(func, result);
     });
@@ -1789,7 +1789,7 @@
 TEST_F(SpirvWriterTest, Builtin_SubgroupBroadcastValueF32) {
     auto* func = b.Function("foo", ty.f32());
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.f32(), core::Function::kSubgroupBroadcast, 1_f, 0_u);
+        auto* result = b.Call(ty.f32(), core::BuiltinFn::kSubgroupBroadcast, 1_f, 0_u);
         mod.SetName(result, "result");
         b.Return(func, result);
     });
@@ -1802,7 +1802,7 @@
 TEST_F(SpirvWriterTest, Builtin_SubgroupBroadcastValueI32) {
     auto* func = b.Function("foo", ty.i32());
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.i32(), core::Function::kSubgroupBroadcast, 1_i, 0_u);
+        auto* result = b.Call(ty.i32(), core::BuiltinFn::kSubgroupBroadcast, 1_i, 0_u);
         mod.SetName(result, "result");
         b.Return(func, result);
     });
@@ -1815,7 +1815,7 @@
 TEST_F(SpirvWriterTest, Builtin_SubgroupBroadcastValueU32) {
     auto* func = b.Function("foo", ty.u32());
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.u32(), core::Function::kSubgroupBroadcast, 1_u, 0_u);
+        auto* result = b.Call(ty.u32(), core::BuiltinFn::kSubgroupBroadcast, 1_u, 0_u);
         mod.SetName(result, "result");
         b.Return(func, result);
     });
@@ -1833,7 +1833,7 @@
     auto* func = b.Function("foo", ty.u32());
     b.Append(func->Block(), [&] {
         auto* ptr = b.Let("ptr", var);
-        auto* result = b.Call(ty.u32(), core::Function::kArrayLength, ptr);
+        auto* result = b.Call(ty.u32(), core::BuiltinFn::kArrayLength, ptr);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -1857,7 +1857,7 @@
     auto* func = b.Function("foo", ty.u32());
     b.Append(func->Block(), [&] {
         auto* ptr = b.Let("ptr", b.Access(ty.ptr(storage, arr), var, 2_u));
-        auto* result = b.Call(ty.u32(), core::Function::kArrayLength, ptr);
+        auto* result = b.Call(ty.u32(), core::BuiltinFn::kArrayLength, ptr);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
diff --git a/src/tint/lang/spirv/writer/common/BUILD.bazel b/src/tint/lang/spirv/writer/common/BUILD.bazel
index db94b5b..679753a 100644
--- a/src/tint/lang/spirv/writer/common/BUILD.bazel
+++ b/src/tint/lang/spirv/writer/common/BUILD.bazel
@@ -79,6 +79,7 @@
     "//src/tint/lang/core/intrinsic/data",
     "//src/tint/lang/core/ir",
     "//src/tint/lang/core/type",
+    "//src/tint/lang/spirv",
     "//src/tint/lang/spirv/intrinsic/data",
     "//src/tint/lang/spirv/ir",
     "//src/tint/utils/containers",
diff --git a/src/tint/lang/spirv/writer/common/BUILD.cmake b/src/tint/lang/spirv/writer/common/BUILD.cmake
index 9a74b57..8e00115 100644
--- a/src/tint/lang/spirv/writer/common/BUILD.cmake
+++ b/src/tint/lang/spirv/writer/common/BUILD.cmake
@@ -84,6 +84,7 @@
   tint_lang_core_intrinsic_data
   tint_lang_core_ir
   tint_lang_core_type
+  tint_lang_spirv
   tint_lang_spirv_intrinsic_data
   tint_lang_spirv_ir
   tint_utils_containers
diff --git a/src/tint/lang/spirv/writer/common/BUILD.gn b/src/tint/lang/spirv/writer/common/BUILD.gn
index c15d605..17330ef 100644
--- a/src/tint/lang/spirv/writer/common/BUILD.gn
+++ b/src/tint/lang/spirv/writer/common/BUILD.gn
@@ -82,6 +82,7 @@
         "${tint_src_dir}/lang/core/intrinsic/data",
         "${tint_src_dir}/lang/core/ir",
         "${tint_src_dir}/lang/core/type",
+        "${tint_src_dir}/lang/spirv",
         "${tint_src_dir}/lang/spirv/intrinsic/data",
         "${tint_src_dir}/lang/spirv/ir",
         "${tint_src_dir}/utils/containers",
diff --git a/src/tint/lang/spirv/writer/printer/BUILD.bazel b/src/tint/lang/spirv/writer/printer/BUILD.bazel
index 672fdad..6cbbbbe 100644
--- a/src/tint/lang/spirv/writer/printer/BUILD.bazel
+++ b/src/tint/lang/spirv/writer/printer/BUILD.bazel
@@ -40,6 +40,7 @@
     "//src/tint/lang/core/intrinsic/data",
     "//src/tint/lang/core/ir",
     "//src/tint/lang/core/type",
+    "//src/tint/lang/spirv",
     "//src/tint/lang/spirv/intrinsic/data",
     "//src/tint/lang/spirv/ir",
     "//src/tint/lang/spirv/type",
diff --git a/src/tint/lang/spirv/writer/printer/BUILD.cmake b/src/tint/lang/spirv/writer/printer/BUILD.cmake
index e296599..2eccda6 100644
--- a/src/tint/lang/spirv/writer/printer/BUILD.cmake
+++ b/src/tint/lang/spirv/writer/printer/BUILD.cmake
@@ -41,6 +41,7 @@
   tint_lang_core_intrinsic_data
   tint_lang_core_ir
   tint_lang_core_type
+  tint_lang_spirv
   tint_lang_spirv_intrinsic_data
   tint_lang_spirv_ir
   tint_lang_spirv_type
diff --git a/src/tint/lang/spirv/writer/printer/BUILD.gn b/src/tint/lang/spirv/writer/printer/BUILD.gn
index fd1dd71..69b21cd 100644
--- a/src/tint/lang/spirv/writer/printer/BUILD.gn
+++ b/src/tint/lang/spirv/writer/printer/BUILD.gn
@@ -39,6 +39,7 @@
       "${tint_src_dir}/lang/core/intrinsic/data",
       "${tint_src_dir}/lang/core/ir",
       "${tint_src_dir}/lang/core/type",
+      "${tint_src_dir}/lang/spirv",
       "${tint_src_dir}/lang/spirv/intrinsic/data",
       "${tint_src_dir}/lang/spirv/ir",
       "${tint_src_dir}/lang/spirv/type",
diff --git a/src/tint/lang/spirv/writer/printer/printer.cc b/src/tint/lang/spirv/writer/printer/printer.cc
index d5b2d92..35c9327 100644
--- a/src/tint/lang/spirv/writer/printer/printer.cc
+++ b/src/tint/lang/spirv/writer/printer/printer.cc
@@ -35,7 +35,6 @@
 #include "src/tint/lang/core/ir/exit_loop.h"
 #include "src/tint/lang/core/ir/exit_switch.h"
 #include "src/tint/lang/core/ir/if.h"
-#include "src/tint/lang/core/ir/intrinsic_call.h"
 #include "src/tint/lang/core/ir/let.h"
 #include "src/tint/lang/core/ir/load.h"
 #include "src/tint/lang/core/ir/load_vector_element.h"
@@ -74,7 +73,6 @@
 #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/spirv/ir/intrinsic_call.h"
 #include "src/tint/lang/spirv/type/sampled_image.h"
 #include "src/tint/lang/spirv/writer/ast_printer/ast_printer.h"
 #include "src/tint/lang/spirv/writer/common/module.h"
@@ -710,7 +708,6 @@
             [&](spirv::ir::BuiltinCall* b) { EmitSpirvBuiltinCall(b); },          //
             [&](core::ir::Construct* c) { EmitConstruct(c); },                    //
             [&](core::ir::Convert* c) { EmitConvert(c); },                        //
-            [&](spirv::ir::IntrinsicCall* i) { EmitIntrinsicCall(i); },           //
             [&](core::ir::Load* l) { EmitLoad(l); },                              //
             [&](core::ir::LoadVectorElement* l) { EmitLoadVectorElement(l); },    //
             [&](core::ir::Loop* l) { EmitLoop(l); },                              //
@@ -1036,93 +1033,108 @@
 
     spv::Op op = spv::Op::Max;
     switch (builtin->Func()) {
-        case spirv::ir::Function::kArrayLength:
+        case spirv::BuiltinFn::kArrayLength:
             op = spv::Op::OpArrayLength;
             break;
-        case spirv::ir::Function::kAtomicIadd:
+        case spirv::BuiltinFn::kAtomicIadd:
             op = spv::Op::OpAtomicIAdd;
             break;
-        case spirv::ir::Function::kAtomicIsub:
+        case spirv::BuiltinFn::kAtomicIsub:
             op = spv::Op::OpAtomicISub;
             break;
-        case spirv::ir::Function::kAtomicAnd:
+        case spirv::BuiltinFn::kAtomicAnd:
             op = spv::Op::OpAtomicAnd;
             break;
-        case spirv::ir::Function::kAtomicCompareExchange:
+        case spirv::BuiltinFn::kAtomicCompareExchange:
             op = spv::Op::OpAtomicCompareExchange;
             break;
-        case spirv::ir::Function::kAtomicExchange:
+        case spirv::BuiltinFn::kAtomicExchange:
             op = spv::Op::OpAtomicExchange;
             break;
-        case spirv::ir::Function::kAtomicLoad:
+        case spirv::BuiltinFn::kAtomicLoad:
             op = spv::Op::OpAtomicLoad;
             break;
-        case spirv::ir::Function::kAtomicOr:
+        case spirv::BuiltinFn::kAtomicOr:
             op = spv::Op::OpAtomicOr;
             break;
-        case spirv::ir::Function::kAtomicSmax:
+        case spirv::BuiltinFn::kAtomicSmax:
             op = spv::Op::OpAtomicSMax;
             break;
-        case spirv::ir::Function::kAtomicSmin:
+        case spirv::BuiltinFn::kAtomicSmin:
             op = spv::Op::OpAtomicSMin;
             break;
-        case spirv::ir::Function::kAtomicStore:
+        case spirv::BuiltinFn::kAtomicStore:
             op = spv::Op::OpAtomicStore;
             break;
-        case spirv::ir::Function::kAtomicUmax:
+        case spirv::BuiltinFn::kAtomicUmax:
             op = spv::Op::OpAtomicUMax;
             break;
-        case spirv::ir::Function::kAtomicUmin:
+        case spirv::BuiltinFn::kAtomicUmin:
             op = spv::Op::OpAtomicUMin;
             break;
-        case spirv::ir::Function::kAtomicXor:
+        case spirv::BuiltinFn::kAtomicXor:
             op = spv::Op::OpAtomicXor;
             break;
-        case spirv::ir::Function::kDot:
+        case spirv::BuiltinFn::kDot:
             op = spv::Op::OpDot;
             break;
-        case spirv::ir::Function::kImageDrefGather:
+        case spirv::BuiltinFn::kImageDrefGather:
             op = spv::Op::OpImageDrefGather;
             break;
-        case spirv::ir::Function::kImageFetch:
+        case spirv::BuiltinFn::kImageFetch:
             op = spv::Op::OpImageFetch;
             break;
-        case spirv::ir::Function::kImageGather:
+        case spirv::BuiltinFn::kImageGather:
             op = spv::Op::OpImageGather;
             break;
-        case spirv::ir::Function::kImageQuerySize:
+        case spirv::BuiltinFn::kImageQuerySize:
             module_.PushCapability(SpvCapabilityImageQuery);
             op = spv::Op::OpImageQuerySize;
             break;
-        case spirv::ir::Function::kImageQuerySizeLod:
+        case spirv::BuiltinFn::kImageQuerySizeLod:
             module_.PushCapability(SpvCapabilityImageQuery);
             op = spv::Op::OpImageQuerySizeLod;
             break;
-        case spirv::ir::Function::kImageRead:
+        case spirv::BuiltinFn::kImageRead:
             op = spv::Op::OpImageRead;
             break;
-        case spirv::ir::Function::kMatrixTimesMatrix:
+        case spirv::BuiltinFn::kImageSampleImplicitLod:
+            op = spv::Op::OpImageSampleImplicitLod;
+            break;
+        case spirv::BuiltinFn::kImageSampleExplicitLod:
+            op = spv::Op::OpImageSampleExplicitLod;
+            break;
+        case spirv::BuiltinFn::kImageSampleDrefImplicitLod:
+            op = spv::Op::OpImageSampleDrefImplicitLod;
+            break;
+        case spirv::BuiltinFn::kImageSampleDrefExplicitLod:
+            op = spv::Op::OpImageSampleDrefExplicitLod;
+            break;
+        case spirv::BuiltinFn::kImageWrite:
+            op = spv::Op::OpImageWrite;
+            break;
+        case spirv::BuiltinFn::kMatrixTimesMatrix:
             op = spv::Op::OpMatrixTimesMatrix;
             break;
-        case spirv::ir::Function::kMatrixTimesScalar:
+        case spirv::BuiltinFn::kMatrixTimesScalar:
             op = spv::Op::OpMatrixTimesScalar;
             break;
-        case spirv::ir::Function::kMatrixTimesVector:
+        case spirv::BuiltinFn::kMatrixTimesVector:
             op = spv::Op::OpMatrixTimesVector;
             break;
-        case spirv::ir::Function::kSampledImage:
+        case spirv::BuiltinFn::kSampledImage:
             op = spv::Op::OpSampledImage;
             break;
-        case spirv::ir::Function::kSelect:
+        case spirv::BuiltinFn::kSelect:
             op = spv::Op::OpSelect;
             break;
-        case spirv::ir::Function::kVectorTimesMatrix:
+        case spirv::BuiltinFn::kVectorTimesMatrix:
             op = spv::Op::OpVectorTimesMatrix;
             break;
-        case spirv::ir::Function::kVectorTimesScalar:
+        case spirv::BuiltinFn::kVectorTimesScalar:
             op = spv::Op::OpVectorTimesScalar;
             break;
-        case spirv::ir::Function::kNone:
+        case spirv::BuiltinFn::kNone:
             TINT_ICE() << "undefined spirv ir function";
             return;
     }
@@ -1140,13 +1152,13 @@
 void Printer::EmitCoreBuiltinCall(core::ir::CoreBuiltinCall* builtin) {
     auto* result_ty = builtin->Result()->Type();
 
-    if (builtin->Func() == core::Function::kAbs &&
+    if (builtin->Func() == core::BuiltinFn::kAbs &&
         result_ty->is_unsigned_integer_scalar_or_vector()) {
         // abs() is a no-op for unsigned integers.
         values_.Add(builtin->Result(), Value(builtin->Args()[0]));
         return;
     }
-    if ((builtin->Func() == core::Function::kAll || builtin->Func() == core::Function::kAny) &&
+    if ((builtin->Func() == core::BuiltinFn::kAll || builtin->Func() == core::BuiltinFn::kAny) &&
         builtin->Args()[0]->Type()->Is<core::type::Bool>()) {
         // all() and any() are passthroughs for scalar arguments.
         values_.Add(builtin->Result(), Value(builtin->Args()[0]));
@@ -1173,41 +1185,41 @@
 
     // Determine the opcode.
     switch (builtin->Func()) {
-        case core::Function::kAbs:
+        case core::BuiltinFn::kAbs:
             if (result_ty->is_float_scalar_or_vector()) {
                 glsl_ext_inst(GLSLstd450FAbs);
             } else if (result_ty->is_signed_integer_scalar_or_vector()) {
                 glsl_ext_inst(GLSLstd450SAbs);
             }
             break;
-        case core::Function::kAll:
+        case core::BuiltinFn::kAll:
             op = spv::Op::OpAll;
             break;
-        case core::Function::kAny:
+        case core::BuiltinFn::kAny:
             op = spv::Op::OpAny;
             break;
-        case core::Function::kAcos:
+        case core::BuiltinFn::kAcos:
             glsl_ext_inst(GLSLstd450Acos);
             break;
-        case core::Function::kAcosh:
+        case core::BuiltinFn::kAcosh:
             glsl_ext_inst(GLSLstd450Acosh);
             break;
-        case core::Function::kAsin:
+        case core::BuiltinFn::kAsin:
             glsl_ext_inst(GLSLstd450Asin);
             break;
-        case core::Function::kAsinh:
+        case core::BuiltinFn::kAsinh:
             glsl_ext_inst(GLSLstd450Asinh);
             break;
-        case core::Function::kAtan:
+        case core::BuiltinFn::kAtan:
             glsl_ext_inst(GLSLstd450Atan);
             break;
-        case core::Function::kAtan2:
+        case core::BuiltinFn::kAtan2:
             glsl_ext_inst(GLSLstd450Atan2);
             break;
-        case core::Function::kAtanh:
+        case core::BuiltinFn::kAtanh:
             glsl_ext_inst(GLSLstd450Atanh);
             break;
-        case core::Function::kClamp:
+        case core::BuiltinFn::kClamp:
             if (result_ty->is_float_scalar_or_vector()) {
                 glsl_ext_inst(GLSLstd450NClamp);
             } else if (result_ty->is_unsigned_integer_scalar_or_vector()) {
@@ -1216,107 +1228,107 @@
                 glsl_ext_inst(GLSLstd450SClamp);
             }
             break;
-        case core::Function::kCeil:
+        case core::BuiltinFn::kCeil:
             glsl_ext_inst(GLSLstd450Ceil);
             break;
-        case core::Function::kCos:
+        case core::BuiltinFn::kCos:
             glsl_ext_inst(GLSLstd450Cos);
             break;
-        case core::Function::kCosh:
+        case core::BuiltinFn::kCosh:
             glsl_ext_inst(GLSLstd450Cosh);
             break;
-        case core::Function::kCountOneBits:
+        case core::BuiltinFn::kCountOneBits:
             op = spv::Op::OpBitCount;
             break;
-        case core::Function::kCross:
+        case core::BuiltinFn::kCross:
             glsl_ext_inst(GLSLstd450Cross);
             break;
-        case core::Function::kDegrees:
+        case core::BuiltinFn::kDegrees:
             glsl_ext_inst(GLSLstd450Degrees);
             break;
-        case core::Function::kDeterminant:
+        case core::BuiltinFn::kDeterminant:
             glsl_ext_inst(GLSLstd450Determinant);
             break;
-        case core::Function::kDistance:
+        case core::BuiltinFn::kDistance:
             glsl_ext_inst(GLSLstd450Distance);
             break;
-        case core::Function::kDpdx:
+        case core::BuiltinFn::kDpdx:
             op = spv::Op::OpDPdx;
             break;
-        case core::Function::kDpdxCoarse:
+        case core::BuiltinFn::kDpdxCoarse:
             module_.PushCapability(SpvCapabilityDerivativeControl);
             op = spv::Op::OpDPdxCoarse;
             break;
-        case core::Function::kDpdxFine:
+        case core::BuiltinFn::kDpdxFine:
             module_.PushCapability(SpvCapabilityDerivativeControl);
             op = spv::Op::OpDPdxFine;
             break;
-        case core::Function::kDpdy:
+        case core::BuiltinFn::kDpdy:
             op = spv::Op::OpDPdy;
             break;
-        case core::Function::kDpdyCoarse:
+        case core::BuiltinFn::kDpdyCoarse:
             module_.PushCapability(SpvCapabilityDerivativeControl);
             op = spv::Op::OpDPdyCoarse;
             break;
-        case core::Function::kDpdyFine:
+        case core::BuiltinFn::kDpdyFine:
             module_.PushCapability(SpvCapabilityDerivativeControl);
             op = spv::Op::OpDPdyFine;
             break;
-        case core::Function::kExp:
+        case core::BuiltinFn::kExp:
             glsl_ext_inst(GLSLstd450Exp);
             break;
-        case core::Function::kExp2:
+        case core::BuiltinFn::kExp2:
             glsl_ext_inst(GLSLstd450Exp2);
             break;
-        case core::Function::kExtractBits:
+        case core::BuiltinFn::kExtractBits:
             op = result_ty->is_signed_integer_scalar_or_vector() ? spv::Op::OpBitFieldSExtract
                                                                  : spv::Op::OpBitFieldUExtract;
             break;
-        case core::Function::kFaceForward:
+        case core::BuiltinFn::kFaceForward:
             glsl_ext_inst(GLSLstd450FaceForward);
             break;
-        case core::Function::kFloor:
+        case core::BuiltinFn::kFloor:
             glsl_ext_inst(GLSLstd450Floor);
             break;
-        case core::Function::kFma:
+        case core::BuiltinFn::kFma:
             glsl_ext_inst(GLSLstd450Fma);
             break;
-        case core::Function::kFract:
+        case core::BuiltinFn::kFract:
             glsl_ext_inst(GLSLstd450Fract);
             break;
-        case core::Function::kFrexp:
+        case core::BuiltinFn::kFrexp:
             glsl_ext_inst(GLSLstd450FrexpStruct);
             break;
-        case core::Function::kFwidth:
+        case core::BuiltinFn::kFwidth:
             op = spv::Op::OpFwidth;
             break;
-        case core::Function::kFwidthCoarse:
+        case core::BuiltinFn::kFwidthCoarse:
             module_.PushCapability(SpvCapabilityDerivativeControl);
             op = spv::Op::OpFwidthCoarse;
             break;
-        case core::Function::kFwidthFine:
+        case core::BuiltinFn::kFwidthFine:
             module_.PushCapability(SpvCapabilityDerivativeControl);
             op = spv::Op::OpFwidthFine;
             break;
-        case core::Function::kInsertBits:
+        case core::BuiltinFn::kInsertBits:
             op = spv::Op::OpBitFieldInsert;
             break;
-        case core::Function::kInverseSqrt:
+        case core::BuiltinFn::kInverseSqrt:
             glsl_ext_inst(GLSLstd450InverseSqrt);
             break;
-        case core::Function::kLdexp:
+        case core::BuiltinFn::kLdexp:
             glsl_ext_inst(GLSLstd450Ldexp);
             break;
-        case core::Function::kLength:
+        case core::BuiltinFn::kLength:
             glsl_ext_inst(GLSLstd450Length);
             break;
-        case core::Function::kLog:
+        case core::BuiltinFn::kLog:
             glsl_ext_inst(GLSLstd450Log);
             break;
-        case core::Function::kLog2:
+        case core::BuiltinFn::kLog2:
             glsl_ext_inst(GLSLstd450Log2);
             break;
-        case core::Function::kMax:
+        case core::BuiltinFn::kMax:
             if (result_ty->is_float_scalar_or_vector()) {
                 glsl_ext_inst(GLSLstd450FMax);
             } else if (result_ty->is_signed_integer_scalar_or_vector()) {
@@ -1325,7 +1337,7 @@
                 glsl_ext_inst(GLSLstd450UMax);
             }
             break;
-        case core::Function::kMin:
+        case core::BuiltinFn::kMin:
             if (result_ty->is_float_scalar_or_vector()) {
                 glsl_ext_inst(GLSLstd450FMin);
             } else if (result_ty->is_signed_integer_scalar_or_vector()) {
@@ -1334,74 +1346,74 @@
                 glsl_ext_inst(GLSLstd450UMin);
             }
             break;
-        case core::Function::kMix:
+        case core::BuiltinFn::kMix:
             glsl_ext_inst(GLSLstd450FMix);
             break;
-        case core::Function::kModf:
+        case core::BuiltinFn::kModf:
             glsl_ext_inst(GLSLstd450ModfStruct);
             break;
-        case core::Function::kNormalize:
+        case core::BuiltinFn::kNormalize:
             glsl_ext_inst(GLSLstd450Normalize);
             break;
-        case core::Function::kPack2X16Float:
+        case core::BuiltinFn::kPack2X16Float:
             glsl_ext_inst(GLSLstd450PackHalf2x16);
             break;
-        case core::Function::kPack2X16Snorm:
+        case core::BuiltinFn::kPack2X16Snorm:
             glsl_ext_inst(GLSLstd450PackSnorm2x16);
             break;
-        case core::Function::kPack2X16Unorm:
+        case core::BuiltinFn::kPack2X16Unorm:
             glsl_ext_inst(GLSLstd450PackUnorm2x16);
             break;
-        case core::Function::kPack4X8Snorm:
+        case core::BuiltinFn::kPack4X8Snorm:
             glsl_ext_inst(GLSLstd450PackSnorm4x8);
             break;
-        case core::Function::kPack4X8Unorm:
+        case core::BuiltinFn::kPack4X8Unorm:
             glsl_ext_inst(GLSLstd450PackUnorm4x8);
             break;
-        case core::Function::kPow:
+        case core::BuiltinFn::kPow:
             glsl_ext_inst(GLSLstd450Pow);
             break;
-        case core::Function::kQuantizeToF16:
+        case core::BuiltinFn::kQuantizeToF16:
             op = spv::Op::OpQuantizeToF16;
             break;
-        case core::Function::kRadians:
+        case core::BuiltinFn::kRadians:
             glsl_ext_inst(GLSLstd450Radians);
             break;
-        case core::Function::kReflect:
+        case core::BuiltinFn::kReflect:
             glsl_ext_inst(GLSLstd450Reflect);
             break;
-        case core::Function::kRefract:
+        case core::BuiltinFn::kRefract:
             glsl_ext_inst(GLSLstd450Refract);
             break;
-        case core::Function::kReverseBits:
+        case core::BuiltinFn::kReverseBits:
             op = spv::Op::OpBitReverse;
             break;
-        case core::Function::kRound:
+        case core::BuiltinFn::kRound:
             glsl_ext_inst(GLSLstd450RoundEven);
             break;
-        case core::Function::kSign:
+        case core::BuiltinFn::kSign:
             if (result_ty->is_float_scalar_or_vector()) {
                 glsl_ext_inst(GLSLstd450FSign);
             } else if (result_ty->is_signed_integer_scalar_or_vector()) {
                 glsl_ext_inst(GLSLstd450SSign);
             }
             break;
-        case core::Function::kSin:
+        case core::BuiltinFn::kSin:
             glsl_ext_inst(GLSLstd450Sin);
             break;
-        case core::Function::kSinh:
+        case core::BuiltinFn::kSinh:
             glsl_ext_inst(GLSLstd450Sinh);
             break;
-        case core::Function::kSmoothstep:
+        case core::BuiltinFn::kSmoothstep:
             glsl_ext_inst(GLSLstd450SmoothStep);
             break;
-        case core::Function::kSqrt:
+        case core::BuiltinFn::kSqrt:
             glsl_ext_inst(GLSLstd450Sqrt);
             break;
-        case core::Function::kStep:
+        case core::BuiltinFn::kStep:
             glsl_ext_inst(GLSLstd450Step);
             break;
-        case core::Function::kStorageBarrier:
+        case core::BuiltinFn::kStorageBarrier:
             op = spv::Op::OpControlBarrier;
             operands.clear();
             operands.push_back(Constant(b_.ConstantValue(u32(spv::Scope::Workgroup))));
@@ -1410,24 +1422,24 @@
                 Constant(b_.ConstantValue(u32(spv::MemorySemanticsMask::UniformMemory |
                                               spv::MemorySemanticsMask::AcquireRelease))));
             break;
-        case core::Function::kSubgroupBallot:
+        case core::BuiltinFn::kSubgroupBallot:
             module_.PushCapability(SpvCapabilityGroupNonUniformBallot);
             op = spv::Op::OpGroupNonUniformBallot;
             operands.push_back(Constant(ir_->constant_values.Get(u32(spv::Scope::Subgroup))));
             operands.push_back(Constant(ir_->constant_values.Get(true)));
             break;
-        case core::Function::kSubgroupBroadcast:
+        case core::BuiltinFn::kSubgroupBroadcast:
             module_.PushCapability(SpvCapabilityGroupNonUniformBallot);
             op = spv::Op::OpGroupNonUniformBroadcast;
             operands.push_back(Constant(ir_->constant_values.Get(u32(spv::Scope::Subgroup))));
             break;
-        case core::Function::kTan:
+        case core::BuiltinFn::kTan:
             glsl_ext_inst(GLSLstd450Tan);
             break;
-        case core::Function::kTanh:
+        case core::BuiltinFn::kTanh:
             glsl_ext_inst(GLSLstd450Tanh);
             break;
-        case core::Function::kTextureBarrier:
+        case core::BuiltinFn::kTextureBarrier:
             op = spv::Op::OpControlBarrier;
             operands.clear();
             operands.push_back(Constant(b_.ConstantValue(u32(spv::Scope::Workgroup))));
@@ -1436,36 +1448,36 @@
                 Constant(b_.ConstantValue(u32(spv::MemorySemanticsMask::ImageMemory |
                                               spv::MemorySemanticsMask::AcquireRelease))));
             break;
-        case core::Function::kTextureNumLevels:
+        case core::BuiltinFn::kTextureNumLevels:
             module_.PushCapability(SpvCapabilityImageQuery);
             op = spv::Op::OpImageQueryLevels;
             break;
-        case core::Function::kTextureNumSamples:
+        case core::BuiltinFn::kTextureNumSamples:
             module_.PushCapability(SpvCapabilityImageQuery);
             op = spv::Op::OpImageQuerySamples;
             break;
-        case core::Function::kTranspose:
+        case core::BuiltinFn::kTranspose:
             op = spv::Op::OpTranspose;
             break;
-        case core::Function::kTrunc:
+        case core::BuiltinFn::kTrunc:
             glsl_ext_inst(GLSLstd450Trunc);
             break;
-        case core::Function::kUnpack2X16Float:
+        case core::BuiltinFn::kUnpack2X16Float:
             glsl_ext_inst(GLSLstd450UnpackHalf2x16);
             break;
-        case core::Function::kUnpack2X16Snorm:
+        case core::BuiltinFn::kUnpack2X16Snorm:
             glsl_ext_inst(GLSLstd450UnpackSnorm2x16);
             break;
-        case core::Function::kUnpack2X16Unorm:
+        case core::BuiltinFn::kUnpack2X16Unorm:
             glsl_ext_inst(GLSLstd450UnpackUnorm2x16);
             break;
-        case core::Function::kUnpack4X8Snorm:
+        case core::BuiltinFn::kUnpack4X8Snorm:
             glsl_ext_inst(GLSLstd450UnpackSnorm4x8);
             break;
-        case core::Function::kUnpack4X8Unorm:
+        case core::BuiltinFn::kUnpack4X8Unorm:
             glsl_ext_inst(GLSLstd450UnpackUnorm4x8);
             break;
-        case core::Function::kWorkgroupBarrier:
+        case core::BuiltinFn::kWorkgroupBarrier:
             op = spv::Op::OpControlBarrier;
             operands.clear();
             operands.push_back(Constant(b_.ConstantValue(u32(spv::Scope::Workgroup))));
@@ -1586,41 +1598,6 @@
     current_function_.push_inst(op, std::move(operands));
 }
 
-void Printer::EmitIntrinsicCall(spirv::ir::IntrinsicCall* call) {
-    auto id = Value(call);
-
-    spv::Op op = spv::Op::Max;
-    switch (call->Kind()) {
-        case spirv::ir::Intrinsic::kImageSampleImplicitLod:
-            op = spv::Op::OpImageSampleImplicitLod;
-            break;
-        case spirv::ir::Intrinsic::kImageSampleExplicitLod:
-            op = spv::Op::OpImageSampleExplicitLod;
-            break;
-        case spirv::ir::Intrinsic::kImageSampleDrefImplicitLod:
-            op = spv::Op::OpImageSampleDrefImplicitLod;
-            break;
-        case spirv::ir::Intrinsic::kImageSampleDrefExplicitLod:
-            op = spv::Op::OpImageSampleDrefExplicitLod;
-            break;
-        case spirv::ir::Intrinsic::kImageWrite:
-            op = spv::Op::OpImageWrite;
-            break;
-        case spirv::ir::Intrinsic::kUndefined:
-            TINT_ICE() << "undefined spirv intrinsic";
-            return;
-    }
-
-    OperandList operands;
-    if (!call->Result()->Type()->Is<core::type::Void>()) {
-        operands = {Type(call->Result()->Type()), id};
-    }
-    for (auto* arg : call->Args()) {
-        operands.push_back(Value(arg));
-    }
-    current_function_.push_inst(op, operands);
-}
-
 void Printer::EmitLoad(core::ir::Load* load) {
     current_function_.push_inst(spv::Op::OpLoad,
                                 {Type(load->Result()->Type()), Value(load), Value(load->From())});
diff --git a/src/tint/lang/spirv/writer/printer/printer.h b/src/tint/lang/spirv/writer/printer/printer.h
index 4071ab0..0b061e6 100644
--- a/src/tint/lang/spirv/writer/printer/printer.h
+++ b/src/tint/lang/spirv/writer/printer/printer.h
@@ -25,7 +25,6 @@
 #include "src/tint/lang/core/ir/constant.h"
 #include "src/tint/lang/core/texel_format.h"
 #include "src/tint/lang/spirv/ir/builtin_call.h"
-#include "src/tint/lang/spirv/ir/intrinsic_call.h"
 #include "src/tint/lang/spirv/writer/common/binary_writer.h"
 #include "src/tint/lang/spirv/writer/common/function.h"
 #include "src/tint/lang/spirv/writer/common/module.h"
@@ -51,7 +50,6 @@
 class ExitSwitch;
 class Function;
 class If;
-class IntrinsicCall;
 class Let;
 class Load;
 class LoadVectorElement;
@@ -211,10 +209,6 @@
     /// @param convert the convert instruction to emit
     void EmitConvert(core::ir::Convert* convert);
 
-    /// Emit an intrinsic call instruction.
-    /// @param call the intrinsic call instruction to emit
-    void EmitIntrinsicCall(spirv::ir::IntrinsicCall* call);
-
     /// Emit IO attributes.
     /// @param id the ID of the variable to decorate
     /// @param attrs the shader IO attrs
diff --git a/src/tint/lang/spirv/writer/raise/BUILD.bazel b/src/tint/lang/spirv/writer/raise/BUILD.bazel
index 2f3e484..6c76444 100644
--- a/src/tint/lang/spirv/writer/raise/BUILD.bazel
+++ b/src/tint/lang/spirv/writer/raise/BUILD.bazel
@@ -53,6 +53,7 @@
     "//src/tint/lang/core/ir",
     "//src/tint/lang/core/ir/transform",
     "//src/tint/lang/core/type",
+    "//src/tint/lang/spirv",
     "//src/tint/lang/spirv/intrinsic/data",
     "//src/tint/lang/spirv/ir",
     "//src/tint/lang/spirv/type",
diff --git a/src/tint/lang/spirv/writer/raise/BUILD.cmake b/src/tint/lang/spirv/writer/raise/BUILD.cmake
index 5462889..7e56366 100644
--- a/src/tint/lang/spirv/writer/raise/BUILD.cmake
+++ b/src/tint/lang/spirv/writer/raise/BUILD.cmake
@@ -54,6 +54,7 @@
   tint_lang_core_ir
   tint_lang_core_ir_transform
   tint_lang_core_type
+  tint_lang_spirv
   tint_lang_spirv_intrinsic_data
   tint_lang_spirv_ir
   tint_lang_spirv_type
diff --git a/src/tint/lang/spirv/writer/raise/BUILD.gn b/src/tint/lang/spirv/writer/raise/BUILD.gn
index b62e46f..db76051 100644
--- a/src/tint/lang/spirv/writer/raise/BUILD.gn
+++ b/src/tint/lang/spirv/writer/raise/BUILD.gn
@@ -56,6 +56,7 @@
       "${tint_src_dir}/lang/core/ir",
       "${tint_src_dir}/lang/core/ir/transform",
       "${tint_src_dir}/lang/core/type",
+      "${tint_src_dir}/lang/spirv",
       "${tint_src_dir}/lang/spirv/intrinsic/data",
       "${tint_src_dir}/lang/spirv/ir",
       "${tint_src_dir}/lang/spirv/type",
diff --git a/src/tint/lang/spirv/writer/raise/builtin_polyfill.cc b/src/tint/lang/spirv/writer/raise/builtin_polyfill.cc
index 5b06c9d..313d6fb 100644
--- a/src/tint/lang/spirv/writer/raise/builtin_polyfill.cc
+++ b/src/tint/lang/spirv/writer/raise/builtin_polyfill.cc
@@ -29,7 +29,6 @@
 #include "src/tint/lang/core/type/storage_texture.h"
 #include "src/tint/lang/core/type/texture.h"
 #include "src/tint/lang/spirv/ir/builtin_call.h"
-#include "src/tint/lang/spirv/ir/intrinsic_call.h"
 #include "src/tint/lang/spirv/type/sampled_image.h"
 #include "src/tint/utils/ice/ice.h"
 
@@ -63,35 +62,35 @@
             }
             if (auto* builtin = inst->As<core::ir::CoreBuiltinCall>()) {
                 switch (builtin->Func()) {
-                    case core::Function::kArrayLength:
-                    case core::Function::kAtomicAdd:
-                    case core::Function::kAtomicAnd:
-                    case core::Function::kAtomicCompareExchangeWeak:
-                    case core::Function::kAtomicExchange:
-                    case core::Function::kAtomicLoad:
-                    case core::Function::kAtomicMax:
-                    case core::Function::kAtomicMin:
-                    case core::Function::kAtomicOr:
-                    case core::Function::kAtomicStore:
-                    case core::Function::kAtomicSub:
-                    case core::Function::kAtomicXor:
-                    case core::Function::kDot:
-                    case core::Function::kSelect:
-                    case core::Function::kTextureDimensions:
-                    case core::Function::kTextureGather:
-                    case core::Function::kTextureGatherCompare:
-                    case core::Function::kTextureLoad:
-                    case core::Function::kTextureNumLayers:
-                    case core::Function::kTextureSample:
-                    case core::Function::kTextureSampleBias:
-                    case core::Function::kTextureSampleCompare:
-                    case core::Function::kTextureSampleCompareLevel:
-                    case core::Function::kTextureSampleGrad:
-                    case core::Function::kTextureSampleLevel:
-                    case core::Function::kTextureStore:
+                    case core::BuiltinFn::kArrayLength:
+                    case core::BuiltinFn::kAtomicAdd:
+                    case core::BuiltinFn::kAtomicAnd:
+                    case core::BuiltinFn::kAtomicCompareExchangeWeak:
+                    case core::BuiltinFn::kAtomicExchange:
+                    case core::BuiltinFn::kAtomicLoad:
+                    case core::BuiltinFn::kAtomicMax:
+                    case core::BuiltinFn::kAtomicMin:
+                    case core::BuiltinFn::kAtomicOr:
+                    case core::BuiltinFn::kAtomicStore:
+                    case core::BuiltinFn::kAtomicSub:
+                    case core::BuiltinFn::kAtomicXor:
+                    case core::BuiltinFn::kDot:
+                    case core::BuiltinFn::kSelect:
+                    case core::BuiltinFn::kTextureDimensions:
+                    case core::BuiltinFn::kTextureGather:
+                    case core::BuiltinFn::kTextureGatherCompare:
+                    case core::BuiltinFn::kTextureLoad:
+                    case core::BuiltinFn::kTextureNumLayers:
+                    case core::BuiltinFn::kTextureSample:
+                    case core::BuiltinFn::kTextureSampleBias:
+                    case core::BuiltinFn::kTextureSampleCompare:
+                    case core::BuiltinFn::kTextureSampleCompareLevel:
+                    case core::BuiltinFn::kTextureSampleGrad:
+                    case core::BuiltinFn::kTextureSampleLevel:
+                    case core::BuiltinFn::kTextureStore:
                         worklist.Push(builtin);
                         break;
-                    case core::Function::kQuantizeToF16:
+                    case core::BuiltinFn::kQuantizeToF16:
                         if (builtin->Result()->Type()->Is<core::type::Vector>()) {
                             worklist.Push(builtin);
                         }
@@ -106,53 +105,53 @@
         for (auto* builtin : worklist) {
             core::ir::Value* replacement = nullptr;
             switch (builtin->Func()) {
-                case core::Function::kArrayLength:
+                case core::BuiltinFn::kArrayLength:
                     replacement = ArrayLength(builtin);
                     break;
-                case core::Function::kAtomicAdd:
-                case core::Function::kAtomicAnd:
-                case core::Function::kAtomicCompareExchangeWeak:
-                case core::Function::kAtomicExchange:
-                case core::Function::kAtomicLoad:
-                case core::Function::kAtomicMax:
-                case core::Function::kAtomicMin:
-                case core::Function::kAtomicOr:
-                case core::Function::kAtomicStore:
-                case core::Function::kAtomicSub:
-                case core::Function::kAtomicXor:
+                case core::BuiltinFn::kAtomicAdd:
+                case core::BuiltinFn::kAtomicAnd:
+                case core::BuiltinFn::kAtomicCompareExchangeWeak:
+                case core::BuiltinFn::kAtomicExchange:
+                case core::BuiltinFn::kAtomicLoad:
+                case core::BuiltinFn::kAtomicMax:
+                case core::BuiltinFn::kAtomicMin:
+                case core::BuiltinFn::kAtomicOr:
+                case core::BuiltinFn::kAtomicStore:
+                case core::BuiltinFn::kAtomicSub:
+                case core::BuiltinFn::kAtomicXor:
                     replacement = Atomic(builtin);
                     break;
-                case core::Function::kDot:
+                case core::BuiltinFn::kDot:
                     replacement = Dot(builtin);
                     break;
-                case core::Function::kSelect:
+                case core::BuiltinFn::kSelect:
                     replacement = Select(builtin);
                     break;
-                case core::Function::kTextureDimensions:
+                case core::BuiltinFn::kTextureDimensions:
                     replacement = TextureDimensions(builtin);
                     break;
-                case core::Function::kTextureGather:
-                case core::Function::kTextureGatherCompare:
+                case core::BuiltinFn::kTextureGather:
+                case core::BuiltinFn::kTextureGatherCompare:
                     replacement = TextureGather(builtin);
                     break;
-                case core::Function::kTextureLoad:
+                case core::BuiltinFn::kTextureLoad:
                     replacement = TextureLoad(builtin);
                     break;
-                case core::Function::kTextureNumLayers:
+                case core::BuiltinFn::kTextureNumLayers:
                     replacement = TextureNumLayers(builtin);
                     break;
-                case core::Function::kTextureSample:
-                case core::Function::kTextureSampleBias:
-                case core::Function::kTextureSampleCompare:
-                case core::Function::kTextureSampleCompareLevel:
-                case core::Function::kTextureSampleGrad:
-                case core::Function::kTextureSampleLevel:
+                case core::BuiltinFn::kTextureSample:
+                case core::BuiltinFn::kTextureSampleBias:
+                case core::BuiltinFn::kTextureSampleCompare:
+                case core::BuiltinFn::kTextureSampleCompareLevel:
+                case core::BuiltinFn::kTextureSampleGrad:
+                case core::BuiltinFn::kTextureSampleLevel:
                     replacement = TextureSample(builtin);
                     break;
-                case core::Function::kTextureStore:
+                case core::BuiltinFn::kTextureStore:
                     replacement = TextureStore(builtin);
                     break;
-                case core::Function::kQuantizeToF16:
+                case core::BuiltinFn::kQuantizeToF16:
                     replacement = QuantizeToF16Vec(builtin);
                     break;
                 default:
@@ -196,7 +195,7 @@
 
         // Replace the builtin call with a call to the spirv.array_length intrinsic.
         auto* call = b.Call<spirv::ir::BuiltinCall>(
-            builtin->Result()->Type(), spirv::ir::Function::kArrayLength,
+            builtin->Result()->Type(), spirv::BuiltinFn::kArrayLength,
             Vector{access->Object(), Literal(u32(const_idx->Value()->ValueAs<uint32_t>()))});
         call->InsertBefore(builtin);
         return call->Result();
@@ -223,27 +222,27 @@
         auto* memory_semantics = b.Constant(u32(SpvMemorySemanticsMaskNone));
 
         // Helper to build the builtin call with the common operands.
-        auto build = [&](const core::type::Type* type, enum spirv::ir::Function intrinsic) {
-            return b.Call<spirv::ir::BuiltinCall>(type, intrinsic, pointer, memory,
+        auto build = [&](const core::type::Type* type, enum spirv::BuiltinFn builtin_fn) {
+            return b.Call<spirv::ir::BuiltinCall>(type, builtin_fn, pointer, memory,
                                                   memory_semantics);
         };
 
         // Create the replacement call instruction.
         core::ir::Call* call = nullptr;
         switch (builtin->Func()) {
-            case core::Function::kAtomicAdd:
-                call = build(result_ty, spirv::ir::Function::kAtomicIadd);
+            case core::BuiltinFn::kAtomicAdd:
+                call = build(result_ty, spirv::BuiltinFn::kAtomicIadd);
                 call->AppendArg(builtin->Args()[1]);
                 break;
-            case core::Function::kAtomicAnd:
-                call = build(result_ty, spirv::ir::Function::kAtomicAnd);
+            case core::BuiltinFn::kAtomicAnd:
+                call = build(result_ty, spirv::BuiltinFn::kAtomicAnd);
                 call->AppendArg(builtin->Args()[1]);
                 break;
-            case core::Function::kAtomicCompareExchangeWeak: {
+            case core::BuiltinFn::kAtomicCompareExchangeWeak: {
                 auto* cmp = builtin->Args()[1];
                 auto* value = builtin->Args()[2];
                 auto* int_ty = value->Type();
-                call = build(int_ty, spirv::ir::Function::kAtomicCompareExchange);
+                call = build(int_ty, spirv::BuiltinFn::kAtomicCompareExchange);
                 call->AppendArg(memory_semantics);
                 call->AppendArg(value);
                 call->AppendArg(cmp);
@@ -260,43 +259,43 @@
                     Vector{original, compare->Result()});
                 break;
             }
-            case core::Function::kAtomicExchange:
-                call = build(result_ty, spirv::ir::Function::kAtomicExchange);
+            case core::BuiltinFn::kAtomicExchange:
+                call = build(result_ty, spirv::BuiltinFn::kAtomicExchange);
                 call->AppendArg(builtin->Args()[1]);
                 break;
-            case core::Function::kAtomicLoad:
-                call = build(result_ty, spirv::ir::Function::kAtomicLoad);
+            case core::BuiltinFn::kAtomicLoad:
+                call = build(result_ty, spirv::BuiltinFn::kAtomicLoad);
                 break;
-            case core::Function::kAtomicOr:
-                call = build(result_ty, spirv::ir::Function::kAtomicOr);
+            case core::BuiltinFn::kAtomicOr:
+                call = build(result_ty, spirv::BuiltinFn::kAtomicOr);
                 call->AppendArg(builtin->Args()[1]);
                 break;
-            case core::Function::kAtomicMax:
+            case core::BuiltinFn::kAtomicMax:
                 if (result_ty->is_signed_integer_scalar()) {
-                    call = build(result_ty, spirv::ir::Function::kAtomicSmax);
+                    call = build(result_ty, spirv::BuiltinFn::kAtomicSmax);
                 } else {
-                    call = build(result_ty, spirv::ir::Function::kAtomicUmax);
+                    call = build(result_ty, spirv::BuiltinFn::kAtomicUmax);
                 }
                 call->AppendArg(builtin->Args()[1]);
                 break;
-            case core::Function::kAtomicMin:
+            case core::BuiltinFn::kAtomicMin:
                 if (result_ty->is_signed_integer_scalar()) {
-                    call = build(result_ty, spirv::ir::Function::kAtomicSmin);
+                    call = build(result_ty, spirv::BuiltinFn::kAtomicSmin);
                 } else {
-                    call = build(result_ty, spirv::ir::Function::kAtomicUmin);
+                    call = build(result_ty, spirv::BuiltinFn::kAtomicUmin);
                 }
                 call->AppendArg(builtin->Args()[1]);
                 break;
-            case core::Function::kAtomicStore:
-                call = build(result_ty, spirv::ir::Function::kAtomicStore);
+            case core::BuiltinFn::kAtomicStore:
+                call = build(result_ty, spirv::BuiltinFn::kAtomicStore);
                 call->AppendArg(builtin->Args()[1]);
                 break;
-            case core::Function::kAtomicSub:
-                call = build(result_ty, spirv::ir::Function::kAtomicIsub);
+            case core::BuiltinFn::kAtomicSub:
+                call = build(result_ty, spirv::BuiltinFn::kAtomicIsub);
                 call->AppendArg(builtin->Args()[1]);
                 break;
-            case core::Function::kAtomicXor:
-                call = build(result_ty, spirv::ir::Function::kAtomicXor);
+            case core::BuiltinFn::kAtomicXor:
+                call = build(result_ty, spirv::BuiltinFn::kAtomicXor);
                 call->AppendArg(builtin->Args()[1]);
                 break;
             default:
@@ -338,7 +337,7 @@
         // Replace the builtin call with a call to the spirv.dot intrinsic.
         auto args = Vector<core::ir::Value*, 4>(builtin->Args());
         auto* call = b.Call<spirv::ir::BuiltinCall>(builtin->Result()->Type(),
-                                                    spirv::ir::Function::kDot, std::move(args));
+                                                    spirv::BuiltinFn::kDot, std::move(args));
         call->InsertBefore(builtin);
         return call->Result();
     }
@@ -369,7 +368,7 @@
 
         // Replace the builtin call with a call to the spirv.select intrinsic.
         auto* call = b.Call<spirv::ir::BuiltinCall>(builtin->Result()->Type(),
-                                                    spirv::ir::Function::kSelect, std::move(args));
+                                                    spirv::BuiltinFn::kSelect, std::move(args));
         call->InsertBefore(builtin);
         return call->Result();
     }
@@ -480,7 +479,7 @@
 
         // Use OpSampledImage to create an OpTypeSampledImage object.
         auto* sampled_image = b.Call<spirv::ir::BuiltinCall>(ty.Get<type::SampledImage>(texture_ty),
-                                                             spirv::ir::Function::kSampledImage,
+                                                             spirv::BuiltinFn::kSampledImage,
                                                              Vector{texture, sampler});
         sampled_image->InsertBefore(builtin);
 
@@ -490,39 +489,39 @@
             coords = AppendArrayIndex(coords, array_idx, builtin);
         }
 
-        // Determine which SPIR-V intrinsic to use and which optional image operands are needed.
-        enum spirv::ir::Intrinsic intrinsic;
+        // Determine which SPIR-V function to use and which optional image operands are needed.
+        enum spirv::BuiltinFn function;
         core::ir::Value* depth = nullptr;
         ImageOperands operands;
         switch (builtin->Func()) {
-            case core::Function::kTextureSample:
-                intrinsic = spirv::ir::Intrinsic::kImageSampleImplicitLod;
+            case core::BuiltinFn::kTextureSample:
+                function = spirv::BuiltinFn::kImageSampleImplicitLod;
                 operands.offset = next_arg();
                 break;
-            case core::Function::kTextureSampleBias:
-                intrinsic = spirv::ir::Intrinsic::kImageSampleImplicitLod;
+            case core::BuiltinFn::kTextureSampleBias:
+                function = spirv::BuiltinFn::kImageSampleImplicitLod;
                 operands.bias = next_arg();
                 operands.offset = next_arg();
                 break;
-            case core::Function::kTextureSampleCompare:
-                intrinsic = spirv::ir::Intrinsic::kImageSampleDrefImplicitLod;
+            case core::BuiltinFn::kTextureSampleCompare:
+                function = spirv::BuiltinFn::kImageSampleDrefImplicitLod;
                 depth = next_arg();
                 operands.offset = next_arg();
                 break;
-            case core::Function::kTextureSampleCompareLevel:
-                intrinsic = spirv::ir::Intrinsic::kImageSampleDrefExplicitLod;
+            case core::BuiltinFn::kTextureSampleCompareLevel:
+                function = spirv::BuiltinFn::kImageSampleDrefExplicitLod;
                 depth = next_arg();
                 operands.lod = b.Constant(0_f);
                 operands.offset = next_arg();
                 break;
-            case core::Function::kTextureSampleGrad:
-                intrinsic = spirv::ir::Intrinsic::kImageSampleExplicitLod;
+            case core::BuiltinFn::kTextureSampleGrad:
+                function = spirv::BuiltinFn::kImageSampleExplicitLod;
                 operands.ddx = next_arg();
                 operands.ddy = next_arg();
                 operands.offset = next_arg();
                 break;
-            case core::Function::kTextureSampleLevel:
-                intrinsic = spirv::ir::Intrinsic::kImageSampleExplicitLod;
+            case core::BuiltinFn::kTextureSampleLevel:
+                function = spirv::BuiltinFn::kImageSampleExplicitLod;
                 operands.lod = next_arg();
                 operands.offset = next_arg();
                 break;
@@ -530,24 +529,24 @@
                 return nullptr;
         }
 
-        // Start building the argument list for the intrinsic.
+        // Start building the argument list for the function.
         // The first two operands are always the sampled image and then the coordinates, followed by
         // the depth reference if used.
-        Vector<core::ir::Value*, 8> intrinsic_args;
-        intrinsic_args.Push(sampled_image->Result());
-        intrinsic_args.Push(coords);
+        Vector<core::ir::Value*, 8> function_args;
+        function_args.Push(sampled_image->Result());
+        function_args.Push(coords);
         if (depth) {
-            intrinsic_args.Push(depth);
+            function_args.Push(depth);
         }
 
         // Add the optional image operands, if any.
-        AppendImageOperands(operands, intrinsic_args, builtin, /* requires_float_lod */ true);
+        AppendImageOperands(operands, function_args, builtin, /* requires_float_lod */ true);
 
-        // Call the intrinsic.
+        // Call the function.
         // If this is a depth comparison, the result is always f32, otherwise vec4f.
         auto* result_ty = depth ? static_cast<const core::type::Type*>(ty.f32()) : ty.vec4<f32>();
         auto* texture_call =
-            b.Call<spirv::ir::IntrinsicCall>(result_ty, intrinsic, std::move(intrinsic_args));
+            b.Call<spirv::ir::BuiltinCall>(result_ty, function, std::move(function_args));
         texture_call->InsertBefore(builtin);
 
         auto* result = texture_call->Result();
@@ -588,7 +587,7 @@
 
         // Use OpSampledImage to create an OpTypeSampledImage object.
         auto* sampled_image = b.Call<spirv::ir::BuiltinCall>(ty.Get<type::SampledImage>(texture_ty),
-                                                             spirv::ir::Function::kSampledImage,
+                                                             spirv::BuiltinFn::kSampledImage,
                                                              Vector{texture, sampler});
         sampled_image->InsertBefore(builtin);
 
@@ -599,16 +598,16 @@
         }
 
         // Determine which SPIR-V function to use and which optional image operands are needed.
-        enum spirv::ir::Function function;
+        enum spirv::BuiltinFn function;
         core::ir::Value* depth = nullptr;
         ImageOperands operands;
         switch (builtin->Func()) {
-            case core::Function::kTextureGather:
-                function = spirv::ir::Function::kImageGather;
+            case core::BuiltinFn::kTextureGather:
+                function = spirv::BuiltinFn::kImageGather;
                 operands.offset = next_arg();
                 break;
-            case core::Function::kTextureGatherCompare:
-                function = spirv::ir::Function::kImageDrefGather;
+            case core::BuiltinFn::kTextureGatherCompare:
+                function = spirv::BuiltinFn::kImageDrefGather;
                 depth = next_arg();
                 operands.offset = next_arg();
                 break;
@@ -682,8 +681,8 @@
         if (expects_scalar_result) {
             result_ty = ty.vec4(result_ty);
         }
-        auto kind = texture_ty->Is<core::type::StorageTexture>() ? spirv::ir::Function::kImageRead
-                                                                 : spirv::ir::Function::kImageFetch;
+        auto kind = texture_ty->Is<core::type::StorageTexture>() ? spirv::BuiltinFn::kImageRead
+                                                                 : spirv::BuiltinFn::kImageFetch;
         auto* texture_call =
             b.Call<spirv::ir::BuiltinCall>(result_ty, kind, std::move(builtin_args));
         texture_call->InsertBefore(builtin);
@@ -721,19 +720,19 @@
 
         auto* texel = next_arg();
 
-        // Start building the argument list for the intrinsic.
+        // Start building the argument list for the function.
         // The first two operands are always the texture and then the coordinates.
-        Vector<core::ir::Value*, 8> intrinsic_args;
-        intrinsic_args.Push(texture);
-        intrinsic_args.Push(coords);
-        intrinsic_args.Push(texel);
+        Vector<core::ir::Value*, 8> function_args;
+        function_args.Push(texture);
+        function_args.Push(coords);
+        function_args.Push(texel);
 
         ImageOperands operands;
-        AppendImageOperands(operands, intrinsic_args, builtin, /* requires_float_lod */ false);
+        AppendImageOperands(operands, function_args, builtin, /* requires_float_lod */ false);
 
-        // Call the intrinsic.
-        auto* texture_call = b.Call<spirv::ir::IntrinsicCall>(
-            ty.void_(), spirv::ir::Intrinsic::kImageWrite, std::move(intrinsic_args));
+        // Call the function.
+        auto* texture_call = b.Call<spirv::ir::BuiltinCall>(
+            ty.void_(), spirv::BuiltinFn::kImageWrite, std::move(function_args));
         texture_call->InsertBefore(builtin);
         return texture_call->Result();
     }
@@ -755,13 +754,13 @@
         function_args.Push(texture);
 
         // Determine which SPIR-V function to use, and add the Lod argument if needed.
-        enum spirv::ir::Function function;
+        enum spirv::BuiltinFn function;
         if (texture_ty
                 ->IsAnyOf<core::type::MultisampledTexture, core::type::DepthMultisampledTexture,
                           core::type::StorageTexture>()) {
-            function = spirv::ir::Function::kImageQuerySize;
+            function = spirv::BuiltinFn::kImageQuerySize;
         } else {
-            function = spirv::ir::Function::kImageQuerySizeLod;
+            function = spirv::BuiltinFn::kImageQuerySizeLod;
             if (auto* lod = next_arg()) {
                 function_args.Push(lod);
             } else {
@@ -805,13 +804,13 @@
         function_args.Push(texture);
 
         // Determine which SPIR-V function to use, and add the Lod argument if needed.
-        enum spirv::ir::Function function;
+        enum spirv::BuiltinFn function;
         if (texture_ty
                 ->IsAnyOf<core::type::MultisampledTexture, core::type::DepthMultisampledTexture,
                           core::type::StorageTexture>()) {
-            function = spirv::ir::Function::kImageQuerySize;
+            function = spirv::BuiltinFn::kImageQuerySize;
         } else {
-            function = spirv::ir::Function::kImageQuerySizeLod;
+            function = spirv::BuiltinFn::kImageQuerySizeLod;
             function_args.Push(b.Constant(0_u));
         }
 
@@ -839,7 +838,7 @@
         Vector<core::ir::Value*, 4> args;
         for (uint32_t i = 0; i < vec->Width(); i++) {
             auto* el = b.Access(ty.f32(), arg, u32(i));
-            auto* scalar_call = b.Call(ty.f32(), core::Function::kQuantizeToF16, el);
+            auto* scalar_call = b.Call(ty.f32(), core::BuiltinFn::kQuantizeToF16, el);
             args.Push(scalar_call->Result());
             el->InsertBefore(builtin);
             scalar_call->InsertBefore(builtin);
diff --git a/src/tint/lang/spirv/writer/raise/builtin_polyfill_test.cc b/src/tint/lang/spirv/writer/raise/builtin_polyfill_test.cc
index d20393f..5bd292a 100644
--- a/src/tint/lang/spirv/writer/raise/builtin_polyfill_test.cc
+++ b/src/tint/lang/spirv/writer/raise/builtin_polyfill_test.cc
@@ -47,7 +47,7 @@
     auto* func = b.Function("foo", ty.u32());
     b.Append(func->Block(), [&] {
         auto* access = b.Access(ty.ptr(storage, arr), var, 2_u);
-        auto* result = b.Call(ty.u32(), core::Function::kArrayLength, access);
+        auto* result = b.Call(ty.u32(), core::BuiltinFn::kArrayLength, access);
         b.Return(func, result);
     });
 
@@ -113,7 +113,7 @@
         auto* let_a = b.Let("a", var);
         auto* let_b = b.Let("b", let_a);
         auto* access = b.Access(ty.ptr(storage, arr), let_b, 2_u);
-        auto* result = b.Call(ty.u32(), core::Function::kArrayLength, access);
+        auto* result = b.Call(ty.u32(), core::BuiltinFn::kArrayLength, access);
         b.Return(func, result);
     });
 
@@ -183,7 +183,7 @@
         auto* access = b.Access(ty.ptr(storage, arr), var, 2_u);
         auto* let_a = b.Let("a", access);
         auto* let_b = b.Let("b", let_a);
-        auto* result = b.Call(ty.u32(), core::Function::kArrayLength, let_b);
+        auto* result = b.Call(ty.u32(), core::BuiltinFn::kArrayLength, let_b);
         b.Return(func, result);
     });
 
@@ -247,7 +247,7 @@
     func->SetParams({arg1});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.i32(), core::Function::kAtomicAdd, var, arg1);
+        auto* result = b.Call(ty.i32(), core::BuiltinFn::kAtomicAdd, var, arg1);
         b.Return(func, result);
     });
 
@@ -291,7 +291,7 @@
     func->SetParams({arg1});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.i32(), core::Function::kAtomicAdd, var, arg1);
+        auto* result = b.Call(ty.i32(), core::BuiltinFn::kAtomicAdd, var, arg1);
         b.Return(func, result);
     });
 
@@ -335,7 +335,7 @@
     func->SetParams({arg1});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.i32(), core::Function::kAtomicAnd, var, arg1);
+        auto* result = b.Call(ty.i32(), core::BuiltinFn::kAtomicAnd, var, arg1);
         b.Return(func, result);
     });
 
@@ -381,7 +381,8 @@
 
     b.Append(func->Block(), [&] {
         auto* result_ty = core::type::CreateAtomicCompareExchangeResult(ty, mod.symbols, ty.i32());
-        auto* result = b.Call(result_ty, core::Function::kAtomicCompareExchangeWeak, var, cmp, val);
+        auto* result =
+            b.Call(result_ty, core::BuiltinFn::kAtomicCompareExchangeWeak, var, cmp, val);
         b.Return(func, b.Access(ty.i32(), result, 0_u));
     });
 
@@ -439,7 +440,7 @@
     func->SetParams({arg1});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.i32(), core::Function::kAtomicExchange, var, arg1);
+        auto* result = b.Call(ty.i32(), core::BuiltinFn::kAtomicExchange, var, arg1);
         b.Return(func, result);
     });
 
@@ -481,7 +482,7 @@
     auto* func = b.Function("foo", ty.i32());
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.i32(), core::Function::kAtomicLoad, var);
+        auto* result = b.Call(ty.i32(), core::BuiltinFn::kAtomicLoad, var);
         b.Return(func, result);
     });
 
@@ -525,7 +526,7 @@
     func->SetParams({arg1});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.i32(), core::Function::kAtomicMax, var, arg1);
+        auto* result = b.Call(ty.i32(), core::BuiltinFn::kAtomicMax, var, arg1);
         b.Return(func, result);
     });
 
@@ -569,7 +570,7 @@
     func->SetParams({arg1});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.u32(), core::Function::kAtomicMax, var, arg1);
+        auto* result = b.Call(ty.u32(), core::BuiltinFn::kAtomicMax, var, arg1);
         b.Return(func, result);
     });
 
@@ -613,7 +614,7 @@
     func->SetParams({arg1});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.i32(), core::Function::kAtomicMin, var, arg1);
+        auto* result = b.Call(ty.i32(), core::BuiltinFn::kAtomicMin, var, arg1);
         b.Return(func, result);
     });
 
@@ -657,7 +658,7 @@
     func->SetParams({arg1});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.u32(), core::Function::kAtomicMin, var, arg1);
+        auto* result = b.Call(ty.u32(), core::BuiltinFn::kAtomicMin, var, arg1);
         b.Return(func, result);
     });
 
@@ -701,7 +702,7 @@
     func->SetParams({arg1});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.i32(), core::Function::kAtomicOr, var, arg1);
+        auto* result = b.Call(ty.i32(), core::BuiltinFn::kAtomicOr, var, arg1);
         b.Return(func, result);
     });
 
@@ -745,7 +746,7 @@
     func->SetParams({arg1});
 
     b.Append(func->Block(), [&] {
-        b.Call(ty.void_(), core::Function::kAtomicStore, var, arg1);
+        b.Call(ty.void_(), core::BuiltinFn::kAtomicStore, var, arg1);
         b.Return(func);
     });
 
@@ -789,7 +790,7 @@
     func->SetParams({arg1});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.i32(), core::Function::kAtomicSub, var, arg1);
+        auto* result = b.Call(ty.i32(), core::BuiltinFn::kAtomicSub, var, arg1);
         b.Return(func, result);
     });
 
@@ -833,7 +834,7 @@
     func->SetParams({arg1});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.i32(), core::Function::kAtomicXor, var, arg1);
+        auto* result = b.Call(ty.i32(), core::BuiltinFn::kAtomicXor, var, arg1);
         b.Return(func, result);
     });
 
@@ -876,7 +877,7 @@
     func->SetParams({arg1, arg2});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.f32(), core::Function::kDot, arg1, arg2);
+        auto* result = b.Call(ty.f32(), core::BuiltinFn::kDot, arg1, arg2);
         b.Return(func, result);
     });
 
@@ -911,7 +912,7 @@
     func->SetParams({arg1, arg2});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.i32(), core::Function::kDot, arg1, arg2);
+        auto* result = b.Call(ty.i32(), core::BuiltinFn::kDot, arg1, arg2);
         b.Return(func, result);
     });
 
@@ -952,7 +953,7 @@
     func->SetParams({arg1, arg2});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.u32(), core::Function::kDot, arg1, arg2);
+        auto* result = b.Call(ty.u32(), core::BuiltinFn::kDot, arg1, arg2);
         b.Return(func, result);
     });
 
@@ -1002,7 +1003,7 @@
     func->SetParams({argf, argt, cond});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.i32(), core::Function::kSelect, argf, argt, cond);
+        auto* result = b.Call(ty.i32(), core::BuiltinFn::kSelect, argf, argt, cond);
         b.Return(func, result);
     });
 
@@ -1038,7 +1039,7 @@
     func->SetParams({argf, argt, cond});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.vec4<i32>(), core::Function::kSelect, argf, argt, cond);
+        auto* result = b.Call(ty.vec4<i32>(), core::BuiltinFn::kSelect, argf, argt, cond);
         b.Return(func, result);
     });
 
@@ -1074,7 +1075,7 @@
     func->SetParams({argf, argt, cond});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.vec4<i32>(), core::Function::kSelect, argf, argt, cond);
+        auto* result = b.Call(ty.vec4<i32>(), core::BuiltinFn::kSelect, argf, argt, cond);
         b.Return(func, result);
     });
 
@@ -1112,7 +1113,7 @@
     func->SetParams({t, coords, lod});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.vec4<f32>(), core::Function::kTextureLoad, t, coords, lod);
+        auto* result = b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureLoad, t, coords, lod);
         b.Return(func, result);
     });
 
@@ -1151,7 +1152,7 @@
 
     b.Append(func->Block(), [&] {
         auto* result =
-            b.Call(ty.vec4<f32>(), core::Function::kTextureLoad, t, coords, array_idx, lod);
+            b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureLoad, t, coords, array_idx, lod);
         b.Return(func, result);
     });
 
@@ -1191,7 +1192,7 @@
 
     b.Append(func->Block(), [&] {
         auto* result =
-            b.Call(ty.vec4<f32>(), core::Function::kTextureLoad, t, coords, array_idx, lod);
+            b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureLoad, t, coords, array_idx, lod);
         b.Return(func, result);
     });
 
@@ -1230,7 +1231,7 @@
     func->SetParams({t, coords, sample_idx});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.vec4<f32>(), core::Function::kTextureLoad, t, coords, sample_idx);
+        auto* result = b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureLoad, t, coords, sample_idx);
         b.Return(func, result);
     });
 
@@ -1267,7 +1268,7 @@
     func->SetParams({t, coords, lod});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.f32(), core::Function::kTextureLoad, t, coords, lod);
+        auto* result = b.Call(ty.f32(), core::BuiltinFn::kTextureLoad, t, coords, lod);
         b.Return(func, result);
     });
 
@@ -1305,7 +1306,7 @@
     func->SetParams({t, s, coords});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.vec4<f32>(), core::Function::kTextureSample, t, s, coords);
+        auto* result = b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureSample, t, s, coords);
         b.Return(func, result);
     });
 
@@ -1343,7 +1344,7 @@
     func->SetParams({t, s, coords});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.vec4<f32>(), core::Function::kTextureSample, t, s, coords);
+        auto* result = b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureSample, t, s, coords);
         b.Return(func, result);
     });
 
@@ -1381,7 +1382,7 @@
     func->SetParams({t, s, coords});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.vec4<f32>(), core::Function::kTextureSample, t, s, coords,
+        auto* result = b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureSample, t, s, coords,
                               b.Splat(ty.vec2<i32>(), 1_i, 2));
         b.Return(func, result);
     });
@@ -1421,7 +1422,7 @@
     func->SetParams({t, s, coords, array_idx});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.vec4<f32>(), core::Function::kTextureSample, t, s, coords,
+        auto* result = b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureSample, t, s, coords,
                               array_idx, b.Splat(ty.vec2<i32>(), 1_i, 2));
         b.Return(func, result);
     });
@@ -1464,7 +1465,7 @@
 
     b.Append(func->Block(), [&] {
         auto* result =
-            b.Call(ty.vec4<f32>(), core::Function::kTextureSampleBias, t, s, coords, bias);
+            b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureSampleBias, t, s, coords, bias);
         b.Return(func, result);
     });
 
@@ -1503,7 +1504,7 @@
     func->SetParams({t, s, coords, bias});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.vec4<f32>(), core::Function::kTextureSampleBias, t, s, coords,
+        auto* result = b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureSampleBias, t, s, coords,
                               bias, b.Splat(ty.vec2<i32>(), 1_i, 2));
         b.Return(func, result);
     });
@@ -1544,7 +1545,7 @@
     func->SetParams({t, s, coords, array_idx, bias});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.vec4<f32>(), core::Function::kTextureSampleBias, t, s, coords,
+        auto* result = b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureSampleBias, t, s, coords,
                               array_idx, bias, b.Splat(ty.vec2<i32>(), 1_i, 2));
         b.Return(func, result);
     });
@@ -1586,7 +1587,7 @@
     func->SetParams({t, s, coords, dref});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.f32(), core::Function::kTextureSampleCompare, t, s, coords, dref);
+        auto* result = b.Call(ty.f32(), core::BuiltinFn::kTextureSampleCompare, t, s, coords, dref);
         b.Return(func, result);
     });
 
@@ -1625,7 +1626,7 @@
     func->SetParams({t, s, coords, dref});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.f32(), core::Function::kTextureSampleCompare, t, s, coords, dref,
+        auto* result = b.Call(ty.f32(), core::BuiltinFn::kTextureSampleCompare, t, s, coords, dref,
                               b.Splat(ty.vec2<i32>(), 1_i, 2));
         b.Return(func, result);
     });
@@ -1666,7 +1667,7 @@
     func->SetParams({t, s, coords, array_idx, bias});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.f32(), core::Function::kTextureSampleCompare, t, s, coords,
+        auto* result = b.Call(ty.f32(), core::BuiltinFn::kTextureSampleCompare, t, s, coords,
                               array_idx, bias, b.Splat(ty.vec2<i32>(), 1_i, 2));
         b.Return(func, result);
     });
@@ -1709,7 +1710,7 @@
 
     b.Append(func->Block(), [&] {
         auto* result =
-            b.Call(ty.f32(), core::Function::kTextureSampleCompareLevel, t, s, coords, dref);
+            b.Call(ty.f32(), core::BuiltinFn::kTextureSampleCompareLevel, t, s, coords, dref);
         b.Return(func, result);
     });
 
@@ -1748,7 +1749,7 @@
     func->SetParams({t, s, coords, dref});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.f32(), core::Function::kTextureSampleCompareLevel, t, s, coords,
+        auto* result = b.Call(ty.f32(), core::BuiltinFn::kTextureSampleCompareLevel, t, s, coords,
                               dref, b.Splat(ty.vec2<i32>(), 1_i, 2));
         b.Return(func, result);
     });
@@ -1789,7 +1790,7 @@
     func->SetParams({t, s, coords, array_idx, bias});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.f32(), core::Function::kTextureSampleCompareLevel, t, s, coords,
+        auto* result = b.Call(ty.f32(), core::BuiltinFn::kTextureSampleCompareLevel, t, s, coords,
                               array_idx, bias, b.Splat(ty.vec2<i32>(), 1_i, 2));
         b.Return(func, result);
     });
@@ -1833,7 +1834,7 @@
 
     b.Append(func->Block(), [&] {
         auto* result =
-            b.Call(ty.vec4<f32>(), core::Function::kTextureSampleGrad, t, s, coords, ddx, ddy);
+            b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureSampleGrad, t, s, coords, ddx, ddy);
         b.Return(func, result);
     });
 
@@ -1873,8 +1874,8 @@
     func->SetParams({t, s, coords, ddx, ddy});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.vec4<f32>(), core::Function::kTextureSampleGrad, t, s, coords, ddx,
-                              ddy, b.Splat(ty.vec2<i32>(), 1_i, 2));
+        auto* result = b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureSampleGrad, t, s, coords,
+                              ddx, ddy, b.Splat(ty.vec2<i32>(), 1_i, 2));
         b.Return(func, result);
     });
 
@@ -1915,7 +1916,7 @@
     func->SetParams({t, s, coords, array_idx, ddx, ddy});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.vec4<f32>(), core::Function::kTextureSampleGrad, t, s, coords,
+        auto* result = b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureSampleGrad, t, s, coords,
                               array_idx, ddx, ddy, b.Splat(ty.vec2<i32>(), 1_i, 2));
         b.Return(func, result);
     });
@@ -1958,7 +1959,7 @@
 
     b.Append(func->Block(), [&] {
         auto* result =
-            b.Call(ty.vec4<f32>(), core::Function::kTextureSampleLevel, t, s, coords, lod);
+            b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureSampleLevel, t, s, coords, lod);
         b.Return(func, result);
     });
 
@@ -1997,7 +1998,7 @@
     func->SetParams({t, s, coords, lod});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.vec4<f32>(), core::Function::kTextureSampleLevel, t, s, coords,
+        auto* result = b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureSampleLevel, t, s, coords,
                               lod, b.Splat(ty.vec2<i32>(), 1_i, 2));
         b.Return(func, result);
     });
@@ -2038,7 +2039,7 @@
     func->SetParams({t, s, coords, array_idx, lod});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.vec4<f32>(), core::Function::kTextureSampleLevel, t, s, coords,
+        auto* result = b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureSampleLevel, t, s, coords,
                               array_idx, lod, b.Splat(ty.vec2<i32>(), 1_i, 2));
         b.Return(func, result);
     });
@@ -2081,7 +2082,7 @@
 
     b.Append(func->Block(), [&] {
         auto* result =
-            b.Call(ty.vec4<f32>(), core::Function::kTextureGather, component, t, s, coords);
+            b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureGather, component, t, s, coords);
         b.Return(func, result);
     });
 
@@ -2120,7 +2121,7 @@
     func->SetParams({t, s, component, coords});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.vec4<f32>(), core::Function::kTextureGather, component, t, s,
+        auto* result = b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureGather, component, t, s,
                               coords, b.Splat(ty.vec2<i32>(), 1_i, 2));
         b.Return(func, result);
     });
@@ -2161,7 +2162,7 @@
     func->SetParams({t, s, component, coords, array_idx});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.vec4<f32>(), core::Function::kTextureGather, component, t, s,
+        auto* result = b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureGather, component, t, s,
                               coords, array_idx, b.Splat(ty.vec2<i32>(), 1_i, 2));
         b.Return(func, result);
     });
@@ -2202,7 +2203,7 @@
     func->SetParams({t, s, coords});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.vec4<f32>(), core::Function::kTextureGather, t, s, coords);
+        auto* result = b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureGather, t, s, coords);
         b.Return(func, result);
     });
 
@@ -2242,7 +2243,7 @@
 
     b.Append(func->Block(), [&] {
         auto* result =
-            b.Call(ty.vec4<f32>(), core::Function::kTextureGatherCompare, t, s, coords, depth);
+            b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureGatherCompare, t, s, coords, depth);
         b.Return(func, result);
     });
 
@@ -2281,7 +2282,7 @@
     func->SetParams({t, s, coords, depth});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.vec4<f32>(), core::Function::kTextureGatherCompare, t, s, coords,
+        auto* result = b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureGatherCompare, t, s, coords,
                               depth, b.Splat(ty.vec2<i32>(), 1_i, 2));
         b.Return(func, result);
     });
@@ -2322,7 +2323,7 @@
     func->SetParams({t, s, coords, array_idx, depth});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.vec4<f32>(), core::Function::kTextureGatherCompare, t, s, coords,
+        auto* result = b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureGatherCompare, t, s, coords,
                               array_idx, depth);
         b.Return(func, result);
     });
@@ -2366,7 +2367,7 @@
     func->SetParams({t, coords, texel});
 
     b.Append(func->Block(), [&] {
-        b.Call(ty.void_(), core::Function::kTextureStore, t, coords, texel);
+        b.Call(ty.void_(), core::BuiltinFn::kTextureStore, t, coords, texel);
         b.Return(func);
     });
 
@@ -2407,7 +2408,7 @@
     func->SetParams({t, coords, array_idx, texel});
 
     b.Append(func->Block(), [&] {
-        b.Call(ty.void_(), core::Function::kTextureStore, t, coords, array_idx, texel);
+        b.Call(ty.void_(), core::BuiltinFn::kTextureStore, t, coords, array_idx, texel);
         b.Return(func);
     });
 
@@ -2449,7 +2450,7 @@
     func->SetParams({t, coords, array_idx, texel});
 
     b.Append(func->Block(), [&] {
-        b.Call(ty.void_(), core::Function::kTextureStore, t, coords, array_idx, texel);
+        b.Call(ty.void_(), core::BuiltinFn::kTextureStore, t, coords, array_idx, texel);
         b.Return(func);
     });
 
@@ -2486,7 +2487,7 @@
     func->SetParams({t});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.vec2<u32>(), core::Function::kTextureDimensions, t);
+        auto* result = b.Call(ty.vec2<u32>(), core::BuiltinFn::kTextureDimensions, t);
         b.Return(func, result);
     });
 
@@ -2522,7 +2523,7 @@
     func->SetParams({t, lod});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.vec2<u32>(), core::Function::kTextureDimensions, t, lod);
+        auto* result = b.Call(ty.vec2<u32>(), core::BuiltinFn::kTextureDimensions, t, lod);
         b.Return(func, result);
     });
 
@@ -2557,7 +2558,7 @@
     func->SetParams({t});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.vec2<u32>(), core::Function::kTextureDimensions, t);
+        auto* result = b.Call(ty.vec2<u32>(), core::BuiltinFn::kTextureDimensions, t);
         b.Return(func, result);
     });
 
@@ -2593,7 +2594,7 @@
     func->SetParams({t});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.vec2<u32>(), core::Function::kTextureDimensions, t);
+        auto* result = b.Call(ty.vec2<u32>(), core::BuiltinFn::kTextureDimensions, t);
         b.Return(func, result);
     });
 
@@ -2628,7 +2629,7 @@
     func->SetParams({t});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.u32(), core::Function::kTextureNumLayers, t);
+        auto* result = b.Call(ty.u32(), core::BuiltinFn::kTextureNumLayers, t);
         b.Return(func, result);
     });
 
@@ -2664,7 +2665,7 @@
     func->SetParams({t});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.u32(), core::Function::kTextureNumLayers, t);
+        auto* result = b.Call(ty.u32(), core::BuiltinFn::kTextureNumLayers, t);
         b.Return(func, result);
     });
 
@@ -2700,7 +2701,7 @@
     func->SetParams({t});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.u32(), core::Function::kTextureNumLayers, t);
+        auto* result = b.Call(ty.u32(), core::BuiltinFn::kTextureNumLayers, t);
         b.Return(func, result);
     });
 
@@ -2736,7 +2737,7 @@
     func->SetParams({t});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.u32(), core::Function::kTextureNumLayers, t);
+        auto* result = b.Call(ty.u32(), core::BuiltinFn::kTextureNumLayers, t);
         b.Return(func, result);
     });
 
@@ -2775,7 +2776,7 @@
     func->SetParams({t});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.u32(), core::Function::kTextureNumLayers, t);
+        auto* result = b.Call(ty.u32(), core::BuiltinFn::kTextureNumLayers, t);
         b.Return(func, result);
     });
 
@@ -2810,7 +2811,7 @@
     func->SetParams({arg});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.f32(), core::Function::kQuantizeToF16, arg);
+        auto* result = b.Call(ty.f32(), core::BuiltinFn::kQuantizeToF16, arg);
         b.Return(func, result);
     });
 
@@ -2837,7 +2838,7 @@
     func->SetParams({arg});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.vec4<f32>(), core::Function::kQuantizeToF16, arg);
+        auto* result = b.Call(ty.vec4<f32>(), core::BuiltinFn::kQuantizeToF16, arg);
         b.Return(func, result);
     });
 
diff --git a/src/tint/lang/spirv/writer/raise/expand_implicit_splats.cc b/src/tint/lang/spirv/writer/raise/expand_implicit_splats.cc
index c222b71..775bf53 100644
--- a/src/tint/lang/spirv/writer/raise/expand_implicit_splats.cc
+++ b/src/tint/lang/spirv/writer/raise/expand_implicit_splats.cc
@@ -19,8 +19,8 @@
 #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/spirv/builtin_fn.h"
 #include "src/tint/lang/spirv/ir/builtin_call.h"
-#include "src/tint/lang/spirv/ir/function.h"
 
 using namespace tint::core::number_suffixes;  // NOLINT
 
@@ -62,7 +62,7 @@
         } else if (auto* builtin = inst->As<core::ir::CoreBuiltinCall>()) {
             // A mix builtin call that mixes vector and scalar operands needs to have the scalar
             // operand replaced with an explicit vector constructor.
-            if (builtin->Func() == core::Function::kMix) {
+            if (builtin->Func() == core::BuiltinFn::kMix) {
                 if (builtin->Result()->Type()->Is<core::type::Vector>()) {
                     if (builtin->Args()[2]->Type()->Is<core::type::Scalar>()) {
                         builtin_worklist.Push(builtin);
@@ -91,7 +91,7 @@
         if (result_ty->is_float_vector() && binary->Kind() == core::ir::Binary::Kind::kMultiply) {
             // Use OpVectorTimesScalar for floating point multiply.
             auto* vts =
-                b.Call<spirv::ir::BuiltinCall>(result_ty, spirv::ir::Function::kVectorTimesScalar);
+                b.Call<spirv::ir::BuiltinCall>(result_ty, spirv::BuiltinFn::kVectorTimesScalar);
             if (binary->LHS()->Type()->Is<core::type::Scalar>()) {
                 vts->AppendArg(binary->RHS());
                 vts->AppendArg(binary->LHS());
@@ -118,7 +118,7 @@
     // Replace scalar arguments to builtin calls that produce vectors.
     for (auto* builtin : builtin_worklist) {
         switch (builtin->Func()) {
-            case core::Function::kMix:
+            case core::BuiltinFn::kMix:
                 // Expand the scalar argument into an explicitly constructed vector.
                 expand_operand(builtin, core::ir::CoreBuiltinCall::kArgsOperandOffset + 2);
                 break;
diff --git a/src/tint/lang/spirv/writer/raise/expand_implicit_splats_test.cc b/src/tint/lang/spirv/writer/raise/expand_implicit_splats_test.cc
index 9111271..79b83aa 100644
--- a/src/tint/lang/spirv/writer/raise/expand_implicit_splats_test.cc
+++ b/src/tint/lang/spirv/writer/raise/expand_implicit_splats_test.cc
@@ -639,7 +639,7 @@
     func->SetParams({arg1, arg2, factor});
 
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.vec4<f32>(), core::Function::kMix, arg1, arg2, factor);
+        auto* result = b.Call(ty.vec4<f32>(), core::BuiltinFn::kMix, arg1, arg2, factor);
         b.Return(func, result);
     });
 
diff --git a/src/tint/lang/spirv/writer/raise/handle_matrix_arithmetic.cc b/src/tint/lang/spirv/writer/raise/handle_matrix_arithmetic.cc
index 0a5dd30..30322ec 100644
--- a/src/tint/lang/spirv/writer/raise/handle_matrix_arithmetic.cc
+++ b/src/tint/lang/spirv/writer/raise/handle_matrix_arithmetic.cc
@@ -99,21 +99,21 @@
                 if (lhs_ty->Is<core::type::Matrix>()) {
                     if (rhs_ty->Is<core::type::Scalar>()) {
                         replace(b.Call<spirv::ir::BuiltinCall>(
-                            ty, spirv::ir::Function::kMatrixTimesScalar, lhs, rhs));
+                            ty, spirv::BuiltinFn::kMatrixTimesScalar, lhs, rhs));
                     } else if (rhs_ty->Is<core::type::Vector>()) {
                         replace(b.Call<spirv::ir::BuiltinCall>(
-                            ty, spirv::ir::Function::kMatrixTimesVector, lhs, rhs));
+                            ty, spirv::BuiltinFn::kMatrixTimesVector, lhs, rhs));
                     } else if (rhs_ty->Is<core::type::Matrix>()) {
                         replace(b.Call<spirv::ir::BuiltinCall>(
-                            ty, spirv::ir::Function::kMatrixTimesMatrix, lhs, rhs));
+                            ty, spirv::BuiltinFn::kMatrixTimesMatrix, lhs, rhs));
                     }
                 } else {
                     if (lhs_ty->Is<core::type::Scalar>()) {
                         replace(b.Call<spirv::ir::BuiltinCall>(
-                            ty, spirv::ir::Function::kMatrixTimesScalar, rhs, lhs));
+                            ty, spirv::BuiltinFn::kMatrixTimesScalar, rhs, lhs));
                     } else if (lhs_ty->Is<core::type::Vector>()) {
                         replace(b.Call<spirv::ir::BuiltinCall>(
-                            ty, spirv::ir::Function::kVectorTimesMatrix, lhs, rhs));
+                            ty, spirv::BuiltinFn::kVectorTimesMatrix, lhs, rhs));
                     }
                 }
                 break;
diff --git a/src/tint/lang/spirv/writer/raise/shader_io.cc b/src/tint/lang/spirv/writer/raise/shader_io.cc
index 0c7a3b2..d4c60ed 100644
--- a/src/tint/lang/spirv/writer/raise/shader_io.cc
+++ b/src/tint/lang/spirv/writer/raise/shader_io.cc
@@ -186,7 +186,7 @@
         auto* frag_depth_min = builder.Access(ty.f32(), args, 0_u);
         auto* frag_depth_max = builder.Access(ty.f32(), args, 1_u);
         return builder
-            .Call(ty.f32(), core::Function::kClamp, frag_depth, frag_depth_min, frag_depth_max)
+            .Call(ty.f32(), core::BuiltinFn::kClamp, frag_depth, frag_depth_min, frag_depth_max)
             ->Result();
     }
 
diff --git a/src/tint/lang/spirv/writer/texture_builtin_test.cc b/src/tint/lang/spirv/writer/texture_builtin_test.cc
index c77274b..db4f44f 100644
--- a/src/tint/lang/spirv/writer/texture_builtin_test.cc
+++ b/src/tint/lang/spirv/writer/texture_builtin_test.cc
@@ -12,8 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "src/tint/lang/core/builtin_fn.h"
 #include "src/tint/lang/core/fluent_types.h"
-#include "src/tint/lang/core/function.h"
 #include "src/tint/lang/core/type/depth_multisampled_texture.h"
 #include "src/tint/lang/spirv/writer/common/helper_test.h"
 
@@ -130,11 +130,11 @@
         return nullptr;
     }
 
-    void Run(enum core::Function function, SamplerUsage sampler) {
+    void Run(enum core::BuiltinFn function, SamplerUsage sampler) {
         auto params = GetParam();
 
         auto* result_ty = MakeScalarType(params.result.type);
-        if (function == core::Function::kTextureStore) {
+        if (function == core::BuiltinFn::kTextureStore) {
             result_ty = ty.void_();
         }
         if (params.result.width > 1) {
@@ -162,7 +162,7 @@
             uint32_t arg_value = 1;
 
             Vector<core::ir::Value*, 4> args;
-            if (function == core::Function::kTextureGather &&
+            if (function == core::BuiltinFn::kTextureGather &&
                 params.texture_type != kDepthTexture) {
                 // Special case for textureGather, which has a component argument first.
                 auto* component = MakeScalarValue(kU32, arg_value++);
@@ -205,7 +205,7 @@
 ////////////////////////////////////////////////////////////////
 using TextureSample = TextureBuiltinTest;
 TEST_P(TextureSample, Emit) {
-    Run(core::Function::kTextureSample, kSampler);
+    Run(core::BuiltinFn::kTextureSample, kSampler);
 }
 INSTANTIATE_TEST_SUITE_P(
     SpirvWriterTest,
@@ -400,7 +400,7 @@
 ////////////////////////////////////////////////////////////////
 using TextureSampleBias = TextureBuiltinTest;
 TEST_P(TextureSampleBias, Emit) {
-    Run(core::Function::kTextureSampleBias, kSampler);
+    Run(core::BuiltinFn::kTextureSampleBias, kSampler);
 }
 INSTANTIATE_TEST_SUITE_P(
     SpirvWriterTest,
@@ -507,7 +507,7 @@
 ////////////////////////////////////////////////////////////////
 using TextureSampleGrad = TextureBuiltinTest;
 TEST_P(TextureSampleGrad, Emit) {
-    Run(core::Function::kTextureSampleGrad, kSampler);
+    Run(core::BuiltinFn::kTextureSampleGrad, kSampler);
 }
 INSTANTIATE_TEST_SUITE_P(
     SpirvWriterTest,
@@ -618,7 +618,7 @@
 ////////////////////////////////////////////////////////////////
 using TextureSampleLevel = TextureBuiltinTest;
 TEST_P(TextureSampleLevel, Emit) {
-    Run(core::Function::kTextureSampleLevel, kSampler);
+    Run(core::BuiltinFn::kTextureSampleLevel, kSampler);
 }
 INSTANTIATE_TEST_SUITE_P(
     SpirvWriterTest,
@@ -808,7 +808,7 @@
 ////////////////////////////////////////////////////////////////
 using TextureSampleCompare = TextureBuiltinTest;
 TEST_P(TextureSampleCompare, Emit) {
-    Run(core::Function::kTextureSampleCompare, kComparisonSampler);
+    Run(core::BuiltinFn::kTextureSampleCompare, kComparisonSampler);
 }
 INSTANTIATE_TEST_SUITE_P(
     SpirvWriterTest,
@@ -893,7 +893,7 @@
 ////////////////////////////////////////////////////////////////
 using TextureSampleCompareLevel = TextureBuiltinTest;
 TEST_P(TextureSampleCompareLevel, Emit) {
-    Run(core::Function::kTextureSampleCompareLevel, kComparisonSampler);
+    Run(core::BuiltinFn::kTextureSampleCompareLevel, kComparisonSampler);
 }
 INSTANTIATE_TEST_SUITE_P(
     SpirvWriterTest,
@@ -983,7 +983,7 @@
 ////////////////////////////////////////////////////////////////
 using TextureGather = TextureBuiltinTest;
 TEST_P(TextureGather, Emit) {
-    Run(core::Function::kTextureGather, kSampler);
+    Run(core::BuiltinFn::kTextureGather, kSampler);
 }
 INSTANTIATE_TEST_SUITE_P(
     SpirvWriterTest,
@@ -1164,7 +1164,7 @@
 ////////////////////////////////////////////////////////////////
 using TextureGatherCompare = TextureBuiltinTest;
 TEST_P(TextureGatherCompare, Emit) {
-    Run(core::Function::kTextureGatherCompare, kComparisonSampler);
+    Run(core::BuiltinFn::kTextureGatherCompare, kComparisonSampler);
 }
 INSTANTIATE_TEST_SUITE_P(
     SpirvWriterTest,
@@ -1249,7 +1249,7 @@
 ////////////////////////////////////////////////////////////////
 using TextureLoad = TextureBuiltinTest;
 TEST_P(TextureLoad, Emit) {
-    Run(core::Function::kTextureLoad, kNoSampler);
+    Run(core::BuiltinFn::kTextureLoad, kNoSampler);
 }
 INSTANTIATE_TEST_SUITE_P(SpirvWriterTest,
                          TextureLoad,
@@ -1368,7 +1368,7 @@
 ////////////////////////////////////////////////////////////////
 using TextureStore = TextureBuiltinTest;
 TEST_P(TextureStore, Emit) {
-    Run(core::Function::kTextureStore, kNoSampler);
+    Run(core::BuiltinFn::kTextureStore, kNoSampler);
 }
 INSTANTIATE_TEST_SUITE_P(SpirvWriterTest,
                          TextureStore,
@@ -1443,7 +1443,7 @@
 ////////////////////////////////////////////////////////////////
 using TextureDimensions = TextureBuiltinTest;
 TEST_P(TextureDimensions, Emit) {
-    Run(core::Function::kTextureDimensions, kNoSampler);
+    Run(core::BuiltinFn::kTextureDimensions, kNoSampler);
 }
 INSTANTIATE_TEST_SUITE_P(SpirvWriterTest,
                          TextureDimensions,
@@ -1693,7 +1693,7 @@
 ////////////////////////////////////////////////////////////////
 using TextureNumLayers = TextureBuiltinTest;
 TEST_P(TextureNumLayers, Emit) {
-    Run(core::Function::kTextureNumLayers, kNoSampler);
+    Run(core::BuiltinFn::kTextureNumLayers, kNoSampler);
 }
 INSTANTIATE_TEST_SUITE_P(SpirvWriterTest,
                          TextureNumLayers,
@@ -1760,7 +1760,7 @@
 ////////////////////////////////////////////////////////////////
 using TextureNumLevels = TextureBuiltinTest;
 TEST_P(TextureNumLevels, Emit) {
-    Run(core::Function::kTextureNumLevels, kNoSampler);
+    Run(core::BuiltinFn::kTextureNumLevels, kNoSampler);
 }
 INSTANTIATE_TEST_SUITE_P(SpirvWriterTest,
                          TextureNumLevels,
@@ -1852,7 +1852,7 @@
 ////////////////////////////////////////////////////////////////
 using TextureNumSamples = TextureBuiltinTest;
 TEST_P(TextureNumSamples, Emit) {
-    Run(core::Function::kTextureNumSamples, kNoSampler);
+    Run(core::BuiltinFn::kTextureNumSamples, kNoSampler);
 }
 INSTANTIATE_TEST_SUITE_P(SpirvWriterTest,
                          TextureNumSamples,
@@ -1891,7 +1891,7 @@
     auto* func = b.Function("foo", ty.vec4<f32>());
     func->SetParams(args);
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.vec4<f32>(), core::Function::kTextureSampleBaseClampToEdge, args);
+        auto* result = b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureSampleBaseClampToEdge, args);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -1926,7 +1926,7 @@
     auto* func = b.Function("foo", ty.void_());
     func->SetParams({texture, coords, value});
     b.Append(func->Block(), [&] {
-        b.Call(ty.void_(), core::Function::kTextureStore, texture, coords, value);
+        b.Call(ty.void_(), core::BuiltinFn::kTextureStore, texture, coords, value);
         b.Return(func);
     });
 
@@ -1952,7 +1952,7 @@
     auto* func = b.Function("foo", ty.vec2<u32>());
     func->SetParams({texture, level});
     b.Append(func->Block(), [&] {
-        auto* dims = b.Call(ty.vec2<u32>(), core::Function::kTextureDimensions, texture, level);
+        auto* dims = b.Call(ty.vec2<u32>(), core::BuiltinFn::kTextureDimensions, texture, level);
         b.Return(func, dims);
         mod.SetName(dims, "dims");
     });
@@ -1977,7 +1977,8 @@
     auto* func = b.Function("foo", ty.vec4<f32>());
     func->SetParams({texture, coords, level});
     b.Append(func->Block(), [&] {
-        auto* result = b.Call(ty.vec4<f32>(), core::Function::kTextureLoad, texture, coords, level);
+        auto* result =
+            b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureLoad, texture, coords, level);
         b.Return(func, result);
         mod.SetName(result, "result");
     });
@@ -2008,7 +2009,7 @@
     auto* func = b.Function("foo", ty.void_());
     func->SetParams({texture, coords, layer, value});
     b.Append(func->Block(), [&] {
-        b.Call(ty.void_(), core::Function::kTextureStore, texture, coords, layer, value);
+        b.Call(ty.void_(), core::BuiltinFn::kTextureStore, texture, coords, layer, value);
         b.Return(func);
     });
 
diff --git a/src/tint/lang/spirv/writer/writer.cc b/src/tint/lang/spirv/writer/writer.cc
index 81acf02..c8181bc 100644
--- a/src/tint/lang/spirv/writer/writer.cc
+++ b/src/tint/lang/spirv/writer/writer.cc
@@ -32,8 +32,8 @@
 Output::~Output() = default;
 Output::Output(const Output&) = default;
 
-Result<Output, std::string> Generate(const Program* program, const Options& options) {
-    if (!program->IsValid()) {
+Result<Output, std::string> Generate(const Program& program, const Options& options) {
+    if (!program.IsValid()) {
         return std::string("input program is not valid");
     }
 
@@ -79,7 +79,7 @@
 
         // Generate the SPIR-V code.
         auto impl = std::make_unique<ASTPrinter>(
-            &sanitized_result.program, zero_initialize_workgroup_memory,
+            sanitized_result.program, zero_initialize_workgroup_memory,
             options.experimental_require_subgroup_uniform_control_flow);
         if (!impl->Generate()) {
             return impl->Diagnostics().str();
diff --git a/src/tint/lang/spirv/writer/writer.h b/src/tint/lang/spirv/writer/writer.h
index 98edb39..7845cb9 100644
--- a/src/tint/lang/spirv/writer/writer.h
+++ b/src/tint/lang/spirv/writer/writer.h
@@ -33,7 +33,7 @@
 /// @param program the program to translate to SPIR-V
 /// @param options the configuration options to use when generating SPIR-V
 /// @returns the resulting SPIR-V and supplementary information, or an error string
-Result<Output, std::string> Generate(const Program* program, const Options& options);
+Result<Output, std::string> Generate(const Program& program, const Options& options);
 
 }  // namespace tint::spirv::writer
 
diff --git a/src/tint/lang/spirv/writer/writer_bench.cc b/src/tint/lang/spirv/writer/writer_bench.cc
index 3d74eb8..b514576 100644
--- a/src/tint/lang/spirv/writer/writer_bench.cc
+++ b/src/tint/lang/spirv/writer/writer_bench.cc
@@ -28,7 +28,7 @@
     }
     auto& program = std::get<bench::ProgramAndFile>(res).program;
     for (auto _ : state) {
-        auto res = Generate(&program, options);
+        auto res = Generate(program, options);
         if (!res) {
             state.SkipWithError(res.Failure());
         }
diff --git a/src/tint/lang/wgsl/ast/call_expression.h b/src/tint/lang/wgsl/ast/call_expression.h
index 07a3e5a..a26eeac 100644
--- a/src/tint/lang/wgsl/ast/call_expression.h
+++ b/src/tint/lang/wgsl/ast/call_expression.h
@@ -27,7 +27,7 @@
 
 /// A call expression - represents either a:
 /// * sem::Function
-/// * sem::Builtin
+/// * sem::BuiltinFn
 /// * sem::ValueConstructor
 /// * sem::ValueConversion
 class CallExpression final : public Castable<CallExpression, Expression> {
diff --git a/src/tint/lang/wgsl/ast/module_clone_test.cc b/src/tint/lang/wgsl/ast/module_clone_test.cc
index 9e3c7f9..a683b0a 100644
--- a/src/tint/lang/wgsl/ast/module_clone_test.cc
+++ b/src/tint/lang/wgsl/ast/module_clone_test.cc
@@ -124,15 +124,15 @@
     // Parse the wgsl, create the src program
     auto src = wgsl::reader::Parse(&file);
 
-    ASSERT_TRUE(src.IsValid()) << src.Diagnostics().str();
+    ASSERT_TRUE(src.IsValid()) << src.Diagnostics();
 
     // Clone the src program to dst
     Program dst(src.Clone());
 
-    ASSERT_TRUE(dst.IsValid()) << dst.Diagnostics().str();
+    ASSERT_TRUE(dst.IsValid()) << dst.Diagnostics();
 
     // Expect the printed strings to match
-    EXPECT_EQ(Program::printer(&src), Program::printer(&dst));
+    EXPECT_EQ(Program::printer(src), Program::printer(dst));
 
     // Check that none of the AST nodes or type pointers in dst are found in src
     std::unordered_set<const Node*> src_nodes;
@@ -156,7 +156,7 @@
     wgsl::writer::Options options;
     std::string src_wgsl;
     {
-        auto result = wgsl::writer::Generate(&src, options);
+        auto result = wgsl::writer::Generate(src, options);
         ASSERT_TRUE(result) << result.Failure();
         src_wgsl = result->wgsl;
 
@@ -169,7 +169,7 @@
     }
 
     // Print the dst module, check it matches the original source
-    auto result = wgsl::writer::Generate(&dst, options);
+    auto result = wgsl::writer::Generate(dst, options);
     ASSERT_TRUE(result);
     auto dst_wgsl = result->wgsl;
     ASSERT_EQ(src_wgsl, dst_wgsl);
diff --git a/src/tint/lang/wgsl/ast/transform/add_block_attribute.cc b/src/tint/lang/wgsl/ast/transform/add_block_attribute.cc
index e9ad3ef..f1572a7 100644
--- a/src/tint/lang/wgsl/ast/transform/add_block_attribute.cc
+++ b/src/tint/lang/wgsl/ast/transform/add_block_attribute.cc
@@ -33,13 +33,13 @@
 
 AddBlockAttribute::~AddBlockAttribute() = default;
 
-Transform::ApplyResult AddBlockAttribute::Apply(const Program* src,
+Transform::ApplyResult AddBlockAttribute::Apply(const Program& src,
                                                 const DataMap&,
                                                 DataMap&) const {
     ProgramBuilder b;
-    program::CloneContext ctx{&b, src, /* auto_clone_symbols */ true};
+    program::CloneContext ctx{&b, &src, /* auto_clone_symbols */ true};
 
-    auto& sem = src->Sem();
+    auto& sem = src.Sem();
 
     // A map from a type in the source program to a block-decorated wrapper that contains it in the
     // destination program.
@@ -47,7 +47,7 @@
 
     // Process global 'var' declarations that are buffers.
     bool made_changes = false;
-    for (auto* global : src->AST().GlobalVariables()) {
+    for (auto* global : src.AST().GlobalVariables()) {
         auto* var = sem.Get(global);
         if (!core::IsHostShareable(var->AddressSpace())) {
             // Not declared in a host-sharable address space
@@ -77,7 +77,7 @@
                     b.create<Struct>(b.Ident(b.Symbols().New(wrapper_name)),
                                      tint::Vector{b.Member(kMemberName, CreateASTTypeFor(ctx, ty))},
                                      tint::Vector{block});
-                ctx.InsertBefore(src->AST().GlobalDeclarations(), global, ret);
+                ctx.InsertBefore(src.AST().GlobalDeclarations(), global, ret);
                 return ret;
             });
             ctx.Replace(global->type.expr, b.Expr(wrapper->name->symbol));
diff --git a/src/tint/lang/wgsl/ast/transform/add_block_attribute.h b/src/tint/lang/wgsl/ast/transform/add_block_attribute.h
index cd17977..3e31d08 100644
--- a/src/tint/lang/wgsl/ast/transform/add_block_attribute.h
+++ b/src/tint/lang/wgsl/ast/transform/add_block_attribute.h
@@ -54,7 +54,7 @@
     ~AddBlockAttribute() override;
 
     /// @copydoc Transform::Apply
-    ApplyResult Apply(const Program* program,
+    ApplyResult Apply(const Program& program,
                       const DataMap& inputs,
                       DataMap& outputs) const override;
 };
diff --git a/src/tint/lang/wgsl/ast/transform/add_empty_entry_point.cc b/src/tint/lang/wgsl/ast/transform/add_empty_entry_point.cc
index 015f4ae..86fb0ce 100644
--- a/src/tint/lang/wgsl/ast/transform/add_empty_entry_point.cc
+++ b/src/tint/lang/wgsl/ast/transform/add_empty_entry_point.cc
@@ -27,8 +27,8 @@
 namespace tint::ast::transform {
 namespace {
 
-bool ShouldRun(const Program* program) {
-    for (auto* func : program->AST().Functions()) {
+bool ShouldRun(const Program& program) {
+    for (auto* func : program.AST().Functions()) {
         if (func->IsEntryPoint()) {
             return false;
         }
@@ -42,7 +42,7 @@
 
 AddEmptyEntryPoint::~AddEmptyEntryPoint() = default;
 
-Transform::ApplyResult AddEmptyEntryPoint::Apply(const Program* src,
+Transform::ApplyResult AddEmptyEntryPoint::Apply(const Program& src,
                                                  const DataMap&,
                                                  DataMap&) const {
     if (!ShouldRun(src)) {
@@ -50,7 +50,7 @@
     }
 
     ProgramBuilder b;
-    program::CloneContext ctx{&b, src, /* auto_clone_symbols */ true};
+    program::CloneContext ctx{&b, &src, /* auto_clone_symbols */ true};
 
     b.Func(b.Symbols().New("unused_entry_point"), {}, b.ty.void_(), {},
            tint::Vector{
diff --git a/src/tint/lang/wgsl/ast/transform/add_empty_entry_point.h b/src/tint/lang/wgsl/ast/transform/add_empty_entry_point.h
index f282d82..91767a9 100644
--- a/src/tint/lang/wgsl/ast/transform/add_empty_entry_point.h
+++ b/src/tint/lang/wgsl/ast/transform/add_empty_entry_point.h
@@ -28,7 +28,7 @@
     ~AddEmptyEntryPoint() override;
 
     /// @copydoc Transform::Apply
-    ApplyResult Apply(const Program* program,
+    ApplyResult Apply(const Program& program,
                       const DataMap& inputs,
                       DataMap& outputs) const override;
 };
diff --git a/src/tint/lang/wgsl/ast/transform/array_length_from_uniform.cc b/src/tint/lang/wgsl/ast/transform/array_length_from_uniform.cc
index d9e1c60..c065eea 100644
--- a/src/tint/lang/wgsl/ast/transform/array_length_from_uniform.cc
+++ b/src/tint/lang/wgsl/ast/transform/array_length_from_uniform.cc
@@ -37,11 +37,11 @@
 namespace tint::ast::transform {
 namespace {
 
-bool ShouldRun(const Program* program) {
-    for (auto* fn : program->AST().Functions()) {
-        if (auto* sem_fn = program->Sem().Get(fn)) {
+bool ShouldRun(const Program& program) {
+    for (auto* fn : program.AST().Functions()) {
+        if (auto* sem_fn = program.Sem().Get(fn)) {
             for (auto* builtin : sem_fn->DirectlyCalledBuiltins()) {
-                if (builtin->Type() == core::Function::kArrayLength) {
+                if (builtin->Fn() == core::BuiltinFn::kArrayLength) {
                     return true;
                 }
             }
@@ -61,7 +61,7 @@
     /// @param program the source program
     /// @param in the input transform data
     /// @param out the output transform data
-    explicit State(const Program* program, const DataMap& in, DataMap& out)
+    explicit State(const Program& program, const DataMap& in, DataMap& out)
         : src(program), inputs(in), outputs(out) {}
 
     /// Runs the transform
@@ -76,7 +76,7 @@
             return resolver::Resolve(b);
         }
 
-        if (!ShouldRun(ctx.src)) {
+        if (!ShouldRun(src)) {
             return SkipTransform;
         }
 
@@ -177,7 +177,7 @@
 
   private:
     /// The source program
-    const Program* const src;
+    const Program& src;
     /// The transform inputs
     const DataMap& inputs;
     /// The transform outputs
@@ -185,7 +185,7 @@
     /// The target program builder
     ProgramBuilder b;
     /// The clone context
-    program::CloneContext ctx = {&b, src, /* auto_clone_symbols */ true};
+    program::CloneContext ctx = {&b, &src, /* auto_clone_symbols */ true};
 
     /// Iterate over all arrayLength() builtins that operate on
     /// storage buffer variables.
@@ -196,18 +196,18 @@
     /// sem::GlobalVariable for the storage buffer.
     template <typename F>
     void IterateArrayLengthOnStorageVar(F&& functor) {
-        auto& sem = src->Sem();
+        auto& sem = src.Sem();
 
         // Find all calls to the arrayLength() builtin.
-        for (auto* node : src->ASTNodes().Objects()) {
+        for (auto* node : src.ASTNodes().Objects()) {
             auto* call_expr = node->As<CallExpression>();
             if (!call_expr) {
                 continue;
             }
 
             auto* call = sem.Get(call_expr)->UnwrapMaterialize()->As<sem::Call>();
-            auto* builtin = call->Target()->As<sem::Builtin>();
-            if (!builtin || builtin->Type() != core::Function::kArrayLength) {
+            auto* builtin = call->Target()->As<sem::BuiltinFn>();
+            if (!builtin || builtin->Fn() != core::BuiltinFn::kArrayLength) {
                 continue;
             }
 
@@ -253,7 +253,7 @@
     }
 };
 
-Transform::ApplyResult ArrayLengthFromUniform::Apply(const Program* src,
+Transform::ApplyResult ArrayLengthFromUniform::Apply(const Program& src,
                                                      const DataMap& inputs,
                                                      DataMap& outputs) const {
     return State{src, inputs, outputs}.Run();
diff --git a/src/tint/lang/wgsl/ast/transform/array_length_from_uniform.h b/src/tint/lang/wgsl/ast/transform/array_length_from_uniform.h
index 7f0acdb..67e9f56 100644
--- a/src/tint/lang/wgsl/ast/transform/array_length_from_uniform.h
+++ b/src/tint/lang/wgsl/ast/transform/array_length_from_uniform.h
@@ -96,7 +96,7 @@
     };
 
     /// @copydoc Transform::Apply
-    ApplyResult Apply(const Program* program,
+    ApplyResult Apply(const Program& program,
                       const DataMap& inputs,
                       DataMap& outputs) const override;
 
diff --git a/src/tint/lang/wgsl/ast/transform/binding_remapper.cc b/src/tint/lang/wgsl/ast/transform/binding_remapper.cc
index 83a35bd..b77cc11 100644
--- a/src/tint/lang/wgsl/ast/transform/binding_remapper.cc
+++ b/src/tint/lang/wgsl/ast/transform/binding_remapper.cc
@@ -45,11 +45,11 @@
 BindingRemapper::BindingRemapper() = default;
 BindingRemapper::~BindingRemapper() = default;
 
-Transform::ApplyResult BindingRemapper::Apply(const Program* src,
+Transform::ApplyResult BindingRemapper::Apply(const Program& src,
                                               const DataMap& inputs,
                                               DataMap&) const {
     ProgramBuilder b;
-    program::CloneContext ctx{&b, src, /* auto_clone_symbols */ true};
+    program::CloneContext ctx{&b, &src, /* auto_clone_symbols */ true};
 
     auto* remappings = inputs.Get<Remappings>();
     if (!remappings) {
@@ -69,11 +69,11 @@
     if (remappings->allow_collisions) {
         // Scan for binding point collisions generated by this transform.
         // Populate all collisions in the `add_collision_attr` set.
-        for (auto* func_ast : src->AST().Functions()) {
+        for (auto* func_ast : src.AST().Functions()) {
             if (!func_ast->IsEntryPoint()) {
                 continue;
             }
-            auto* func = src->Sem().Get(func_ast);
+            auto* func = src.Sem().Get(func_ast);
             std::unordered_map<BindingPoint, int> binding_point_counts;
             for (auto* global : func->TransitivelyReferencedGlobals()) {
                 if (auto from = global->BindingPoint()) {
@@ -95,9 +95,9 @@
         }
     }
 
-    for (auto* var : src->AST().Globals<Var>()) {
+    for (auto* var : src.AST().Globals<Var>()) {
         if (var->HasBindingPoint()) {
-            auto* global_sem = src->Sem().Get<sem::GlobalVariable>(var);
+            auto* global_sem = src.Sem().Get<sem::GlobalVariable>(var);
 
             // The original binding point
             BindingPoint from = *global_sem->BindingPoint();
@@ -133,7 +133,7 @@
                                                   ")");
                     return resolver::Resolve(b);
                 }
-                auto* sem = src->Sem().Get(var);
+                auto* sem = src.Sem().Get(var);
                 if (sem->AddressSpace() != core::AddressSpace::kStorage) {
                     b.Diagnostics().add_error(
                         diag::System::Transform,
diff --git a/src/tint/lang/wgsl/ast/transform/binding_remapper.h b/src/tint/lang/wgsl/ast/transform/binding_remapper.h
index 62183b9..71a038b 100644
--- a/src/tint/lang/wgsl/ast/transform/binding_remapper.h
+++ b/src/tint/lang/wgsl/ast/transform/binding_remapper.h
@@ -68,7 +68,7 @@
     ~BindingRemapper() override;
 
     /// @copydoc Transform::Apply
-    ApplyResult Apply(const Program* program,
+    ApplyResult Apply(const Program& program,
                       const DataMap& inputs,
                       DataMap& outputs) const override;
 };
diff --git a/src/tint/lang/wgsl/ast/transform/builtin_polyfill.cc b/src/tint/lang/wgsl/ast/transform/builtin_polyfill.cc
index dc4ba14..2c92aa3 100644
--- a/src/tint/lang/wgsl/ast/transform/builtin_polyfill.cc
+++ b/src/tint/lang/wgsl/ast/transform/builtin_polyfill.cc
@@ -24,7 +24,7 @@
 #include "src/tint/lang/wgsl/program/clone_context.h"
 #include "src/tint/lang/wgsl/program/program_builder.h"
 #include "src/tint/lang/wgsl/resolver/resolve.h"
-#include "src/tint/lang/wgsl/sem/builtin.h"
+#include "src/tint/lang/wgsl/sem/builtin_fn.h"
 #include "src/tint/lang/wgsl/sem/call.h"
 #include "src/tint/lang/wgsl/sem/type_expression.h"
 #include "src/tint/lang/wgsl/sem/value_conversion.h"
@@ -48,9 +48,9 @@
     /// Constructor
     /// @param program the source program
     /// @param config the transform config
-    State(const Program* program, const Config& config) : src(program), cfg(config) {
+    State(const Program& program, const Config& config) : src(program), cfg(config) {
         has_full_ptr_params = false;
-        for (auto* enable : src->AST().Enables()) {
+        for (auto* enable : src.AST().Enables()) {
             if (enable->HasExtension(wgsl::Extension::kChromiumExperimentalFullPtrParameters)) {
                 has_full_ptr_params = true;
                 break;
@@ -61,12 +61,12 @@
     /// Runs the transform
     /// @returns the new program or SkipTransform if the transform is not required
     Transform::ApplyResult Run() {
-        for (auto* node : src->ASTNodes().Objects()) {
+        for (auto* node : src.ASTNodes().Objects()) {
             Switch(
                 node,  //
                 [&](const CallExpression* expr) { Call(expr); },
                 [&](const BinaryExpression* bin_op) {
-                    if (auto* s = src->Sem().Get(bin_op);
+                    if (auto* s = src.Sem().Get(bin_op);
                         !s || s->Stage() == core::EvaluationStage::kConstant ||
                         s->Stage() == core::EvaluationStage::kNotEvaluated) {
                         return;  // Don't polyfill @const expressions
@@ -83,7 +83,7 @@
                         }
                         case core::BinaryOp::kDivide: {
                             if (cfg.builtins.int_div_mod) {
-                                auto* lhs_ty = src->TypeOf(bin_op->lhs)->UnwrapRef();
+                                auto* lhs_ty = src.TypeOf(bin_op->lhs)->UnwrapRef();
                                 if (lhs_ty->is_integer_scalar_or_vector()) {
                                     ctx.Replace(bin_op,
                                                 [this, bin_op] { return IntDivMod(bin_op); });
@@ -94,7 +94,7 @@
                         }
                         case core::BinaryOp::kModulo: {
                             if (cfg.builtins.int_div_mod) {
-                                auto* lhs_ty = src->TypeOf(bin_op->lhs)->UnwrapRef();
+                                auto* lhs_ty = src.TypeOf(bin_op->lhs)->UnwrapRef();
                                 if (lhs_ty->is_integer_scalar_or_vector()) {
                                     ctx.Replace(bin_op,
                                                 [this, bin_op] { return IntDivMod(bin_op); });
@@ -102,7 +102,7 @@
                                 }
                             }
                             if (cfg.builtins.precise_float_mod) {
-                                auto* lhs_ty = src->TypeOf(bin_op->lhs)->UnwrapRef();
+                                auto* lhs_ty = src.TypeOf(bin_op->lhs)->UnwrapRef();
                                 if (lhs_ty->is_float_scalar_or_vector()) {
                                     ctx.Replace(bin_op,
                                                 [this, bin_op] { return PreciseFloatMod(bin_op); });
@@ -117,7 +117,7 @@
                 },
                 [&](const Expression* expr) {
                     if (cfg.builtins.bgra8unorm) {
-                        if (auto* ty_expr = src->Sem().Get<sem::TypeExpression>(expr)) {
+                        if (auto* ty_expr = src.Sem().Get<sem::TypeExpression>(expr)) {
                             if (auto* tex = ty_expr->Type()->As<core::type::StorageTexture>()) {
                                 if (tex->texel_format() == core::TexelFormat::kBgra8Unorm) {
                                     ctx.Replace(expr, [this, tex] {
@@ -143,19 +143,19 @@
 
   private:
     /// The source program
-    Program const* const src;
+    const Program& src;
     /// The transform config
     const Config& cfg;
     /// The destination program builder
     ProgramBuilder b;
     /// The clone context
-    program::CloneContext ctx{&b, src};
+    program::CloneContext ctx{&b, &src};
     /// The source clone context
-    const sem::Info& sem = src->Sem();
+    const sem::Info& sem = src.Sem();
     /// Polyfill functions for binary operators.
     Hashmap<BinaryOpSignature, Symbol, 8> binary_op_polyfills;
     /// Polyfill builtins.
-    Hashmap<const sem::Builtin*, Symbol, 8> builtin_polyfills;
+    Hashmap<const sem::BuiltinFn*, Symbol, 8> builtin_polyfills;
     /// Polyfill f32 conversion to i32 or u32 (or vectors of)
     Hashmap<const core::type::Type*, Symbol, 2> f32_conv_polyfills;
     // Tracks whether the chromium_experimental_full_ptr_parameters extension has been enabled.
@@ -858,13 +858,13 @@
         const uint32_t width = WidthOf(target);
 
         // select(target(v), low_limit, v < low_condition)
-        auto* select_low = b.Call(core::Function::kSelect,                  //
+        auto* select_low = b.Call(core::BuiltinFn::kSelect,                 //
                                   b.Call(T(target), "v"),                   //
                                   ScalarOrVector(width, limits.low_limit),  //
                                   b.LessThan("v", ScalarOrVector(width, limits.low_condition)));
 
         // select(high_limit, select_low, v < high_condition)
-        auto* select_high = b.Call(core::Function::kSelect,                   //
+        auto* select_high = b.Call(core::BuiltinFn::kSelect,                  //
                                    ScalarOrVector(width, limits.high_limit),  //
                                    select_low,                                //
                                    b.LessThan("v", ScalarOrVector(width, limits.high_condition)));
@@ -884,8 +884,8 @@
     /// @param bin_op the original BinaryExpression
     /// @return the polyfill value for bitshift operation
     const Expression* BitshiftModulo(const BinaryExpression* bin_op) {
-        auto* lhs_ty = src->TypeOf(bin_op->lhs)->UnwrapRef();
-        auto* rhs_ty = src->TypeOf(bin_op->rhs)->UnwrapRef();
+        auto* lhs_ty = src.TypeOf(bin_op->lhs)->UnwrapRef();
+        auto* rhs_ty = src.TypeOf(bin_op->rhs)->UnwrapRef();
         auto* lhs_el_ty = lhs_ty->DeepestElement();
         const Expression* mask = b.Expr(AInt(lhs_el_ty->Size() * 8 - 1));
         if (rhs_ty->Is<core::type::Vector>()) {
@@ -901,8 +901,8 @@
     /// @param bin_op the original BinaryExpression
     /// @return the polyfill divide or modulo
     const Expression* IntDivMod(const BinaryExpression* bin_op) {
-        auto* lhs_ty = src->TypeOf(bin_op->lhs)->UnwrapRef();
-        auto* rhs_ty = src->TypeOf(bin_op->rhs)->UnwrapRef();
+        auto* lhs_ty = src.TypeOf(bin_op->lhs)->UnwrapRef();
+        auto* rhs_ty = src.TypeOf(bin_op->rhs)->UnwrapRef();
         BinaryOpSignature sig{bin_op->op, lhs_ty, rhs_ty};
         auto fn = binary_op_polyfills.GetOrCreate(sig, [&] {
             const bool is_div = bin_op->op == core::BinaryOp::kDivide;
@@ -994,8 +994,8 @@
     /// @param bin_op the original BinaryExpression
     /// @return the polyfill divide or modulo
     const Expression* PreciseFloatMod(const BinaryExpression* bin_op) {
-        auto* lhs_ty = src->TypeOf(bin_op->lhs)->UnwrapRef();
-        auto* rhs_ty = src->TypeOf(bin_op->rhs)->UnwrapRef();
+        auto* lhs_ty = src.TypeOf(bin_op->lhs)->UnwrapRef();
+        auto* rhs_ty = src.TypeOf(bin_op->rhs)->UnwrapRef();
         BinaryOpSignature sig{bin_op->op, lhs_ty, rhs_ty};
         auto fn = binary_op_polyfills.GetOrCreate(sig, [&] {
             const auto [lhs_el_ty, lhs_width] = lhs_ty->Elements(lhs_ty, 1);
@@ -1071,37 +1071,37 @@
 
     /// Examines the call expression @p expr, applying any necessary polyfill transforms
     void Call(const CallExpression* expr) {
-        auto* call = src->Sem().Get(expr)->UnwrapMaterialize()->As<sem::Call>();
+        auto* call = src.Sem().Get(expr)->UnwrapMaterialize()->As<sem::Call>();
         if (!call || call->Stage() == core::EvaluationStage::kConstant ||
             call->Stage() == core::EvaluationStage::kNotEvaluated) {
             return;  // Don't polyfill @const expressions
         }
         Symbol fn = Switch(
             call->Target(),  //
-            [&](const sem::Builtin* builtin) {
-                switch (builtin->Type()) {
-                    case core::Function::kAcosh:
+            [&](const sem::BuiltinFn* builtin) {
+                switch (builtin->Fn()) {
+                    case core::BuiltinFn::kAcosh:
                         if (cfg.builtins.acosh != Level::kNone) {
                             return builtin_polyfills.GetOrCreate(
                                 builtin, [&] { return acosh(builtin->ReturnType()); });
                         }
                         return Symbol{};
 
-                    case core::Function::kAsinh:
+                    case core::BuiltinFn::kAsinh:
                         if (cfg.builtins.asinh) {
                             return builtin_polyfills.GetOrCreate(
                                 builtin, [&] { return asinh(builtin->ReturnType()); });
                         }
                         return Symbol{};
 
-                    case core::Function::kAtanh:
+                    case core::BuiltinFn::kAtanh:
                         if (cfg.builtins.atanh != Level::kNone) {
                             return builtin_polyfills.GetOrCreate(
                                 builtin, [&] { return atanh(builtin->ReturnType()); });
                         }
                         return Symbol{};
 
-                    case core::Function::kClamp:
+                    case core::BuiltinFn::kClamp:
                         if (cfg.builtins.clamp_int) {
                             auto& sig = builtin->Signature();
                             if (sig.parameters[0]->Type()->is_integer_scalar_or_vector()) {
@@ -1111,49 +1111,49 @@
                         }
                         return Symbol{};
 
-                    case core::Function::kCountLeadingZeros:
+                    case core::BuiltinFn::kCountLeadingZeros:
                         if (cfg.builtins.count_leading_zeros) {
                             return builtin_polyfills.GetOrCreate(
                                 builtin, [&] { return countLeadingZeros(builtin->ReturnType()); });
                         }
                         return Symbol{};
 
-                    case core::Function::kCountTrailingZeros:
+                    case core::BuiltinFn::kCountTrailingZeros:
                         if (cfg.builtins.count_trailing_zeros) {
                             return builtin_polyfills.GetOrCreate(
                                 builtin, [&] { return countTrailingZeros(builtin->ReturnType()); });
                         }
                         return Symbol{};
 
-                    case core::Function::kExtractBits:
+                    case core::BuiltinFn::kExtractBits:
                         if (cfg.builtins.extract_bits != Level::kNone) {
                             return builtin_polyfills.GetOrCreate(
                                 builtin, [&] { return extractBits(builtin->ReturnType()); });
                         }
                         return Symbol{};
 
-                    case core::Function::kFirstLeadingBit:
+                    case core::BuiltinFn::kFirstLeadingBit:
                         if (cfg.builtins.first_leading_bit) {
                             return builtin_polyfills.GetOrCreate(
                                 builtin, [&] { return firstLeadingBit(builtin->ReturnType()); });
                         }
                         return Symbol{};
 
-                    case core::Function::kFirstTrailingBit:
+                    case core::BuiltinFn::kFirstTrailingBit:
                         if (cfg.builtins.first_trailing_bit) {
                             return builtin_polyfills.GetOrCreate(
                                 builtin, [&] { return firstTrailingBit(builtin->ReturnType()); });
                         }
                         return Symbol{};
 
-                    case core::Function::kInsertBits:
+                    case core::BuiltinFn::kInsertBits:
                         if (cfg.builtins.insert_bits != Level::kNone) {
                             return builtin_polyfills.GetOrCreate(
                                 builtin, [&] { return insertBits(builtin->ReturnType()); });
                         }
                         return Symbol{};
 
-                    case core::Function::kReflect:
+                    case core::BuiltinFn::kReflect:
                         // Only polyfill for vec2<f32>. See https://crbug.com/tint/1798 for
                         // more details.
                         if (cfg.builtins.reflect_vec2_f32) {
@@ -1166,14 +1166,14 @@
                         }
                         return Symbol{};
 
-                    case core::Function::kSaturate:
+                    case core::BuiltinFn::kSaturate:
                         if (cfg.builtins.saturate) {
                             return builtin_polyfills.GetOrCreate(
                                 builtin, [&] { return saturate(builtin->ReturnType()); });
                         }
                         return Symbol{};
 
-                    case core::Function::kSign:
+                    case core::BuiltinFn::kSign:
                         if (cfg.builtins.sign_int) {
                             auto* ty = builtin->ReturnType();
                             if (ty->is_signed_integer_scalar_or_vector()) {
@@ -1183,7 +1183,7 @@
                         }
                         return Symbol{};
 
-                    case core::Function::kTextureLoad:
+                    case core::BuiltinFn::kTextureLoad:
                         if (cfg.builtins.bgra8unorm) {
                             auto& sig = builtin->Signature();
                             auto* tex = sig.Parameter(core::ParameterUsage::kTexture);
@@ -1199,7 +1199,7 @@
                         }
                         return Symbol{};
 
-                    case core::Function::kTextureSampleBaseClampToEdge:
+                    case core::BuiltinFn::kTextureSampleBaseClampToEdge:
                         if (cfg.builtins.texture_sample_base_clamp_to_edge_2d_f32) {
                             auto& sig = builtin->Signature();
                             auto* tex = sig.Parameter(core::ParameterUsage::kTexture);
@@ -1213,7 +1213,7 @@
                         }
                         return Symbol{};
 
-                    case core::Function::kTextureStore:
+                    case core::BuiltinFn::kTextureStore:
                         if (cfg.builtins.bgra8unorm) {
                             auto& sig = builtin->Signature();
                             auto* tex = sig.Parameter(core::ParameterUsage::kTexture);
@@ -1231,7 +1231,7 @@
                                             args.Push(arg);
                                         }
                                         return ctx.dst->Call(
-                                            tint::ToString(core::Function::kTextureStore),
+                                            tint::ToString(core::BuiltinFn::kTextureStore),
                                             std::move(args));
                                     });
                                     made_changes = true;
@@ -1240,7 +1240,7 @@
                         }
                         return Symbol{};
 
-                    case core::Function::kQuantizeToF16:
+                    case core::BuiltinFn::kQuantizeToF16:
                         if (cfg.builtins.quantize_to_vec_f16) {
                             if (auto* vec = builtin->ReturnType()->As<core::type::Vector>()) {
                                 return builtin_polyfills.GetOrCreate(
@@ -1249,7 +1249,7 @@
                         }
                         return Symbol{};
 
-                    case core::Function::kWorkgroupUniformLoad:
+                    case core::BuiltinFn::kWorkgroupUniformLoad:
                         if (cfg.builtins.workgroup_uniform_load) {
                             return builtin_polyfills.GetOrCreate(builtin, [&] {
                                 return workgroupUniformLoad(builtin->ReturnType());
@@ -1289,7 +1289,7 @@
 
 BuiltinPolyfill::~BuiltinPolyfill() = default;
 
-Transform::ApplyResult BuiltinPolyfill::Apply(const Program* src,
+Transform::ApplyResult BuiltinPolyfill::Apply(const Program& src,
                                               const DataMap& data,
                                               DataMap&) const {
     auto* cfg = data.Get<Config>();
diff --git a/src/tint/lang/wgsl/ast/transform/builtin_polyfill.h b/src/tint/lang/wgsl/ast/transform/builtin_polyfill.h
index 8805996..cd1de52 100644
--- a/src/tint/lang/wgsl/ast/transform/builtin_polyfill.h
+++ b/src/tint/lang/wgsl/ast/transform/builtin_polyfill.h
@@ -105,7 +105,7 @@
     };
 
     /// @copydoc Transform::Apply
-    ApplyResult Apply(const Program* program,
+    ApplyResult Apply(const Program& program,
                       const DataMap& inputs,
                       DataMap& outputs) const override;
 
diff --git a/src/tint/lang/wgsl/ast/transform/canonicalize_entry_point_io.cc b/src/tint/lang/wgsl/ast/transform/canonicalize_entry_point_io.cc
index e9d244e..97425f5 100644
--- a/src/tint/lang/wgsl/ast/transform/canonicalize_entry_point_io.cc
+++ b/src/tint/lang/wgsl/ast/transform/canonicalize_entry_point_io.cc
@@ -884,11 +884,11 @@
     }
 };
 
-Transform::ApplyResult CanonicalizeEntryPointIO::Apply(const Program* src,
+Transform::ApplyResult CanonicalizeEntryPointIO::Apply(const Program& src,
                                                        const DataMap& inputs,
                                                        DataMap&) const {
     ProgramBuilder b;
-    program::CloneContext ctx{&b, src, /* auto_clone_symbols */ true};
+    program::CloneContext ctx{&b, &src, /* auto_clone_symbols */ true};
 
     auto* cfg = inputs.Get<Config>();
     if (cfg == nullptr) {
@@ -899,7 +899,7 @@
 
     // Remove entry point IO attributes from struct declarations.
     // New structures will be created for each entry point, as necessary.
-    for (auto* ty : src->AST().TypeDecls()) {
+    for (auto* ty : src.AST().TypeDecls()) {
         if (auto* struct_ty = ty->As<Struct>()) {
             for (auto* member : struct_ty->members) {
                 for (auto* attr : member->attributes) {
@@ -911,7 +911,7 @@
         }
     }
 
-    for (auto* func_ast : src->AST().Functions()) {
+    for (auto* func_ast : src.AST().Functions()) {
         if (!func_ast->IsEntryPoint()) {
             continue;
         }
diff --git a/src/tint/lang/wgsl/ast/transform/canonicalize_entry_point_io.h b/src/tint/lang/wgsl/ast/transform/canonicalize_entry_point_io.h
index a6b3715..d80a83f 100644
--- a/src/tint/lang/wgsl/ast/transform/canonicalize_entry_point_io.h
+++ b/src/tint/lang/wgsl/ast/transform/canonicalize_entry_point_io.h
@@ -161,7 +161,7 @@
     ~CanonicalizeEntryPointIO() override;
 
     /// @copydoc Transform::Apply
-    ApplyResult Apply(const Program* program,
+    ApplyResult Apply(const Program& program,
                       const DataMap& inputs,
                       DataMap& outputs) const override;
 
diff --git a/src/tint/lang/wgsl/ast/transform/demote_to_helper.cc b/src/tint/lang/wgsl/ast/transform/demote_to_helper.cc
index f3f16ff..8c9d3e2 100644
--- a/src/tint/lang/wgsl/ast/transform/demote_to_helper.cc
+++ b/src/tint/lang/wgsl/ast/transform/demote_to_helper.cc
@@ -40,14 +40,14 @@
 
 DemoteToHelper::~DemoteToHelper() = default;
 
-Transform::ApplyResult DemoteToHelper::Apply(const Program* src, const DataMap&, DataMap&) const {
-    auto& sem = src->Sem();
+Transform::ApplyResult DemoteToHelper::Apply(const Program& src, const DataMap&, DataMap&) const {
+    auto& sem = src.Sem();
 
     // Collect the set of functions that need to be processed.
     // A function needs to be processed if it is reachable by a shader that contains a discard at
     // any point in its call hierarchy.
     std::unordered_set<const sem::Function*> functions_to_process;
-    for (auto* func : src->AST().Functions()) {
+    for (auto* func : src.AST().Functions()) {
         if (!func->IsEntryPoint()) {
             continue;
         }
@@ -80,7 +80,7 @@
     }
 
     ProgramBuilder b;
-    program::CloneContext ctx{&b, src, /* auto_clone_symbols */ true};
+    program::CloneContext ctx{&b, &src, /* auto_clone_symbols */ true};
 
     // Create a module-scope flag that indicates whether the current invocation has been discarded.
     auto flag = b.Symbols().New("tint_discarded");
@@ -107,7 +107,7 @@
     // We also insert a discard statement before all return statements in entry points for shaders
     // that discard.
     std::unordered_map<const core::type::Type*, Symbol> atomic_cmpxchg_result_types;
-    for (auto* node : src->ASTNodes().Objects()) {
+    for (auto* node : src.ASTNodes().Objects()) {
         Switch(
             node,
 
@@ -149,17 +149,17 @@
                 auto* sem_call = sem.Get<sem::Call>(call);
                 auto* stmt = sem_call ? sem_call->Stmt() : nullptr;
                 auto* func = stmt ? stmt->Function() : nullptr;
-                auto* builtin = sem_call ? sem_call->Target()->As<sem::Builtin>() : nullptr;
+                auto* builtin = sem_call ? sem_call->Target()->As<sem::BuiltinFn>() : nullptr;
                 if (functions_to_process.count(func) == 0 || !builtin) {
                     return;
                 }
 
-                if (builtin->Type() == core::Function::kTextureStore) {
+                if (builtin->Fn() == core::BuiltinFn::kTextureStore) {
                     // A call to textureStore() will always be a statement.
                     // Wrap it inside a conditional block.
                     auto* masked_call = b.If(b.Not(flag), b.Block(ctx.Clone(stmt->Declaration())));
                     ctx.Replace(stmt->Declaration(), masked_call);
-                } else if (builtin->IsAtomic() && builtin->Type() != core::Function::kAtomicLoad) {
+                } else if (builtin->IsAtomic() && builtin->Fn() != core::BuiltinFn::kAtomicLoad) {
                     // A call to an atomic builtin can be a statement or an expression.
                     if (auto* call_stmt = stmt->Declaration()->As<CallStatement>();
                         call_stmt && call_stmt->expr == call) {
@@ -180,7 +180,7 @@
                         auto result = b.Sym();
                         Type result_ty;
                         const Statement* masked_call = nullptr;
-                        if (builtin->Type() == core::Function::kAtomicCompareExchangeWeak) {
+                        if (builtin->Fn() == core::BuiltinFn::kAtomicCompareExchangeWeak) {
                             // Special case for atomicCompareExchangeWeak as we cannot name its
                             // result type. We have to declare an equivalent struct and copy the
                             // original member values over to it.
diff --git a/src/tint/lang/wgsl/ast/transform/demote_to_helper.h b/src/tint/lang/wgsl/ast/transform/demote_to_helper.h
index 1fe3c59..7e18d7b 100644
--- a/src/tint/lang/wgsl/ast/transform/demote_to_helper.h
+++ b/src/tint/lang/wgsl/ast/transform/demote_to_helper.h
@@ -37,7 +37,7 @@
     ~DemoteToHelper() override;
 
     /// @copydoc Transform::Apply
-    ApplyResult Apply(const Program* program,
+    ApplyResult Apply(const Program& program,
                       const DataMap& inputs,
                       DataMap& outputs) const override;
 };
diff --git a/src/tint/lang/wgsl/ast/transform/direct_variable_access.cc b/src/tint/lang/wgsl/ast/transform/direct_variable_access.cc
index 9cdc734..c10c787 100644
--- a/src/tint/lang/wgsl/ast/transform/direct_variable_access.cc
+++ b/src/tint/lang/wgsl/ast/transform/direct_variable_access.cc
@@ -179,8 +179,8 @@
     /// Constructor
     /// @param src the source Program
     /// @param options the transform options
-    State(const Program* src, const Options& options)
-        : ctx{&b, src, /* auto_clone_symbols */ true}, opts(options) {}
+    State(const Program& src, const Options& options)
+        : ctx{&b, &src, /* auto_clone_symbols */ true}, opts(options) {}
 
     /// The main function for the transform.
     /// @returns the ApplyResult
@@ -1188,7 +1188,7 @@
 
 DirectVariableAccess::~DirectVariableAccess() = default;
 
-Transform::ApplyResult DirectVariableAccess::Apply(const Program* program,
+Transform::ApplyResult DirectVariableAccess::Apply(const Program& program,
                                                    const DataMap& inputs,
                                                    DataMap&) const {
     Options options;
diff --git a/src/tint/lang/wgsl/ast/transform/direct_variable_access.h b/src/tint/lang/wgsl/ast/transform/direct_variable_access.h
index 2866383..28cbb5a 100644
--- a/src/tint/lang/wgsl/ast/transform/direct_variable_access.h
+++ b/src/tint/lang/wgsl/ast/transform/direct_variable_access.h
@@ -61,7 +61,7 @@
     };
 
     /// @copydoc Transform::Apply
-    ApplyResult Apply(const Program* program,
+    ApplyResult Apply(const Program& program,
                       const DataMap& inputs,
                       DataMap& outputs) const override;
 
diff --git a/src/tint/lang/wgsl/ast/transform/disable_uniformity_analysis.cc b/src/tint/lang/wgsl/ast/transform/disable_uniformity_analysis.cc
index 34d212b..8dd0ce5 100644
--- a/src/tint/lang/wgsl/ast/transform/disable_uniformity_analysis.cc
+++ b/src/tint/lang/wgsl/ast/transform/disable_uniformity_analysis.cc
@@ -29,16 +29,16 @@
 
 DisableUniformityAnalysis::~DisableUniformityAnalysis() = default;
 
-Transform::ApplyResult DisableUniformityAnalysis::Apply(const Program* src,
+Transform::ApplyResult DisableUniformityAnalysis::Apply(const Program& src,
                                                         const DataMap&,
                                                         DataMap&) const {
-    if (src->Sem().Module()->Extensions().Contains(
+    if (src.Sem().Module()->Extensions().Contains(
             wgsl::Extension::kChromiumDisableUniformityAnalysis)) {
         return SkipTransform;
     }
 
     ProgramBuilder b;
-    program::CloneContext ctx{&b, src, /* auto_clone_symbols */ true};
+    program::CloneContext ctx{&b, &src, /* auto_clone_symbols */ true};
     b.Enable(wgsl::Extension::kChromiumDisableUniformityAnalysis);
 
     ctx.Clone();
diff --git a/src/tint/lang/wgsl/ast/transform/disable_uniformity_analysis.h b/src/tint/lang/wgsl/ast/transform/disable_uniformity_analysis.h
index 6bf6dc1..3fbf877 100644
--- a/src/tint/lang/wgsl/ast/transform/disable_uniformity_analysis.h
+++ b/src/tint/lang/wgsl/ast/transform/disable_uniformity_analysis.h
@@ -28,7 +28,7 @@
     ~DisableUniformityAnalysis() override;
 
     /// @copydoc Transform::Apply
-    ApplyResult Apply(const Program* program,
+    ApplyResult Apply(const Program& program,
                       const DataMap& inputs,
                       DataMap& outputs) const override;
 };
diff --git a/src/tint/lang/wgsl/ast/transform/expand_compound_assignment.cc b/src/tint/lang/wgsl/ast/transform/expand_compound_assignment.cc
index a568c1d..ec847ea 100644
--- a/src/tint/lang/wgsl/ast/transform/expand_compound_assignment.cc
+++ b/src/tint/lang/wgsl/ast/transform/expand_compound_assignment.cc
@@ -35,8 +35,8 @@
 
 namespace {
 
-bool ShouldRun(const Program* program) {
-    for (auto* node : program->ASTNodes().Objects()) {
+bool ShouldRun(const Program& program) {
+    for (auto* node : program.ASTNodes().Objects()) {
         if (node->IsAnyOf<CompoundAssignmentStatement, IncrementDecrementStatement>()) {
             return true;
         }
@@ -166,7 +166,7 @@
 
 ExpandCompoundAssignment::~ExpandCompoundAssignment() = default;
 
-Transform::ApplyResult ExpandCompoundAssignment::Apply(const Program* src,
+Transform::ApplyResult ExpandCompoundAssignment::Apply(const Program& src,
                                                        const DataMap&,
                                                        DataMap&) const {
     if (!ShouldRun(src)) {
@@ -174,9 +174,9 @@
     }
 
     ProgramBuilder b;
-    program::CloneContext ctx{&b, src, /* auto_clone_symbols */ true};
+    program::CloneContext ctx{&b, &src, /* auto_clone_symbols */ true};
     State state(ctx);
-    for (auto* node : src->ASTNodes().Objects()) {
+    for (auto* node : src.ASTNodes().Objects()) {
         if (auto* assign = node->As<CompoundAssignmentStatement>()) {
             state.Expand(assign, assign->lhs, ctx.Clone(assign->rhs), assign->op);
         } else if (auto* inc_dec = node->As<IncrementDecrementStatement>()) {
diff --git a/src/tint/lang/wgsl/ast/transform/expand_compound_assignment.h b/src/tint/lang/wgsl/ast/transform/expand_compound_assignment.h
index 33da512..ff04ffc 100644
--- a/src/tint/lang/wgsl/ast/transform/expand_compound_assignment.h
+++ b/src/tint/lang/wgsl/ast/transform/expand_compound_assignment.h
@@ -46,7 +46,7 @@
     ~ExpandCompoundAssignment() override;
 
     /// @copydoc Transform::Apply
-    ApplyResult Apply(const Program* program,
+    ApplyResult Apply(const Program& program,
                       const DataMap& inputs,
                       DataMap& outputs) const override;
 
diff --git a/src/tint/lang/wgsl/ast/transform/first_index_offset.cc b/src/tint/lang/wgsl/ast/transform/first_index_offset.cc
index 4deef70..170f063 100644
--- a/src/tint/lang/wgsl/ast/transform/first_index_offset.cc
+++ b/src/tint/lang/wgsl/ast/transform/first_index_offset.cc
@@ -41,8 +41,8 @@
 constexpr char kFirstVertexName[] = "first_vertex_index";
 constexpr char kFirstInstanceName[] = "first_instance_index";
 
-bool ShouldRun(const Program* program) {
-    for (auto* fn : program->AST().Functions()) {
+bool ShouldRun(const Program& program) {
+    for (auto* fn : program.AST().Functions()) {
         if (fn->PipelineStage() == PipelineStage::kVertex) {
             return true;
         }
@@ -64,7 +64,7 @@
 FirstIndexOffset::FirstIndexOffset() = default;
 FirstIndexOffset::~FirstIndexOffset() = default;
 
-Transform::ApplyResult FirstIndexOffset::Apply(const Program* src,
+Transform::ApplyResult FirstIndexOffset::Apply(const Program& src,
                                                const DataMap& inputs,
                                                DataMap& outputs) const {
     if (!ShouldRun(src)) {
@@ -72,7 +72,7 @@
     }
 
     ProgramBuilder b;
-    program::CloneContext ctx{&b, src, /* auto_clone_symbols */ true};
+    program::CloneContext ctx{&b, &src, /* auto_clone_symbols */ true};
 
     // Get the uniform buffer binding point
     uint32_t ub_binding = binding_;
@@ -95,7 +95,7 @@
         if (auto* var = node->As<Variable>()) {
             for (auto* attr : var->attributes) {
                 if (auto* builtin_attr = attr->As<BuiltinAttribute>()) {
-                    core::BuiltinValue builtin = src->Sem().Get(builtin_attr)->Value();
+                    core::BuiltinValue builtin = src.Sem().Get(builtin_attr)->Value();
                     if (builtin == core::BuiltinValue::kVertexIndex) {
                         auto* sem_var = ctx.src->Sem().Get(var);
                         builtin_vars.emplace(sem_var, kFirstVertexName);
@@ -112,7 +112,7 @@
         if (auto* member = node->As<StructMember>()) {
             for (auto* attr : member->attributes) {
                 if (auto* builtin_attr = attr->As<BuiltinAttribute>()) {
-                    core::BuiltinValue builtin = src->Sem().Get(builtin_attr)->Value();
+                    core::BuiltinValue builtin = src.Sem().Get(builtin_attr)->Value();
                     if (builtin == core::BuiltinValue::kVertexIndex) {
                         auto* sem_mem = ctx.src->Sem().Get(member);
                         builtin_members.emplace(sem_mem, kFirstVertexName);
diff --git a/src/tint/lang/wgsl/ast/transform/first_index_offset.h b/src/tint/lang/wgsl/ast/transform/first_index_offset.h
index d59713d..a37b55c 100644
--- a/src/tint/lang/wgsl/ast/transform/first_index_offset.h
+++ b/src/tint/lang/wgsl/ast/transform/first_index_offset.h
@@ -106,7 +106,7 @@
     ~FirstIndexOffset() override;
 
     /// @copydoc Transform::Apply
-    ApplyResult Apply(const Program* program,
+    ApplyResult Apply(const Program& program,
                       const DataMap& inputs,
                       DataMap& outputs) const override;
 
diff --git a/src/tint/lang/wgsl/ast/transform/helper_test.h b/src/tint/lang/wgsl/ast/transform/helper_test.h
index 0d185e4..836209a 100644
--- a/src/tint/lang/wgsl/ast/transform/helper_test.h
+++ b/src/tint/lang/wgsl/ast/transform/helper_test.h
@@ -37,7 +37,7 @@
     }
 
     wgsl::writer::Options options;
-    auto result = wgsl::writer::Generate(&program, options);
+    auto result = wgsl::writer::Generate(program, options);
     if (!result) {
         return "WGSL writer failed:\n" + result.Failure();
     }
@@ -106,7 +106,7 @@
         for (auto* transform_ptr : std::initializer_list<Transform*>{new TRANSFORMS()...}) {
             manager.append(std::unique_ptr<Transform>(transform_ptr));
         }
-        auto result = manager.Run(&program, data, outputs);
+        auto result = manager.Run(program, data, outputs);
         return {std::move(result), std::move(outputs)};
     }
 
@@ -116,21 +116,20 @@
     template <typename TRANSFORM>
     bool ShouldRun(Program&& program, const DataMap& data = {}) {
         if (!program.IsValid()) {
-            ADD_FAILURE() << "ShouldRun() called with invalid program: "
-                          << program.Diagnostics().str();
+            ADD_FAILURE() << "ShouldRun() called with invalid program: " << program.Diagnostics();
             return false;
         }
 
         const Transform& t = TRANSFORM();
 
         DataMap outputs;
-        auto result = t.Apply(&program, data, outputs);
+        auto result = t.Apply(program, data, outputs);
         if (!result) {
             return false;
         }
         if (!result->IsValid()) {
             ADD_FAILURE() << "Apply() called by ShouldRun() returned errors: "
-                          << result->Diagnostics().str();
+                          << result->Diagnostics();
             return true;
         }
         return result.has_value();
diff --git a/src/tint/lang/wgsl/ast/transform/manager.cc b/src/tint/lang/wgsl/ast/transform/manager.cc
index 2313bfe..0b3b47c 100644
--- a/src/tint/lang/wgsl/ast/transform/manager.cc
+++ b/src/tint/lang/wgsl/ast/transform/manager.cc
@@ -34,7 +34,9 @@
 Manager::Manager() = default;
 Manager::~Manager() = default;
 
-Program Manager::Run(const Program* program, const DataMap& inputs, DataMap& outputs) const {
+Program Manager::Run(const Program& program_in, const DataMap& inputs, DataMap& outputs) const {
+    const Program* program = &program_in;
+
 #if TINT_PRINT_PROGRAM_FOR_EACH_TRANSFORM
     auto print_program = [&](const char* msg, const Transform* transform) {
         auto wgsl = Program::printer(program);
@@ -44,9 +46,9 @@
                   << std::endl;
         std::cout << "=========================================================" << std::endl;
         std::cout << wgsl << std::endl;
-        if (!program->IsValid()) {
+        if (!program.IsValid()) {
             std::cout << "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --" << std::endl;
-            std::cout << program->Diagnostics().str() << std::endl;
+            std::cout << program.Diagnostics() << std::endl;
         }
         std::cout << "=========================================================" << std::endl
                   << std::endl;
@@ -58,7 +60,7 @@
     TINT_IF_PRINT_PROGRAM(print_program("Input of", nullptr));
 
     for (const auto& transform : transforms_) {
-        if (auto result = transform->Apply(program, inputs, outputs)) {
+        if (auto result = transform->Apply(*program, inputs, outputs)) {
             output.emplace(std::move(result.value()));
             program = &output.value();
 
diff --git a/src/tint/lang/wgsl/ast/transform/manager.h b/src/tint/lang/wgsl/ast/transform/manager.h
index babc02f..a9d628f 100644
--- a/src/tint/lang/wgsl/ast/transform/manager.h
+++ b/src/tint/lang/wgsl/ast/transform/manager.h
@@ -54,7 +54,7 @@
     /// @param inputs optional extra transform-specific input data
     /// @param outputs optional extra transform-specific output data
     /// @returns the transformed program
-    Program Run(const Program* program, const DataMap& inputs, DataMap& outputs) const;
+    Program Run(const Program& program, const DataMap& inputs, DataMap& outputs) const;
 
   private:
     std::vector<std::unique_ptr<Transform>> transforms_;
diff --git a/src/tint/lang/wgsl/ast/transform/manager_test.cc b/src/tint/lang/wgsl/ast/transform/manager_test.cc
index 1d8b61a..d146055 100644
--- a/src/tint/lang/wgsl/ast/transform/manager_test.cc
+++ b/src/tint/lang/wgsl/ast/transform/manager_test.cc
@@ -28,15 +28,15 @@
 using TransformManagerTest = testing::Test;
 
 class AST_NoOp final : public ast::transform::Transform {
-    ApplyResult Apply(const Program*, const DataMap&, DataMap&) const override {
+    ApplyResult Apply(const Program&, const DataMap&, DataMap&) const override {
         return SkipTransform;
     }
 };
 
 class AST_AddFunction final : public ast::transform::Transform {
-    ApplyResult Apply(const Program* src, const DataMap&, DataMap&) const override {
+    ApplyResult Apply(const Program& src, const DataMap&, DataMap&) const override {
         ProgramBuilder b;
-        program::CloneContext ctx{&b, src};
+        program::CloneContext ctx{&b, &src};
         b.Func(b.Sym("ast_func"), {}, b.ty.void_(), {});
         ctx.Clone();
         return resolver::Resolve(b);
@@ -57,7 +57,7 @@
     DataMap outputs;
     manager.Add<AST_NoOp>();
 
-    auto result = manager.Run(&ast, {}, outputs);
+    auto result = manager.Run(ast, {}, outputs);
     EXPECT_TRUE(result.IsValid()) << result.Diagnostics();
     EXPECT_NE(result.ID(), ast.ID());
     ASSERT_EQ(result.AST().Functions().Length(), 1u);
diff --git a/src/tint/lang/wgsl/ast/transform/multiplanar_external_texture.cc b/src/tint/lang/wgsl/ast/transform/multiplanar_external_texture.cc
index 1d68454..21e5309 100644
--- a/src/tint/lang/wgsl/ast/transform/multiplanar_external_texture.cc
+++ b/src/tint/lang/wgsl/ast/transform/multiplanar_external_texture.cc
@@ -35,8 +35,8 @@
 using namespace tint::core::fluent_types;     // NOLINT
 using namespace tint::core::number_suffixes;  // NOLINT
 
-bool ShouldRun(const Program* program) {
-    auto ext = program->Types().Find<core::type::ExternalTexture>();
+bool ShouldRun(const Program& program) {
+    auto ext = program.Types().Find<core::type::ExternalTexture>();
     return ext != nullptr;
 }
 
@@ -190,11 +190,11 @@
         // functions.
         ctx.ReplaceAll([&](const CallExpression* expr) -> const CallExpression* {
             auto* call = sem.Get(expr)->UnwrapMaterialize()->As<sem::Call>();
-            auto* builtin = call->Target()->As<sem::Builtin>();
+            auto* builtin = call->Target()->As<sem::BuiltinFn>();
 
             if (builtin && !builtin->Parameters().IsEmpty() &&
                 builtin->Parameters()[0]->Type()->Is<core::type::ExternalTexture>() &&
-                builtin->Type() != core::Function::kTextureDimensions) {
+                builtin->Fn() != core::BuiltinFn::kTextureDimensions) {
                 if (auto* var_user =
                         sem.GetVal(expr->args[0])->UnwrapLoad()->As<sem::VariableUser>()) {
                     auto it = new_binding_symbols.find(var_user->Variable());
@@ -207,10 +207,10 @@
                     }
                     auto& syms = it->second;
 
-                    switch (builtin->Type()) {
-                        case core::Function::kTextureLoad:
+                    switch (builtin->Fn()) {
+                        case core::BuiltinFn::kTextureLoad:
                             return createTextureLoad(call, syms);
-                        case core::Function::kTextureSampleBaseClampToEdge:
+                        case core::BuiltinFn::kTextureSampleBaseClampToEdge:
                             return createTextureSampleBaseClampToEdge(expr, syms);
                         default:
                             break;
@@ -309,13 +309,13 @@
     /// builtin function.
     /// @param call_type determines which function body to generate
     /// @returns a statement list that makes of the body of the chosen function
-    auto buildTextureBuiltinBody(core::Function call_type) {
+    auto buildTextureBuiltinBody(core::BuiltinFn call_type) {
         tint::Vector<const Statement*, 16> stmts;
         const CallExpression* single_plane_call = nullptr;
         const CallExpression* plane_0_call = nullptr;
         const CallExpression* plane_1_call = nullptr;
         switch (call_type) {
-            case core::Function::kTextureSampleBaseClampToEdge:
+            case core::BuiltinFn::kTextureSampleBaseClampToEdge:
                 stmts.Push(b.Decl(b.Let(
                     "modifiedCoords", b.Mul(b.MemberAccessor("params", "coordTransformationMatrix"),
                                             b.Call<vec3<f32>>("coord", 1_a)))));
@@ -345,7 +345,7 @@
                 // textureSampleLevel(plane1, smp, plane1_clamped, 0.0);
                 plane_1_call = b.Call("textureSampleLevel", "plane1", "smp", "plane1_clamped", 0_a);
                 break;
-            case core::Function::kTextureLoad:
+            case core::BuiltinFn::kTextureLoad:
                 // textureLoad(plane0, coord, 0);
                 single_plane_call = b.Call("textureLoad", "plane0", "coord", 0_a);
                 // textureLoad(plane0, coord, 0);
@@ -439,7 +439,7 @@
                        b.Param("params", b.ty(params_struct_sym)),
                    },
                    b.ty.vec4(b.ty.f32()),
-                   buildTextureBuiltinBody(core::Function::kTextureSampleBaseClampToEdge));
+                   buildTextureBuiltinBody(core::BuiltinFn::kTextureSampleBaseClampToEdge));
         }
 
         return b.Call(texture_sample_external_sym, tint::Vector{
@@ -486,7 +486,7 @@
                        b.Param("params", b.ty(params_struct_sym)),
                    },
                    b.ty.vec4(b.ty.f32()),  //
-                   buildTextureBuiltinBody(core::Function::kTextureLoad));
+                   buildTextureBuiltinBody(core::BuiltinFn::kTextureLoad));
 
             return name;
         });
@@ -511,7 +511,7 @@
 // buffer binding representing a struct of parameters. Calls to texture builtins that contain a
 // texture_external parameter will be transformed into a newly generated version of the function,
 // which can perform the desired operation on a single RGBA plane or on separate Y and UV planes.
-Transform::ApplyResult MultiplanarExternalTexture::Apply(const Program* src,
+Transform::ApplyResult MultiplanarExternalTexture::Apply(const Program& src,
                                                          const DataMap& inputs,
                                                          DataMap&) const {
     auto* new_binding_points = inputs.Get<NewBindingPoints>();
@@ -521,7 +521,7 @@
     }
 
     ProgramBuilder b;
-    program::CloneContext ctx{&b, src, /* auto_clone_symbols */ true};
+    program::CloneContext ctx{&b, &src, /* auto_clone_symbols */ true};
     if (!new_binding_points) {
         b.Diagnostics().add_error(diag::System::Transform, "missing new binding point data for " +
                                                                std::string(TypeInfo().name));
diff --git a/src/tint/lang/wgsl/ast/transform/multiplanar_external_texture.h b/src/tint/lang/wgsl/ast/transform/multiplanar_external_texture.h
index 189c06d..25e1f9e 100644
--- a/src/tint/lang/wgsl/ast/transform/multiplanar_external_texture.h
+++ b/src/tint/lang/wgsl/ast/transform/multiplanar_external_texture.h
@@ -20,7 +20,7 @@
 
 #include "src/tint/api/common/binding_point.h"
 #include "src/tint/api/options/external_texture.h"
-#include "src/tint/lang/core/function.h"
+#include "src/tint/lang/core/builtin_fn.h"
 #include "src/tint/lang/wgsl/ast/struct_member.h"
 #include "src/tint/lang/wgsl/ast/transform/transform.h"
 
@@ -69,7 +69,7 @@
     ~MultiplanarExternalTexture() override;
 
     /// @copydoc Transform::Apply
-    ApplyResult Apply(const Program* program,
+    ApplyResult Apply(const Program& program,
                       const DataMap& inputs,
                       DataMap& outputs) const override;
 
diff --git a/src/tint/lang/wgsl/ast/transform/preserve_padding.cc b/src/tint/lang/wgsl/ast/transform/preserve_padding.cc
index 25cba78..9675b03 100644
--- a/src/tint/lang/wgsl/ast/transform/preserve_padding.cc
+++ b/src/tint/lang/wgsl/ast/transform/preserve_padding.cc
@@ -41,7 +41,7 @@
 struct PreservePadding::State {
     /// Constructor
     /// @param src the source Program
-    explicit State(const Program* src) : ctx{&b, src, /* auto_clone_symbols */ true} {}
+    explicit State(const Program& src) : ctx{&b, &src, /* auto_clone_symbols */ true} {}
 
     /// The main function for the transform.
     /// @returns the ApplyResult
@@ -240,7 +240,7 @@
     Hashmap<const core::type::Type*, Symbol, 8> helpers;
 };
 
-Transform::ApplyResult PreservePadding::Apply(const Program* program,
+Transform::ApplyResult PreservePadding::Apply(const Program& program,
                                               const DataMap&,
                                               DataMap&) const {
     return State(program).Run();
diff --git a/src/tint/lang/wgsl/ast/transform/preserve_padding.h b/src/tint/lang/wgsl/ast/transform/preserve_padding.h
index 21c8131..6e1866e 100644
--- a/src/tint/lang/wgsl/ast/transform/preserve_padding.h
+++ b/src/tint/lang/wgsl/ast/transform/preserve_padding.h
@@ -34,7 +34,7 @@
     ~PreservePadding() override;
 
     /// @copydoc Transform::Apply
-    ApplyResult Apply(const Program* program,
+    ApplyResult Apply(const Program& program,
                       const DataMap& inputs,
                       DataMap& outputs) const override;
 
diff --git a/src/tint/lang/wgsl/ast/transform/promote_initializers_to_let.cc b/src/tint/lang/wgsl/ast/transform/promote_initializers_to_let.cc
index 9533281..5b02260 100644
--- a/src/tint/lang/wgsl/ast/transform/promote_initializers_to_let.cc
+++ b/src/tint/lang/wgsl/ast/transform/promote_initializers_to_let.cc
@@ -35,11 +35,11 @@
 
 PromoteInitializersToLet::~PromoteInitializersToLet() = default;
 
-Transform::ApplyResult PromoteInitializersToLet::Apply(const Program* src,
+Transform::ApplyResult PromoteInitializersToLet::Apply(const Program& src,
                                                        const DataMap&,
                                                        DataMap&) const {
     ProgramBuilder b;
-    program::CloneContext ctx{&b, src, /* auto_clone_symbols */ true};
+    program::CloneContext ctx{&b, &src, /* auto_clone_symbols */ true};
 
     // Returns true if the expression should be hoisted to a new let statement before the
     // expression's statement.
@@ -89,8 +89,8 @@
     Hashset<const Expression*, 32> const_chains;
 
     // Walk the AST nodes. This order guarantees that leaf-expressions are visited first.
-    for (auto* node : src->ASTNodes().Objects()) {
-        if (auto* sem = src->Sem().GetVal(node)) {
+    for (auto* node : src.ASTNodes().Objects()) {
+        if (auto* sem = src.Sem().GetVal(node)) {
             auto* stmt = sem->Stmt();
             if (!stmt) {
                 // Expression is outside of a statement. This usually means the expression is part
@@ -123,7 +123,7 @@
     // After walking the full AST, const_chains only contains the outer-most constant expressions.
     // Check if any of these need hoisting, and append those to to_hoist.
     for (auto* expr : const_chains) {
-        if (auto* sem = src->Sem().GetVal(expr); should_hoist(sem)) {
+        if (auto* sem = src.Sem().GetVal(expr); should_hoist(sem)) {
             to_hoist.Push(sem);
         }
     }
diff --git a/src/tint/lang/wgsl/ast/transform/promote_initializers_to_let.h b/src/tint/lang/wgsl/ast/transform/promote_initializers_to_let.h
index cb6ecba..2a85e17 100644
--- a/src/tint/lang/wgsl/ast/transform/promote_initializers_to_let.h
+++ b/src/tint/lang/wgsl/ast/transform/promote_initializers_to_let.h
@@ -34,7 +34,7 @@
     ~PromoteInitializersToLet() override;
 
     /// @copydoc Transform::Apply
-    ApplyResult Apply(const Program* program,
+    ApplyResult Apply(const Program& program,
                       const DataMap& inputs,
                       DataMap& outputs) const override;
 };
diff --git a/src/tint/lang/wgsl/ast/transform/promote_side_effects_to_decl.cc b/src/tint/lang/wgsl/ast/transform/promote_side_effects_to_decl.cc
index 61dcccc..07003b4 100644
--- a/src/tint/lang/wgsl/ast/transform/promote_side_effects_to_decl.cc
+++ b/src/tint/lang/wgsl/ast/transform/promote_side_effects_to_decl.cc
@@ -56,20 +56,20 @@
 // to else {if}s so that the next transform, DecomposeSideEffects, can insert
 // hoisted expressions above their current location.
 struct SimplifySideEffectStatements : Castable<PromoteSideEffectsToDecl, Transform> {
-    ApplyResult Apply(const Program* src, const DataMap& inputs, DataMap& outputs) const override;
+    ApplyResult Apply(const Program& src, const DataMap& inputs, DataMap& outputs) const override;
 };
 
-Transform::ApplyResult SimplifySideEffectStatements::Apply(const Program* src,
+Transform::ApplyResult SimplifySideEffectStatements::Apply(const Program& src,
                                                            const DataMap&,
                                                            DataMap&) const {
     ProgramBuilder b;
-    program::CloneContext ctx{&b, src, /* auto_clone_symbols */ true};
+    program::CloneContext ctx{&b, &src, /* auto_clone_symbols */ true};
 
     bool made_changes = false;
 
     HoistToDeclBefore hoist_to_decl_before(ctx);
     for (auto* node : ctx.src->ASTNodes().Objects()) {
-        if (auto* sem_expr = src->Sem().GetVal(node)) {
+        if (auto* sem_expr = src.Sem().GetVal(node)) {
             if (!sem_expr->HasSideEffects()) {
                 continue;
             }
@@ -93,7 +93,7 @@
 struct DecomposeSideEffects : Castable<PromoteSideEffectsToDecl, Transform> {
     class CollectHoistsState;
     class DecomposeState;
-    ApplyResult Apply(const Program* src, const DataMap& inputs, DataMap& outputs) const override;
+    ApplyResult Apply(const Program& src, const DataMap& inputs, DataMap& outputs) const override;
 };
 
 // CollectHoistsState traverses the AST top-down, identifying which expressions
@@ -648,11 +648,11 @@
     }
 };
 
-Transform::ApplyResult DecomposeSideEffects::Apply(const Program* src,
+Transform::ApplyResult DecomposeSideEffects::Apply(const Program& src,
                                                    const DataMap&,
                                                    DataMap&) const {
     ProgramBuilder b;
-    program::CloneContext ctx{&b, src, /* auto_clone_symbols */ true};
+    program::CloneContext ctx{&b, &src, /* auto_clone_symbols */ true};
 
     // First collect side-effecting expressions to hoist
     CollectHoistsState collect_hoists_state{ctx};
@@ -671,7 +671,7 @@
 PromoteSideEffectsToDecl::PromoteSideEffectsToDecl() = default;
 PromoteSideEffectsToDecl::~PromoteSideEffectsToDecl() = default;
 
-Transform::ApplyResult PromoteSideEffectsToDecl::Apply(const Program* src,
+Transform::ApplyResult PromoteSideEffectsToDecl::Apply(const Program& src,
                                                        const DataMap& inputs,
                                                        DataMap& outputs) const {
     Manager manager;
diff --git a/src/tint/lang/wgsl/ast/transform/promote_side_effects_to_decl.h b/src/tint/lang/wgsl/ast/transform/promote_side_effects_to_decl.h
index a8c7196..b5ee223 100644
--- a/src/tint/lang/wgsl/ast/transform/promote_side_effects_to_decl.h
+++ b/src/tint/lang/wgsl/ast/transform/promote_side_effects_to_decl.h
@@ -32,7 +32,7 @@
     ~PromoteSideEffectsToDecl() override;
 
     /// @copydoc Transform::Apply
-    ApplyResult Apply(const Program* program,
+    ApplyResult Apply(const Program& program,
                       const DataMap& inputs,
                       DataMap& outputs) const override;
 };
diff --git a/src/tint/lang/wgsl/ast/transform/remove_phonies.cc b/src/tint/lang/wgsl/ast/transform/remove_phonies.cc
index 4a6a392..5661d78 100644
--- a/src/tint/lang/wgsl/ast/transform/remove_phonies.cc
+++ b/src/tint/lang/wgsl/ast/transform/remove_phonies.cc
@@ -43,16 +43,16 @@
 
 RemovePhonies::~RemovePhonies() = default;
 
-Transform::ApplyResult RemovePhonies::Apply(const Program* src, const DataMap&, DataMap&) const {
+Transform::ApplyResult RemovePhonies::Apply(const Program& src, const DataMap&, DataMap&) const {
     ProgramBuilder b;
-    program::CloneContext ctx{&b, src, /* auto_clone_symbols */ true};
+    program::CloneContext ctx{&b, &src, /* auto_clone_symbols */ true};
 
-    auto& sem = src->Sem();
+    auto& sem = src.Sem();
 
     Hashmap<SinkSignature, Symbol, 8> sinks;
 
     bool made_changes = false;
-    for (auto* node : src->ASTNodes().Objects()) {
+    for (auto* node : src.ASTNodes().Objects()) {
         Switch(
             node,
             [&](const AssignmentStatement* stmt) {
@@ -71,7 +71,7 @@
                                 // have side effects. Just skip.
                                 return TraverseAction::Skip;
                             }
-                            if (call->Target()->IsAnyOf<sem::Function, sem::Builtin>() &&
+                            if (call->Target()->IsAnyOf<sem::Function, sem::BuiltinFn>() &&
                                 call->HasSideEffects()) {
                                 side_effects.push_back(expr);
                                 return TraverseAction::Skip;
diff --git a/src/tint/lang/wgsl/ast/transform/remove_phonies.h b/src/tint/lang/wgsl/ast/transform/remove_phonies.h
index 5c3d04c..17b5657 100644
--- a/src/tint/lang/wgsl/ast/transform/remove_phonies.h
+++ b/src/tint/lang/wgsl/ast/transform/remove_phonies.h
@@ -34,7 +34,7 @@
     ~RemovePhonies() override;
 
     /// @copydoc Transform::Apply
-    ApplyResult Apply(const Program* program,
+    ApplyResult Apply(const Program& program,
                       const DataMap& inputs,
                       DataMap& outputs) const override;
 };
diff --git a/src/tint/lang/wgsl/ast/transform/remove_unreachable_statements.cc b/src/tint/lang/wgsl/ast/transform/remove_unreachable_statements.cc
index 82bd693..50cafba 100644
--- a/src/tint/lang/wgsl/ast/transform/remove_unreachable_statements.cc
+++ b/src/tint/lang/wgsl/ast/transform/remove_unreachable_statements.cc
@@ -38,15 +38,15 @@
 
 RemoveUnreachableStatements::~RemoveUnreachableStatements() = default;
 
-Transform::ApplyResult RemoveUnreachableStatements::Apply(const Program* src,
+Transform::ApplyResult RemoveUnreachableStatements::Apply(const Program& src,
                                                           const DataMap&,
                                                           DataMap&) const {
     ProgramBuilder b;
-    program::CloneContext ctx{&b, src, /* auto_clone_symbols */ true};
+    program::CloneContext ctx{&b, &src, /* auto_clone_symbols */ true};
 
     bool made_changes = false;
-    for (auto* node : src->ASTNodes().Objects()) {
-        if (auto* stmt = src->Sem().Get<sem::Statement>(node)) {
+    for (auto* node : src.ASTNodes().Objects()) {
+        if (auto* stmt = src.Sem().Get<sem::Statement>(node)) {
             if (!stmt->IsReachable()) {
                 RemoveStatement(ctx, stmt->Declaration());
                 made_changes = true;
diff --git a/src/tint/lang/wgsl/ast/transform/remove_unreachable_statements.h b/src/tint/lang/wgsl/ast/transform/remove_unreachable_statements.h
index 4469fc7..57b79e8 100644
--- a/src/tint/lang/wgsl/ast/transform/remove_unreachable_statements.h
+++ b/src/tint/lang/wgsl/ast/transform/remove_unreachable_statements.h
@@ -33,7 +33,7 @@
     ~RemoveUnreachableStatements() override;
 
     /// @copydoc Transform::Apply
-    ApplyResult Apply(const Program* program,
+    ApplyResult Apply(const Program& program,
                       const DataMap& inputs,
                       DataMap& outputs) const override;
 };
diff --git a/src/tint/lang/wgsl/ast/transform/renamer.cc b/src/tint/lang/wgsl/ast/transform/renamer.cc
index af66929..f53c942 100644
--- a/src/tint/lang/wgsl/ast/transform/renamer.cc
+++ b/src/tint/lang/wgsl/ast/transform/renamer.cc
@@ -1258,18 +1258,18 @@
 Renamer::Renamer() = default;
 Renamer::~Renamer() = default;
 
-Transform::ApplyResult Renamer::Apply(const Program* src,
+Transform::ApplyResult Renamer::Apply(const Program& src,
                                       const DataMap& inputs,
                                       DataMap& outputs) const {
     Hashset<Symbol, 16> global_decls;
-    for (auto* decl : src->AST().TypeDecls()) {
+    for (auto* decl : src.AST().TypeDecls()) {
         global_decls.Add(decl->name->symbol);
     }
 
     // Identifiers that need to keep their symbols preserved.
     Hashset<const Identifier*, 16> preserved_identifiers;
 
-    for (auto* node : src->ASTNodes().Objects()) {
+    for (auto* node : src.ASTNodes().Objects()) {
         auto preserve_if_builtin_type = [&](const Identifier* ident) {
             if (!global_decls.Contains(ident->symbol)) {
                 preserved_identifiers.Add(ident);
@@ -1279,10 +1279,10 @@
         Switch(
             node,
             [&](const MemberAccessorExpression* accessor) {
-                auto* sem = src->Sem().Get(accessor)->UnwrapLoad();
+                auto* sem = src.Sem().Get(accessor)->UnwrapLoad();
                 if (sem->Is<sem::Swizzle>()) {
                     preserved_identifiers.Add(accessor->member);
-                } else if (auto* str_expr = src->Sem().GetVal(accessor->object)) {
+                } else if (auto* str_expr = src.Sem().GetVal(accessor->object)) {
                     if (auto* ty = str_expr->Type()->UnwrapRef()->As<core::type::Struct>()) {
                         if (!ty->Is<sem::Struct>()) {  // Builtin structure
                             preserved_identifiers.Add(accessor->member);
@@ -1304,7 +1304,7 @@
             },
             [&](const IdentifierExpression* expr) {
                 Switch(
-                    src->Sem().Get(expr),  //
+                    src.Sem().Get(expr),  //
                     [&](const sem::BuiltinEnumExpressionBase*) {
                         preserved_identifiers.Add(expr->identifier);
                     },
@@ -1314,8 +1314,8 @@
             },
             [&](const CallExpression* call) {
                 Switch(
-                    src->Sem().Get(call)->UnwrapMaterialize()->As<sem::Call>()->Target(),
-                    [&](const sem::Builtin*) {
+                    src.Sem().Get(call)->UnwrapMaterialize()->As<sem::Call>()->Target(),
+                    [&](const sem::BuiltinFn*) {
                         preserved_identifiers.Add(call->target->identifier);
                     },
                     [&](const sem::ValueConversion*) {
@@ -1372,7 +1372,7 @@
     Hashmap<Symbol, Symbol, 32> remappings;
 
     ProgramBuilder b;
-    program::CloneContext ctx{&b, src, /* auto_clone_symbols */ false};
+    program::CloneContext ctx{&b, &src, /* auto_clone_symbols */ false};
 
     ctx.ReplaceAll([&](const Identifier* ident) -> const Identifier* {
         const auto symbol = ident->symbol;
diff --git a/src/tint/lang/wgsl/ast/transform/renamer.h b/src/tint/lang/wgsl/ast/transform/renamer.h
index b2520a2..ec81454 100644
--- a/src/tint/lang/wgsl/ast/transform/renamer.h
+++ b/src/tint/lang/wgsl/ast/transform/renamer.h
@@ -86,7 +86,7 @@
     ~Renamer() override;
 
     /// @copydoc Transform::Apply
-    ApplyResult Apply(const Program* program,
+    ApplyResult Apply(const Program& program,
                       const DataMap& inputs,
                       DataMap& outputs) const override;
 };
diff --git a/src/tint/lang/wgsl/ast/transform/renamer_test.cc b/src/tint/lang/wgsl/ast/transform/renamer_test.cc
index c127282..48fb950 100644
--- a/src/tint/lang/wgsl/ast/transform/renamer_test.cc
+++ b/src/tint/lang/wgsl/ast/transform/renamer_test.cc
@@ -19,7 +19,7 @@
 #include <vector>
 
 #include "gmock/gmock.h"
-#include "src/tint/lang/core/builtin.h"
+#include "src/tint/lang/core/builtin_type.h"
 #include "src/tint/lang/core/texel_format.h"
 #include "src/tint/lang/wgsl/ast/transform/helper_test.h"
 #include "src/tint/utils/text/string.h"
@@ -1712,7 +1712,7 @@
 
 std::vector<const char*> ConstructableTypes() {
     std::vector<const char*> out;
-    for (auto* ty : core::kBuiltinStrings) {
+    for (auto* ty : core::kBuiltinTypeStrings) {
         std::string_view type(ty);
         if (type != "ptr" && type != "atomic" && !tint::HasPrefix(type, "sampler") &&
             !tint::HasPrefix(type, "texture") && !tint::HasPrefix(type, "__")) {
@@ -1924,7 +1924,7 @@
 /// @return WGSL builtin identifier keywords
 std::vector<const char*> Identifiers() {
     std::vector<const char*> out;
-    for (auto* ident : core::kBuiltinStrings) {
+    for (auto* ident : core::kBuiltinTypeStrings) {
         if (!tint::HasPrefix(ident, "__")) {
             out.push_back(ident);
         }
diff --git a/src/tint/lang/wgsl/ast/transform/robustness.cc b/src/tint/lang/wgsl/ast/transform/robustness.cc
index 8a60ff8..09b85fd 100644
--- a/src/tint/lang/wgsl/ast/transform/robustness.cc
+++ b/src/tint/lang/wgsl/ast/transform/robustness.cc
@@ -24,7 +24,7 @@
 #include "src/tint/lang/wgsl/program/program_builder.h"
 #include "src/tint/lang/wgsl/resolver/resolve.h"
 #include "src/tint/lang/wgsl/sem/block_statement.h"
-#include "src/tint/lang/wgsl/sem/builtin.h"
+#include "src/tint/lang/wgsl/sem/builtin_fn.h"
 #include "src/tint/lang/wgsl/sem/call.h"
 #include "src/tint/lang/wgsl/sem/function.h"
 #include "src/tint/lang/wgsl/sem/index_accessor_expression.h"
@@ -47,7 +47,7 @@
     /// Constructor
     /// @param p the source program
     /// @param c the transform config
-    State(const Program* p, Config&& c) : src(p), cfg(std::move(c)) {}
+    State(const Program& p, Config&& c) : src(p), cfg(std::move(c)) {}
 
     /// Runs the transform
     /// @returns the new program or SkipTransform if the transform is not required
@@ -139,7 +139,7 @@
                     if (auto* call = sem.Get<sem::Call>(e)) {
                         Switch(
                             call->Target(),  //
-                            [&](const sem::Builtin* builtin) {
+                            [&](const sem::BuiltinFn* builtin) {
                                 // Calls to builtins may require robustness transformation.
                                 // Inspect.
                                 if (builtin->IsTexture()) {
@@ -204,13 +204,13 @@
 
   private:
     /// The source program
-    const Program* const src;
+    const Program& src;
     /// The transform's config
     Config cfg;
     /// The target program builder
     ProgramBuilder b{};
     /// The clone context
-    program::CloneContext ctx = {&b, src, /* auto_clone_symbols */ true};
+    program::CloneContext ctx = {&b, &src, /* auto_clone_symbols */ true};
     /// Helper for hoisting declarations
     HoistToDeclBefore hoist{ctx};
     /// Alias to the source program's semantic info
@@ -246,7 +246,7 @@
                     // Must clamp, even if the index is constant.
 
                     auto* arr_ptr = b.AddressOf(ctx.Clone(expr->Object()->Declaration()));
-                    return b.Sub(b.Call(core::Function::kArrayLength, arr_ptr), 1_u);
+                    return b.Sub(b.Call(core::BuiltinFn::kArrayLength, arr_ptr), 1_u);
                 }
                 if (auto count = arr->ConstantCount()) {
                     if (expr->Index()->ConstantValue()) {
@@ -272,7 +272,7 @@
     /// Transform the program to insert additional predicate parameters to all user functions that
     /// have a pointer parameter type in an address space that has predicate action.
     void AddPredicateParameters() {
-        for (auto* fn : src->AST().Functions()) {
+        for (auto* fn : src.AST().Functions()) {
             for (auto* param : fn->params) {
                 auto* sem_param = sem.Get(param);
                 if (auto* ptr = sem_param->Type()->As<core::type::Pointer>()) {
@@ -352,12 +352,12 @@
 
         auto* expr_sem = expr->Unwrap()->As<sem::IndexAccessorExpression>();
         auto idx = CastToU32(expr_sem->Index());
-        auto* clamped_idx = b.Call(core::Function::kMin, idx, max);
+        auto* clamped_idx = b.Call(core::BuiltinFn::kMin, idx, max);
         ctx.Replace(expr->Declaration()->index, clamped_idx);
     }
 
     /// Applies predication to the non-texture builtin call, if required.
-    void MaybePredicateNonTextureBuiltin(const sem::Call* call, const sem::Builtin* builtin) {
+    void MaybePredicateNonTextureBuiltin(const sem::Call* call, const sem::BuiltinFn* builtin) {
         // Gather the predications for the builtin arguments
         const Expression* predicate = nullptr;
         for (auto* arg : call->Declaration()->args) {
@@ -367,14 +367,14 @@
         }
 
         if (predicate) {
-            if (builtin->Type() == core::Function::kWorkgroupUniformLoad) {
+            if (builtin->Fn() == core::BuiltinFn::kWorkgroupUniformLoad) {
                 // https://www.w3.org/TR/WGSL/#workgroupUniformLoad-builtin:
                 //  "Executes a control barrier synchronization function that affects memory and
                 //   atomic operations in the workgroup address space."
                 // Because the call acts like a control barrier, we need to make sure that we still
                 // trigger a workgroup barrier if the predicate fails.
                 PredicateCall(call, predicate,
-                              b.Block(b.CallStmt(b.Call(core::Function::kWorkgroupBarrier))));
+                              b.Block(b.CallStmt(b.Call(core::BuiltinFn::kWorkgroupBarrier))));
             } else {
                 PredicateCall(call, predicate);
             }
@@ -383,8 +383,8 @@
 
     /// Applies predication to texture builtins, based on whether the coordinates, array index and
     /// level arguments are all in bounds.
-    void PredicateTextureBuiltin(const sem::Call* call, const sem::Builtin* builtin) {
-        if (!TextureBuiltinNeedsRobustness(builtin->Type())) {
+    void PredicateTextureBuiltin(const sem::Call* call, const sem::BuiltinFn* builtin) {
+        if (!TextureBuiltinNeedsRobustness(builtin->Fn())) {
             return;
         }
 
@@ -417,7 +417,7 @@
                 // let num_levels = textureNumLevels(texture-arg);
                 num_levels = b.Symbols().New("num_levels");
                 hoist.InsertBefore(
-                    stmt, b.Decl(b.Let(num_levels, b.Call(core::Function::kTextureNumLevels,
+                    stmt, b.Decl(b.Let(num_levels, b.Call(core::BuiltinFn::kTextureNumLevels,
                                                           ctx.Clone(texture_arg)))));
 
                 // predicate: level_idx < num_levels
@@ -442,12 +442,12 @@
                 // predicate: all(coords < textureDimensions(texture))
                 auto* dimensions =
                     level_idx.IsValid()
-                        ? b.Call(core::Function::kTextureDimensions, ctx.Clone(texture_arg),
-                                 b.Call(core::Function::kMin, b.Expr(level_idx),
+                        ? b.Call(core::BuiltinFn::kTextureDimensions, ctx.Clone(texture_arg),
+                                 b.Call(core::BuiltinFn::kMin, b.Expr(level_idx),
                                         b.Sub(num_levels, 1_a)))
-                        : b.Call(core::Function::kTextureDimensions, ctx.Clone(texture_arg));
+                        : b.Call(core::BuiltinFn::kTextureDimensions, ctx.Clone(texture_arg));
                 predicate =
-                    And(predicate, b.Call(core::Function::kAll, b.LessThan(coords, dimensions)));
+                    And(predicate, b.Call(core::BuiltinFn::kAll, b.LessThan(coords, dimensions)));
 
                 // Replace the level argument with `coord`
                 ctx.Replace(arg, b.Expr(coords));
@@ -457,7 +457,7 @@
         if (array_arg_idx >= 0) {
             // let array_idx = u32(array-arg)
             auto* arg = expr->args[static_cast<size_t>(array_arg_idx)];
-            auto* num_layers = b.Call(core::Function::kTextureNumLayers, ctx.Clone(texture_arg));
+            auto* num_layers = b.Call(core::BuiltinFn::kTextureNumLayers, ctx.Clone(texture_arg));
             auto array_idx = b.Symbols().New("array_idx");
             hoist.InsertBefore(stmt, b.Decl(b.Let(array_idx, CastToUnsigned(ctx.Clone(arg), 1u))));
 
@@ -475,8 +475,8 @@
 
     /// Applies bounds clamping to the coordinates, array index and level arguments of the texture
     /// builtin.
-    void ClampTextureBuiltin(const sem::Call* call, const sem::Builtin* builtin) {
-        if (!TextureBuiltinNeedsRobustness(builtin->Type())) {
+    void ClampTextureBuiltin(const sem::Call* call, const sem::BuiltinFn* builtin) {
+        if (!TextureBuiltinNeedsRobustness(builtin->Fn())) {
             return;
         }
 
@@ -502,10 +502,10 @@
                 const auto* arg = expr->args[static_cast<size_t>(level_arg_idx)];
                 level_idx = b.Symbols().New("level_idx");
                 const auto* num_levels =
-                    b.Call(core::Function::kTextureNumLevels, ctx.Clone(texture_arg));
+                    b.Call(core::BuiltinFn::kTextureNumLevels, ctx.Clone(texture_arg));
                 const auto* max = b.Sub(num_levels, 1_a);
                 hoist.InsertBefore(
-                    stmt, b.Decl(b.Let(level_idx, b.Call(core::Function::kMin,
+                    stmt, b.Decl(b.Let(level_idx, b.Call(core::BuiltinFn::kMin,
                                                          b.Call<u32>(ctx.Clone(arg)), max))));
                 ctx.Replace(arg, b.Expr(level_idx));
             }
@@ -519,9 +519,9 @@
                 const auto width = WidthOf(param->Type());
                 const auto* dimensions =
                     level_idx.IsValid()
-                        ? b.Call(core::Function::kTextureDimensions, ctx.Clone(texture_arg),
+                        ? b.Call(core::BuiltinFn::kTextureDimensions, ctx.Clone(texture_arg),
                                  level_idx)
-                        : b.Call(core::Function::kTextureDimensions, ctx.Clone(texture_arg));
+                        : b.Call(core::BuiltinFn::kTextureDimensions, ctx.Clone(texture_arg));
 
                 // dimensions is u32 or vecN<u32>
                 const auto* unsigned_max = b.Sub(dimensions, ScalarOrVec(b.Expr(1_a), width));
@@ -529,9 +529,9 @@
                     const auto* zero = ScalarOrVec(b.Expr(0_a), width);
                     const auto* signed_max = CastToSigned(unsigned_max, width);
                     ctx.Replace(arg,
-                                b.Call(core::Function::kClamp, ctx.Clone(arg), zero, signed_max));
+                                b.Call(core::BuiltinFn::kClamp, ctx.Clone(arg), zero, signed_max));
                 } else {
-                    ctx.Replace(arg, b.Call(core::Function::kMin, ctx.Clone(arg), unsigned_max));
+                    ctx.Replace(arg, b.Call(core::BuiltinFn::kMin, ctx.Clone(arg), unsigned_max));
                 }
             }
         }
@@ -540,14 +540,14 @@
         if (array_arg_idx >= 0) {
             auto* param = builtin->Parameters()[static_cast<size_t>(array_arg_idx)];
             auto* arg = expr->args[static_cast<size_t>(array_arg_idx)];
-            auto* num_layers = b.Call(core::Function::kTextureNumLayers, ctx.Clone(texture_arg));
+            auto* num_layers = b.Call(core::BuiltinFn::kTextureNumLayers, ctx.Clone(texture_arg));
 
             const auto* unsigned_max = b.Sub(num_layers, 1_a);
             if (param->Type()->is_signed_integer_scalar()) {
                 const auto* signed_max = CastToSigned(unsigned_max, 1u);
-                ctx.Replace(arg, b.Call(core::Function::kClamp, ctx.Clone(arg), 0_a, signed_max));
+                ctx.Replace(arg, b.Call(core::BuiltinFn::kClamp, ctx.Clone(arg), 0_a, signed_max));
             } else {
-                ctx.Replace(arg, b.Call(core::Function::kMin, ctx.Clone(arg), unsigned_max));
+                ctx.Replace(arg, b.Call(core::BuiltinFn::kMin, ctx.Clone(arg), unsigned_max));
             }
         }
     }
@@ -555,9 +555,9 @@
     /// @param type builtin type
     /// @returns true if the given builtin is a texture function that requires predication or
     /// clamping of arguments.
-    bool TextureBuiltinNeedsRobustness(core::Function type) {
-        return type == core::Function::kTextureLoad || type == core::Function::kTextureStore ||
-               type == core::Function::kTextureDimensions;
+    bool TextureBuiltinNeedsRobustness(core::BuiltinFn type) {
+        return type == core::BuiltinFn::kTextureLoad || type == core::BuiltinFn::kTextureStore ||
+               type == core::BuiltinFn::kTextureDimensions;
     }
 
     /// @returns a bitwise and of the two expressions, or the other expression if one is null.
@@ -722,7 +722,7 @@
 Robustness::Robustness() = default;
 Robustness::~Robustness() = default;
 
-Transform::ApplyResult Robustness::Apply(const Program* src,
+Transform::ApplyResult Robustness::Apply(const Program& src,
                                          const DataMap& inputs,
                                          DataMap&) const {
     Config cfg;
diff --git a/src/tint/lang/wgsl/ast/transform/robustness.h b/src/tint/lang/wgsl/ast/transform/robustness.h
index bf8d5be..5ca301f 100644
--- a/src/tint/lang/wgsl/ast/transform/robustness.h
+++ b/src/tint/lang/wgsl/ast/transform/robustness.h
@@ -94,7 +94,7 @@
     ~Robustness() override;
 
     /// @copydoc Transform::Apply
-    ApplyResult Apply(const Program* program,
+    ApplyResult Apply(const Program& program,
                       const DataMap& inputs,
                       DataMap& outputs) const override;
 
diff --git a/src/tint/lang/wgsl/ast/transform/simplify_pointers.cc b/src/tint/lang/wgsl/ast/transform/simplify_pointers.cc
index 98de628..b5a2181 100644
--- a/src/tint/lang/wgsl/ast/transform/simplify_pointers.cc
+++ b/src/tint/lang/wgsl/ast/transform/simplify_pointers.cc
@@ -51,15 +51,15 @@
 /// PIMPL state for the transform
 struct SimplifyPointers::State {
     /// The source program
-    const Program* const src;
+    const Program& src;
     /// The target program builder
     ProgramBuilder b;
     /// The clone context
-    program::CloneContext ctx = {&b, src, /* auto_clone_symbols */ true};
+    program::CloneContext ctx = {&b, &src, /* auto_clone_symbols */ true};
 
     /// Constructor
     /// @param program the source program
-    explicit State(const Program* program) : src(program) {}
+    explicit State(const Program& program) : src(program) {}
 
     /// Traverses the expression `expr` looking for non-literal array indexing
     /// expressions that would affect the computed address of a pointer
@@ -259,7 +259,7 @@
 
 SimplifyPointers::~SimplifyPointers() = default;
 
-Transform::ApplyResult SimplifyPointers::Apply(const Program* src, const DataMap&, DataMap&) const {
+Transform::ApplyResult SimplifyPointers::Apply(const Program& src, const DataMap&, DataMap&) const {
     return State(src).Run();
 }
 
diff --git a/src/tint/lang/wgsl/ast/transform/simplify_pointers.h b/src/tint/lang/wgsl/ast/transform/simplify_pointers.h
index df529c2..59e3a35 100644
--- a/src/tint/lang/wgsl/ast/transform/simplify_pointers.h
+++ b/src/tint/lang/wgsl/ast/transform/simplify_pointers.h
@@ -40,7 +40,7 @@
     ~SimplifyPointers() override;
 
     /// @copydoc Transform::Apply
-    ApplyResult Apply(const Program* program,
+    ApplyResult Apply(const Program& program,
                       const DataMap& inputs,
                       DataMap& outputs) const override;
 
diff --git a/src/tint/lang/wgsl/ast/transform/single_entry_point.cc b/src/tint/lang/wgsl/ast/transform/single_entry_point.cc
index 9976562..c61798d 100644
--- a/src/tint/lang/wgsl/ast/transform/single_entry_point.cc
+++ b/src/tint/lang/wgsl/ast/transform/single_entry_point.cc
@@ -33,11 +33,11 @@
 
 SingleEntryPoint::~SingleEntryPoint() = default;
 
-Transform::ApplyResult SingleEntryPoint::Apply(const Program* src,
+Transform::ApplyResult SingleEntryPoint::Apply(const Program& src,
                                                const DataMap& inputs,
                                                DataMap&) const {
     ProgramBuilder b;
-    program::CloneContext ctx{&b, src, /* auto_clone_symbols */ true};
+    program::CloneContext ctx{&b, &src, /* auto_clone_symbols */ true};
 
     auto* cfg = inputs.Get<Config>();
     if (cfg == nullptr) {
@@ -48,7 +48,7 @@
 
     // Find the target entry point.
     const Function* entry_point = nullptr;
-    for (auto* f : src->AST().Functions()) {
+    for (auto* f : src.AST().Functions()) {
         if (!f->IsEntryPoint()) {
             continue;
         }
@@ -63,12 +63,12 @@
         return resolver::Resolve(b);
     }
 
-    auto& sem = src->Sem();
+    auto& sem = src.Sem();
     auto& referenced_vars = sem.Get(entry_point)->TransitivelyReferencedGlobals();
 
     // Clone any module-scope variables, types, and functions that are statically referenced by the
     // target entry point.
-    for (auto* decl : src->AST().GlobalDeclarations()) {
+    for (auto* decl : src.AST().GlobalDeclarations()) {
         Switch(
             decl,  //
             [&](const TypeDecl* ty) {
diff --git a/src/tint/lang/wgsl/ast/transform/single_entry_point.h b/src/tint/lang/wgsl/ast/transform/single_entry_point.h
index 97a99da..4d7f493 100644
--- a/src/tint/lang/wgsl/ast/transform/single_entry_point.h
+++ b/src/tint/lang/wgsl/ast/transform/single_entry_point.h
@@ -54,7 +54,7 @@
     ~SingleEntryPoint() override;
 
     /// @copydoc Transform::Apply
-    ApplyResult Apply(const Program* program,
+    ApplyResult Apply(const Program& program,
                       const DataMap& inputs,
                       DataMap& outputs) const override;
 };
diff --git a/src/tint/lang/wgsl/ast/transform/std140.cc b/src/tint/lang/wgsl/ast/transform/std140.cc
index fd3193c..38fd81e 100644
--- a/src/tint/lang/wgsl/ast/transform/std140.cc
+++ b/src/tint/lang/wgsl/ast/transform/std140.cc
@@ -71,7 +71,7 @@
 struct Std140::State {
     /// Constructor
     /// @param program the source program
-    explicit State(const Program* program) : src(program) {}
+    explicit State(const Program& program) : src(program) {}
 
     /// Runs the transform
     /// @returns the new program or SkipTransform if the transform is not required
@@ -131,7 +131,7 @@
         };
 
         // Scan structures for members that need forking
-        for (auto* ty : src->Types()) {
+        for (auto* ty : src.Types()) {
             if (auto* str = ty->As<core::type::Struct>()) {
                 if (str->UsedAs(core::AddressSpace::kUniform)) {
                     for (auto* member : str->Members()) {
@@ -144,8 +144,8 @@
         }
 
         // Scan uniform variables that have types that need forking
-        for (auto* decl : src->AST().GlobalVariables()) {
-            auto* global = src->Sem().Get(decl);
+        for (auto* decl : src.AST().GlobalVariables()) {
+            auto* global = src.Sem().Get(decl);
             if (global->AddressSpace() == core::AddressSpace::kUniform) {
                 if (needs_fork(global->Type()->UnwrapRef())) {
                     return true;
@@ -194,15 +194,15 @@
     };
 
     /// The source program
-    const Program* const src;
+    const Program& src;
     /// The target program builder
     ProgramBuilder b;
     /// The clone context
-    program::CloneContext ctx = {&b, src, /* auto_clone_symbols */ true};
+    program::CloneContext ctx = {&b, &src, /* auto_clone_symbols */ true};
     /// Alias to the semantic info in src
-    const sem::Info& sem = src->Sem();
+    const sem::Info& sem = src.Sem();
     /// Alias to the symbols in src
-    const SymbolTable& sym = src->Symbols();
+    const SymbolTable& sym = src.Symbols();
 
     /// Map of load function signature, to the generated function
     Hashmap<LoadFnKey, Symbol, 8, LoadFnKey::Hasher> load_fns;
@@ -266,7 +266,7 @@
     /// map (via Std140Type()).
     void ForkTypes() {
         // For each module scope declaration...
-        for (auto* global : src->Sem().Module()->DependencyOrderedDeclarations()) {
+        for (auto* global : src.Sem().Module()->DependencyOrderedDeclarations()) {
             // Check to see if this is a structure used by a uniform buffer...
             auto* str = sem.Get<sem::Struct>(global);
             if (str && str->UsedAs(core::AddressSpace::kUniform)) {
@@ -316,7 +316,7 @@
                 if (fork_std140) {
                     // Clone any members that have not already been cloned.
                     for (auto& member : members) {
-                        if (member->generation_id == src->ID()) {
+                        if (member->generation_id == src.ID()) {
                             member = ctx.Clone(member);
                         }
                     }
@@ -325,7 +325,7 @@
                     auto name = b.Symbols().New(str->Name().Name() + "_std140");
                     auto* std140 = b.create<Struct>(b.Ident(name), std::move(members),
                                                     ctx.Clone(str->Declaration()->attributes));
-                    ctx.InsertAfter(src->AST().GlobalDeclarations(), global, std140);
+                    ctx.InsertAfter(src.AST().GlobalDeclarations(), global, std140);
                     std140_structs.Add(str, name);
                 }
             }
@@ -336,7 +336,7 @@
     /// type that has been forked for std140-layout.
     /// Populates the #std140_uniforms set.
     void ReplaceUniformVarTypes() {
-        for (auto* global : src->AST().GlobalVariables()) {
+        for (auto* global : src.AST().GlobalVariables()) {
             if (auto* var = global->As<Var>()) {
                 auto* v = sem.Get(var);
                 if (v->AddressSpace() == core::AddressSpace::kUniform) {
@@ -1096,7 +1096,7 @@
                     for (auto el : *swizzle) {
                         rhs += xyzw[el];
                     }
-                    auto swizzle_ty = src->Types().Find<core::type::Vector>(
+                    auto swizzle_ty = src.Types().Find<core::type::Vector>(
                         vec->type(), static_cast<uint32_t>(swizzle->Length()));
                     auto* expr = b.MemberAccessor(lhs, rhs);
                     return {expr, swizzle_ty, rhs};
@@ -1140,7 +1140,7 @@
 
 Std140::~Std140() = default;
 
-Transform::ApplyResult Std140::Apply(const Program* src, const DataMap&, DataMap&) const {
+Transform::ApplyResult Std140::Apply(const Program& src, const DataMap&, DataMap&) const {
     return State(src).Run();
 }
 
diff --git a/src/tint/lang/wgsl/ast/transform/std140.h b/src/tint/lang/wgsl/ast/transform/std140.h
index 8b60728..20e5a02 100644
--- a/src/tint/lang/wgsl/ast/transform/std140.h
+++ b/src/tint/lang/wgsl/ast/transform/std140.h
@@ -36,7 +36,7 @@
     ~Std140() override;
 
     /// @copydoc Transform::Apply
-    ApplyResult Apply(const Program* program,
+    ApplyResult Apply(const Program& program,
                       const DataMap& inputs,
                       DataMap& outputs) const override;
 
diff --git a/src/tint/lang/wgsl/ast/transform/substitute_override.cc b/src/tint/lang/wgsl/ast/transform/substitute_override.cc
index ffd3960..9f41800 100644
--- a/src/tint/lang/wgsl/ast/transform/substitute_override.cc
+++ b/src/tint/lang/wgsl/ast/transform/substitute_override.cc
@@ -17,12 +17,12 @@
 #include <functional>
 #include <utility>
 
+#include "src/tint/lang/core/builtin_fn.h"
 #include "src/tint/lang/core/fluent_types.h"
-#include "src/tint/lang/core/function.h"
 #include "src/tint/lang/wgsl/program/clone_context.h"
 #include "src/tint/lang/wgsl/program/program_builder.h"
 #include "src/tint/lang/wgsl/resolver/resolve.h"
-#include "src/tint/lang/wgsl/sem/builtin.h"
+#include "src/tint/lang/wgsl/sem/builtin_fn.h"
 #include "src/tint/lang/wgsl/sem/index_accessor_expression.h"
 #include "src/tint/lang/wgsl/sem/variable.h"
 #include "src/tint/utils/rtti/switch.h"
@@ -35,8 +35,8 @@
 namespace tint::ast::transform {
 namespace {
 
-bool ShouldRun(const Program* program) {
-    for (auto* node : program->AST().GlobalVariables()) {
+bool ShouldRun(const Program& program) {
+    for (auto* node : program.AST().GlobalVariables()) {
         if (node->Is<Override>()) {
             return true;
         }
@@ -50,11 +50,11 @@
 
 SubstituteOverride::~SubstituteOverride() = default;
 
-Transform::ApplyResult SubstituteOverride::Apply(const Program* src,
+Transform::ApplyResult SubstituteOverride::Apply(const Program& src,
                                                  const DataMap& config,
                                                  DataMap&) const {
     ProgramBuilder b;
-    program::CloneContext ctx{&b, src, /* auto_clone_symbols */ true};
+    program::CloneContext ctx{&b, &src, /* auto_clone_symbols */ true};
 
     const auto* data = config.Get<Config>();
     if (!data) {
@@ -62,7 +62,7 @@
         return resolver::Resolve(b);
     }
 
-    if (!ShouldRun(ctx.src)) {
+    if (!ShouldRun(src)) {
         return SkipTransform;
     }
 
@@ -107,11 +107,11 @@
     // If the object is not materialized, and the 'override' variable is turned to a 'const', the
     // resulting type of the index may change. See: crbug.com/tint/1697.
     ctx.ReplaceAll([&](const IndexAccessorExpression* expr) -> const IndexAccessorExpression* {
-        if (auto* sem = src->Sem().Get(expr)) {
+        if (auto* sem = src.Sem().Get(expr)) {
             if (auto* access = sem->UnwrapMaterialize()->As<sem::IndexAccessorExpression>()) {
                 if (access->Object()->UnwrapMaterialize()->Type()->HoldsAbstract() &&
                     access->Index()->Stage() == core::EvaluationStage::kOverride) {
-                    auto* obj = b.Call(core::str(core::Function::kTintMaterialize),
+                    auto* obj = b.Call(core::str(core::BuiltinFn::kTintMaterialize),
                                        ctx.Clone(expr->object));
                     return b.IndexAccessor(obj, ctx.Clone(expr->index));
                 }
diff --git a/src/tint/lang/wgsl/ast/transform/substitute_override.h b/src/tint/lang/wgsl/ast/transform/substitute_override.h
index 2108dca..e234450 100644
--- a/src/tint/lang/wgsl/ast/transform/substitute_override.h
+++ b/src/tint/lang/wgsl/ast/transform/substitute_override.h
@@ -76,7 +76,7 @@
     ~SubstituteOverride() override;
 
     /// @copydoc Transform::Apply
-    ApplyResult Apply(const Program* program,
+    ApplyResult Apply(const Program& program,
                       const DataMap& inputs,
                       DataMap& outputs) const override;
 };
diff --git a/src/tint/lang/wgsl/ast/transform/transform.cc b/src/tint/lang/wgsl/ast/transform/transform.cc
index 1e70a35..781f2f5 100644
--- a/src/tint/lang/wgsl/ast/transform/transform.cc
+++ b/src/tint/lang/wgsl/ast/transform/transform.cc
@@ -17,7 +17,7 @@
 #include <algorithm>
 #include <string>
 
-#include "src/tint/lang/core/builtin.h"
+#include "src/tint/lang/core/builtin_type.h"
 #include "src/tint/lang/core/fluent_types.h"
 #include "src/tint/lang/core/type/atomic.h"
 #include "src/tint/lang/core/type/depth_multisampled_texture.h"
@@ -41,13 +41,13 @@
 Transform::Transform() = default;
 Transform::~Transform() = default;
 
-Output Transform::Run(const Program* src, const DataMap& data /* = {} */) const {
+Output Transform::Run(const Program& src, const DataMap& data /* = {} */) const {
     Output output;
     if (auto program = Apply(src, data, output.data)) {
         output.program = std::move(program.value());
     } else {
         ProgramBuilder b;
-        program::CloneContext ctx{&b, src, /* auto_clone_symbols */ true};
+        program::CloneContext ctx{&b, &src, /* auto_clone_symbols */ true};
         ctx.Clone();
         output.program = resolver::Resolve(b);
     }
@@ -94,7 +94,7 @@
         auto el = CreateASTTypeFor(ctx, v->type());
         if (v->Packed()) {
             TINT_ASSERT(v->Width() == 3u);
-            return ctx.dst->ty(core::Builtin::kPackedVec3, el);
+            return ctx.dst->ty(core::BuiltinType::kPackedVec3, el);
         } else {
             return ctx.dst->ty.vec(el, v->Width());
         }
diff --git a/src/tint/lang/wgsl/ast/transform/transform.h b/src/tint/lang/wgsl/ast/transform/transform.h
index 8e7400c..e0c25e2 100644
--- a/src/tint/lang/wgsl/ast/transform/transform.h
+++ b/src/tint/lang/wgsl/ast/transform/transform.h
@@ -66,7 +66,7 @@
     /// @param program the source program to transform
     /// @param data optional extra transform-specific input data
     /// @returns the transformation result
-    Output Run(const Program* program, const DataMap& data = {}) const;
+    Output Run(const Program& program, const DataMap& data = {}) const;
 
     /// The return value of Apply().
     /// If SkipTransform (std::nullopt), then the transform is not needed to be run.
@@ -80,7 +80,7 @@
     /// @param inputs optional extra transform-specific input data
     /// @param outputs optional extra transform-specific output data
     /// @returns a transformed program, or std::nullopt if the transform didn't need to run.
-    virtual ApplyResult Apply(const Program* program,
+    virtual ApplyResult Apply(const Program& program,
                               const DataMap& inputs,
                               DataMap& outputs) const = 0;
 
diff --git a/src/tint/lang/wgsl/ast/transform/transform_test.cc b/src/tint/lang/wgsl/ast/transform/transform_test.cc
index 46c5f24..9efe65e 100644
--- a/src/tint/lang/wgsl/ast/transform/transform_test.cc
+++ b/src/tint/lang/wgsl/ast/transform/transform_test.cc
@@ -29,7 +29,7 @@
 
 // Inherit from Transform so we have access to protected methods
 struct CreateASTTypeForTest : public testing::Test, public Transform {
-    ApplyResult Apply(const Program*, const DataMap&, DataMap&) const override {
+    ApplyResult Apply(const Program&, const DataMap&, DataMap&) const override {
         return SkipTransform;
     }
 
diff --git a/src/tint/lang/wgsl/ast/transform/unshadow.cc b/src/tint/lang/wgsl/ast/transform/unshadow.cc
index e6655bb..4ff846d 100644
--- a/src/tint/lang/wgsl/ast/transform/unshadow.cc
+++ b/src/tint/lang/wgsl/ast/transform/unshadow.cc
@@ -34,20 +34,20 @@
 /// PIMPL state for the transform
 struct Unshadow::State {
     /// The source program
-    const Program* const src;
+    const Program& src;
     /// The target program builder
     ProgramBuilder b;
     /// The clone context
-    program::CloneContext ctx = {&b, src, /* auto_clone_symbols */ true};
+    program::CloneContext ctx = {&b, &src, /* auto_clone_symbols */ true};
 
     /// Constructor
     /// @param program the source program
-    explicit State(const Program* program) : src(program) {}
+    explicit State(const Program& program) : src(program) {}
 
     /// Runs the transform
     /// @returns the new program or SkipTransform if the transform is not required
     Transform::ApplyResult Run() {
-        auto& sem = src->Sem();
+        auto& sem = src.Sem();
 
         // Maps a variable to its new name.
         Hashmap<const sem::Variable*, Symbol, 8> renamed_to;
@@ -124,7 +124,7 @@
 
 Unshadow::~Unshadow() = default;
 
-Transform::ApplyResult Unshadow::Apply(const Program* src, const DataMap&, DataMap&) const {
+Transform::ApplyResult Unshadow::Apply(const Program& src, const DataMap&, DataMap&) const {
     return State(src).Run();
 }
 
diff --git a/src/tint/lang/wgsl/ast/transform/unshadow.h b/src/tint/lang/wgsl/ast/transform/unshadow.h
index 77640cd..119c37c 100644
--- a/src/tint/lang/wgsl/ast/transform/unshadow.h
+++ b/src/tint/lang/wgsl/ast/transform/unshadow.h
@@ -29,7 +29,7 @@
     ~Unshadow() override;
 
     /// @copydoc Transform::Apply
-    ApplyResult Apply(const Program* program,
+    ApplyResult Apply(const Program& program,
                       const DataMap& inputs,
                       DataMap& outputs) const override;
 
diff --git a/src/tint/lang/wgsl/ast/transform/vectorize_scalar_matrix_initializers.cc b/src/tint/lang/wgsl/ast/transform/vectorize_scalar_matrix_initializers.cc
index d670b91..2168218 100644
--- a/src/tint/lang/wgsl/ast/transform/vectorize_scalar_matrix_initializers.cc
+++ b/src/tint/lang/wgsl/ast/transform/vectorize_scalar_matrix_initializers.cc
@@ -31,9 +31,9 @@
 namespace tint::ast::transform {
 namespace {
 
-bool ShouldRun(const Program* program) {
-    for (auto* node : program->ASTNodes().Objects()) {
-        if (auto* call = program->Sem().Get<sem::Call>(node)) {
+bool ShouldRun(const Program& program) {
+    for (auto* node : program.ASTNodes().Objects()) {
+        if (auto* call = program.Sem().Get<sem::Call>(node)) {
             if (call->Target()->Is<sem::ValueConstructor>() &&
                 call->Type()->Is<core::type::Matrix>()) {
                 auto& args = call->Arguments();
@@ -52,7 +52,7 @@
 
 VectorizeScalarMatrixInitializers::~VectorizeScalarMatrixInitializers() = default;
 
-Transform::ApplyResult VectorizeScalarMatrixInitializers::Apply(const Program* src,
+Transform::ApplyResult VectorizeScalarMatrixInitializers::Apply(const Program& src,
                                                                 const DataMap&,
                                                                 DataMap&) const {
     if (!ShouldRun(src)) {
@@ -60,12 +60,12 @@
     }
 
     ProgramBuilder b;
-    program::CloneContext ctx{&b, src, /* auto_clone_symbols */ true};
+    program::CloneContext ctx{&b, &src, /* auto_clone_symbols */ true};
 
     std::unordered_map<const core::type::Matrix*, Symbol> scalar_inits;
 
     ctx.ReplaceAll([&](const CallExpression* expr) -> const CallExpression* {
-        auto* call = src->Sem().Get(expr)->UnwrapMaterialize()->As<sem::Call>();
+        auto* call = src.Sem().Get(expr)->UnwrapMaterialize()->As<sem::Call>();
         auto* ty_init = call->Target()->As<sem::ValueConstructor>();
         if (!ty_init) {
             return nullptr;
diff --git a/src/tint/lang/wgsl/ast/transform/vectorize_scalar_matrix_initializers.h b/src/tint/lang/wgsl/ast/transform/vectorize_scalar_matrix_initializers.h
index fc02609..4c49769 100644
--- a/src/tint/lang/wgsl/ast/transform/vectorize_scalar_matrix_initializers.h
+++ b/src/tint/lang/wgsl/ast/transform/vectorize_scalar_matrix_initializers.h
@@ -30,7 +30,7 @@
     ~VectorizeScalarMatrixInitializers() override;
 
     /// @copydoc Transform::Apply
-    ApplyResult Apply(const Program* program,
+    ApplyResult Apply(const Program& program,
                       const DataMap& inputs,
                       DataMap& outputs) const override;
 };
diff --git a/src/tint/lang/wgsl/ast/transform/vertex_pulling.cc b/src/tint/lang/wgsl/ast/transform/vertex_pulling.cc
index e91dd35..cc747fb 100644
--- a/src/tint/lang/wgsl/ast/transform/vertex_pulling.cc
+++ b/src/tint/lang/wgsl/ast/transform/vertex_pulling.cc
@@ -235,14 +235,14 @@
     /// Constructor
     /// @param program the source program
     /// @param c the VertexPulling config
-    State(const Program* program, const VertexPulling::Config& c) : src(program), cfg(c) {}
+    State(const Program& program, const VertexPulling::Config& c) : src(program), cfg(c) {}
 
     /// Runs the transform
     /// @returns the new program or SkipTransform if the transform is not required
     ApplyResult Run() {
         // Find entry point
         const Function* func = nullptr;
-        for (auto* fn : src->AST().Functions()) {
+        for (auto* fn : src.AST().Functions()) {
             if (fn->PipelineStage() == PipelineStage::kVertex) {
                 if (func != nullptr) {
                     b.Diagnostics().add_error(
@@ -284,13 +284,13 @@
     };
 
     /// The source program
-    const Program* const src;
+    const Program& src;
     /// The transform config
     VertexPulling::Config const cfg;
     /// The target program builder
     ProgramBuilder b;
     /// The clone context
-    program::CloneContext ctx = {&b, src, /* auto_clone_symbols */ true};
+    program::CloneContext ctx = {&b, &src, /* auto_clone_symbols */ true};
     std::unordered_map<uint32_t, LocationInfo> location_info;
     std::function<const Expression*()> vertex_index_expr = nullptr;
     std::function<const Expression*()> instance_index_expr = nullptr;
@@ -768,7 +768,7 @@
             LocationInfo info;
             info.expr = [this, func_var] { return b.Expr(func_var); };
 
-            auto* sem = src->Sem().Get<sem::Parameter>(param);
+            auto* sem = src.Sem().Get<sem::Parameter>(param);
             info.type = sem->Type();
 
             if (TINT_UNLIKELY(!sem->Location().has_value())) {
@@ -782,7 +782,7 @@
                 TINT_ICE() << "Invalid entry point parameter";
                 return;
             }
-            auto builtin = src->Sem().Get(builtin_attr)->Value();
+            auto builtin = src.Sem().Get(builtin_attr)->Value();
             // Check for existing vertex_index and instance_index builtins.
             if (builtin == core::BuiltinValue::kVertexIndex) {
                 vertex_index_expr = [this, param] {
@@ -824,7 +824,7 @@
                 LocationInfo info;
                 info.expr = member_expr;
 
-                auto* sem = src->Sem().Get(member);
+                auto* sem = src.Sem().Get(member);
                 info.type = sem->Type();
 
                 TINT_ASSERT(sem->Attributes().location.has_value());
@@ -836,7 +836,7 @@
                     TINT_ICE() << "Invalid entry point parameter";
                     return;
                 }
-                auto builtin = src->Sem().Get(builtin_attr)->Value();
+                auto builtin = src.Sem().Get(builtin_attr)->Value();
                 // Check for existing vertex_index and instance_index builtins.
                 if (builtin == core::BuiltinValue::kVertexIndex) {
                     vertex_index_expr = member_expr;
@@ -891,7 +891,7 @@
 
         // Process entry point parameters.
         for (auto* param : func->params) {
-            auto* sem = src->Sem().Get(param);
+            auto* sem = src.Sem().Get(param);
             if (auto* str = sem->Type()->As<sem::Struct>()) {
                 ProcessStructParameter(func, param, str->Declaration());
             } else {
@@ -946,7 +946,7 @@
 VertexPulling::VertexPulling() = default;
 VertexPulling::~VertexPulling() = default;
 
-Transform::ApplyResult VertexPulling::Apply(const Program* src,
+Transform::ApplyResult VertexPulling::Apply(const Program& src,
                                             const DataMap& inputs,
                                             DataMap&) const {
     auto cfg = cfg_;
diff --git a/src/tint/lang/wgsl/ast/transform/vertex_pulling.h b/src/tint/lang/wgsl/ast/transform/vertex_pulling.h
index d18bb0f..deaed5a 100644
--- a/src/tint/lang/wgsl/ast/transform/vertex_pulling.h
+++ b/src/tint/lang/wgsl/ast/transform/vertex_pulling.h
@@ -172,7 +172,7 @@
     ~VertexPulling() override;
 
     /// @copydoc Transform::Apply
-    ApplyResult Apply(const Program* program,
+    ApplyResult Apply(const Program& program,
                       const DataMap& inputs,
                       DataMap& outputs) const override;
 
diff --git a/src/tint/lang/wgsl/ast/transform/zero_init_workgroup_memory.cc b/src/tint/lang/wgsl/ast/transform/zero_init_workgroup_memory.cc
index a14d218..39ea897 100644
--- a/src/tint/lang/wgsl/ast/transform/zero_init_workgroup_memory.cc
+++ b/src/tint/lang/wgsl/ast/transform/zero_init_workgroup_memory.cc
@@ -39,10 +39,10 @@
 namespace tint::ast::transform {
 namespace {
 
-bool ShouldRun(const Program* program) {
-    for (auto* global : program->AST().GlobalVariables()) {
+bool ShouldRun(const Program& program) {
+    for (auto* global : program.AST().GlobalVariables()) {
         if (auto* var = global->As<Var>()) {
-            auto* v = program->Sem().Get(var);
+            auto* v = program.Sem().Get(var);
             if (v->AddressSpace() == core::AddressSpace::kWorkgroup) {
                 return true;
             }
@@ -461,7 +461,7 @@
 
 ZeroInitWorkgroupMemory::~ZeroInitWorkgroupMemory() = default;
 
-Transform::ApplyResult ZeroInitWorkgroupMemory::Apply(const Program* src,
+Transform::ApplyResult ZeroInitWorkgroupMemory::Apply(const Program& src,
                                                       const DataMap&,
                                                       DataMap&) const {
     if (!ShouldRun(src)) {
@@ -469,9 +469,9 @@
     }
 
     ProgramBuilder b;
-    program::CloneContext ctx{&b, src, /* auto_clone_symbols */ true};
+    program::CloneContext ctx{&b, &src, /* auto_clone_symbols */ true};
 
-    for (auto* fn : src->AST().Functions()) {
+    for (auto* fn : src.AST().Functions()) {
         if (fn->PipelineStage() == PipelineStage::kCompute) {
             State{ctx}.Run(fn);
         }
diff --git a/src/tint/lang/wgsl/ast/transform/zero_init_workgroup_memory.h b/src/tint/lang/wgsl/ast/transform/zero_init_workgroup_memory.h
index cb3de9a..3d12132 100644
--- a/src/tint/lang/wgsl/ast/transform/zero_init_workgroup_memory.h
+++ b/src/tint/lang/wgsl/ast/transform/zero_init_workgroup_memory.h
@@ -31,7 +31,7 @@
     ~ZeroInitWorkgroupMemory() override;
 
     /// @copydoc Transform::Apply
-    ApplyResult Apply(const Program* program,
+    ApplyResult Apply(const Program& program,
                       const DataMap& inputs,
                       DataMap& outputs) const override;
 
diff --git a/src/tint/lang/wgsl/helpers/flatten_bindings.cc b/src/tint/lang/wgsl/helpers/flatten_bindings.cc
index 7e6d6c5..3400dd9 100644
--- a/src/tint/lang/wgsl/helpers/flatten_bindings.cc
+++ b/src/tint/lang/wgsl/helpers/flatten_bindings.cc
@@ -23,7 +23,7 @@
 
 namespace tint::writer {
 
-std::optional<Program> FlattenBindings(const Program* program) {
+std::optional<Program> FlattenBindings(const Program& program) {
     // TODO(crbug.com/tint/1101): Make this more robust for multiple entry points.
     tint::ast::transform::BindingRemapper::BindingPoints binding_points;
     uint32_t next_buffer_idx = 0;
diff --git a/src/tint/lang/wgsl/helpers/flatten_bindings.h b/src/tint/lang/wgsl/helpers/flatten_bindings.h
index 99d75e0..be2ca44 100644
--- a/src/tint/lang/wgsl/helpers/flatten_bindings.h
+++ b/src/tint/lang/wgsl/helpers/flatten_bindings.h
@@ -24,7 +24,7 @@
 /// group 0 within unique binding numbers.
 /// @param program A valid program
 /// @return A new program with bindings remapped if needed
-std::optional<Program> FlattenBindings(const Program* program);
+std::optional<Program> FlattenBindings(const Program& program);
 
 }  // namespace tint::writer
 
diff --git a/src/tint/lang/wgsl/helpers/flatten_bindings_test.cc b/src/tint/lang/wgsl/helpers/flatten_bindings_test.cc
index 201e254..ed04a59 100644
--- a/src/tint/lang/wgsl/helpers/flatten_bindings_test.cc
+++ b/src/tint/lang/wgsl/helpers/flatten_bindings_test.cc
@@ -32,9 +32,9 @@
 TEST_F(FlattenBindingsTest, NoBindings) {
     ProgramBuilder b;
     Program program(resolver::Resolve(b));
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
-    auto flattened = tint::writer::FlattenBindings(&program);
+    auto flattened = tint::writer::FlattenBindings(program);
     EXPECT_FALSE(flattened);
 }
 
@@ -45,9 +45,9 @@
     b.GlobalVar("c", b.ty.i32(), core::AddressSpace::kUniform, b.Group(0_a), b.Binding(2_a));
 
     Program program(resolver::Resolve(b));
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
-    auto flattened = tint::writer::FlattenBindings(&program);
+    auto flattened = tint::writer::FlattenBindings(program);
     EXPECT_FALSE(flattened);
 }
 
@@ -59,9 +59,9 @@
     b.WrapInFunction(b.Expr("a"), b.Expr("b"), b.Expr("c"));
 
     Program program(resolver::Resolve(b));
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
-    auto flattened = tint::writer::FlattenBindings(&program);
+    auto flattened = tint::writer::FlattenBindings(program);
     EXPECT_TRUE(flattened);
 
     auto& vars = flattened->AST().GlobalVariables();
@@ -121,9 +121,9 @@
                      b.Assign(b.Phony(), "texture6"));
 
     Program program(resolver::Resolve(b));
-    ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+    ASSERT_TRUE(program.IsValid()) << program.Diagnostics();
 
-    auto flattened = tint::writer::FlattenBindings(&program);
+    auto flattened = tint::writer::FlattenBindings(program);
     EXPECT_TRUE(flattened);
 
     auto& vars = flattened->AST().GlobalVariables();
diff --git a/src/tint/lang/wgsl/helpers/ir_program_test.h b/src/tint/lang/wgsl/helpers/ir_program_test.h
index d1b96dd..63bdd7f 100644
--- a/src/tint/lang/wgsl/helpers/ir_program_test.h
+++ b/src/tint/lang/wgsl/helpers/ir_program_test.h
@@ -46,7 +46,7 @@
             return program.Diagnostics().str();
         }
 
-        auto result = wgsl::reader::ProgramToIR(&program);
+        auto result = wgsl::reader::ProgramToIR(program);
         if (result) {
             auto validated = core::ir::Validate(result.Get());
             if (!validated) {
@@ -67,7 +67,7 @@
             return program.Diagnostics().str();
         }
 
-        auto result = wgsl::reader::ProgramToIR(&program);
+        auto result = wgsl::reader::ProgramToIR(program);
         if (result) {
             auto validated = core::ir::Validate(result.Get());
             if (!validated) {
diff --git a/src/tint/lang/wgsl/inspector/inspector.cc b/src/tint/lang/wgsl/inspector/inspector.cc
index 48cc3b7..dd12745 100644
--- a/src/tint/lang/wgsl/inspector/inspector.cc
+++ b/src/tint/lang/wgsl/inspector/inspector.cc
@@ -122,7 +122,7 @@
 
 }  // namespace
 
-Inspector::Inspector(const Program* program) : program_(program) {}
+Inspector::Inspector(const Program& program) : program_(program) {}
 
 Inspector::~Inspector() = default;
 
@@ -131,7 +131,7 @@
     TINT_ASSERT(func != nullptr);
     TINT_ASSERT(func->IsEntryPoint());
 
-    auto* sem = program_->Sem().Get(func);
+    auto* sem = program_.Sem().Get(func);
 
     entry_point.name = func->name->symbol.Name();
     entry_point.remapped_name = func->name->symbol.Name();
@@ -245,7 +245,7 @@
 std::vector<EntryPoint> Inspector::GetEntryPoints() {
     std::vector<EntryPoint> result;
 
-    for (auto* func : program_->AST().Functions()) {
+    for (auto* func : program_.AST().Functions()) {
         if (!func->IsEntryPoint()) {
             continue;
         }
@@ -258,8 +258,8 @@
 
 std::map<OverrideId, Scalar> Inspector::GetOverrideDefaultValues() {
     std::map<OverrideId, Scalar> result;
-    for (auto* var : program_->AST().GlobalVariables()) {
-        auto* global = program_->Sem().Get<sem::GlobalVariable>(var);
+    for (auto* var : program_.AST().GlobalVariables()) {
+        auto* global = program_.Sem().Get<sem::GlobalVariable>(var);
         if (!global || !global->Declaration()->Is<ast::Override>()) {
             continue;
         }
@@ -298,8 +298,8 @@
 
 std::map<std::string, OverrideId> Inspector::GetNamedOverrideIds() {
     std::map<std::string, OverrideId> result;
-    for (auto* var : program_->AST().GlobalVariables()) {
-        auto* global = program_->Sem().Get<sem::GlobalVariable>(var);
+    for (auto* var : program_.AST().GlobalVariables()) {
+        auto* global = program_.Sem().Get<sem::GlobalVariable>(var);
         if (global && global->Declaration()->Is<ast::Override>()) {
             auto name = var->name->symbol.Name();
             result[name] = global->OverrideId();
@@ -342,7 +342,7 @@
 
     std::vector<ResourceBinding> result;
 
-    auto* func_sem = program_->Sem().Get(func);
+    auto* func_sem = program_.Sem().Get(func);
     for (auto& ruv : func_sem->TransitivelyReferencedUniformVariables()) {
         auto* var = ruv.first;
         auto binding_info = ruv.second;
@@ -385,7 +385,7 @@
 
     std::vector<ResourceBinding> result;
 
-    auto* func_sem = program_->Sem().Get(func);
+    auto* func_sem = program_.Sem().Get(func);
     for (auto& rs : func_sem->TransitivelyReferencedSamplerVariables()) {
         auto binding_info = rs.second;
 
@@ -409,7 +409,7 @@
 
     std::vector<ResourceBinding> result;
 
-    auto* func_sem = program_->Sem().Get(func);
+    auto* func_sem = program_.Sem().Get(func);
     for (auto& rcs : func_sem->TransitivelyReferencedComparisonSamplerVariables()) {
         auto binding_info = rcs.second;
 
@@ -449,7 +449,7 @@
     }
 
     std::vector<ResourceBinding> result;
-    auto* func_sem = program_->Sem().Get(func);
+    auto* func_sem = program_.Sem().Get(func);
     for (auto& ref : func_sem->TransitivelyReferencedVariablesOfType(texture_type)) {
         auto* var = ref.first;
         auto binding_info = ref.second;
@@ -509,7 +509,7 @@
     if (!func) {
         return {};
     }
-    auto* func_sem = program_->Sem().Get(func);
+    auto* func_sem = program_.Sem().Get(func);
 
     std::vector<SamplerTexturePair> new_pairs;
     for (auto pair : func_sem->TextureSamplerPairs()) {
@@ -524,7 +524,7 @@
 }
 
 std::vector<std::string> Inspector::GetUsedExtensionNames() {
-    auto& extensions = program_->Sem().Module()->Extensions();
+    auto& extensions = program_.Sem().Module()->Extensions();
     std::vector<std::string> out;
     out.reserve(extensions.Length());
     for (auto ext : extensions) {
@@ -537,7 +537,7 @@
     std::vector<std::pair<std::string, Source>> result;
 
     // Ast nodes for enable directive are stored within global declarations list
-    auto global_decls = program_->AST().GlobalDeclarations();
+    auto global_decls = program_.AST().GlobalDeclarations();
     for (auto* node : global_decls) {
         if (auto* enable = node->As<ast::Enable>()) {
             for (auto* ext : enable->extensions) {
@@ -550,7 +550,7 @@
 }
 
 const ast::Function* Inspector::FindEntryPointByName(const std::string& name) {
-    auto* func = program_->AST().Functions().Find(program_->Symbols().Get(name));
+    auto* func = program_.AST().Functions().Find(program_.Symbols().Get(name));
     if (!func) {
         diagnostics_.add_error(diag::System::Inspector, name + " was not found!");
         return nullptr;
@@ -623,7 +623,7 @@
     if (!builtin_declaration) {
         return false;
     }
-    return program_->Sem().Get(builtin_declaration)->Value() == builtin;
+    return program_.Sem().Get(builtin_declaration)->Value() == builtin;
 }
 
 std::vector<ResourceBinding> Inspector::GetStorageBufferResourceBindingsImpl(
@@ -634,7 +634,7 @@
         return {};
     }
 
-    auto* func_sem = program_->Sem().Get(func);
+    auto* func_sem = program_.Sem().Get(func);
     std::vector<ResourceBinding> result;
     for (auto& rsv : func_sem->TransitivelyReferencedStorageBufferVariables()) {
         auto* var = rsv.first;
@@ -673,7 +673,7 @@
     }
 
     std::vector<ResourceBinding> result;
-    auto* func_sem = program_->Sem().Get(func);
+    auto* func_sem = program_.Sem().Get(func);
     auto referenced_variables = multisampled_only
                                     ? func_sem->TransitivelyReferencedMultisampledTextureVariables()
                                     : func_sem->TransitivelyReferencedSampledTextureVariables();
@@ -712,7 +712,7 @@
         return {};
     }
 
-    auto* func_sem = program_->Sem().Get(func);
+    auto* func_sem = program_.Sem().Get(func);
     std::vector<ResourceBinding> result;
     for (auto& ref :
          func_sem->TransitivelyReferencedVariablesOfType<core::type::StorageTexture>()) {
@@ -761,9 +761,9 @@
     sampler_targets_ =
         std::make_unique<std::unordered_map<std::string, UniqueVector<SamplerTexturePair, 4>>>();
 
-    auto& sem = program_->Sem();
+    auto& sem = program_.Sem();
 
-    for (auto* node : program_->ASTNodes().Objects()) {
+    for (auto* node : program_.ASTNodes().Objects()) {
         auto* c = node->As<ast::CallExpression>();
         if (!c) {
             continue;
@@ -774,7 +774,7 @@
             continue;
         }
 
-        auto* i = call->Target()->As<sem::Builtin>();
+        auto* i = call->Target()->As<sem::BuiltinFn>();
         if (!i) {
             continue;
         }
@@ -832,7 +832,7 @@
         return {InterpolationType::kPerspective, InterpolationSampling::kCenter};
     }
 
-    auto& sem = program_->Sem();
+    auto& sem = program_.Sem();
 
     auto ast_interpolation_type =
         sem.Get<sem::BuiltinEnumExpression<core::InterpolationType>>(interpolation_attribute->type)
@@ -886,7 +886,7 @@
 
 uint32_t Inspector::ComputeWorkgroupStorageSize(const ast::Function* func) const {
     uint32_t total_size = 0;
-    auto* func_sem = program_->Sem().Get(func);
+    auto* func_sem = program_.Sem().Get(func);
     for (const sem::Variable* var : func_sem->TransitivelyReferencedGlobals()) {
         if (var->AddressSpace() == core::AddressSpace::kWorkgroup) {
             auto* ty = var->Type()->UnwrapRef();
@@ -906,7 +906,7 @@
 
 std::vector<PixelLocalMemberType> Inspector::ComputePixelLocalMemberTypes(
     const ast::Function* func) const {
-    auto* func_sem = program_->Sem().Get(func);
+    auto* func_sem = program_.Sem().Get(func);
     for (const sem::Variable* var : func_sem->TransitivelyReferencedGlobals()) {
         if (var->AddressSpace() != core::AddressSpace::kPixelLocal) {
             continue;
@@ -937,12 +937,12 @@
 
 template <size_t N, typename F>
 void Inspector::GetOriginatingResources(std::array<const ast::Expression*, N> exprs, F&& callback) {
-    if (TINT_UNLIKELY(!program_->IsValid())) {
+    if (TINT_UNLIKELY(!program_.IsValid())) {
         TINT_ICE() << "attempting to get originating resources in invalid program";
         return;
     }
 
-    auto& sem = program_->Sem();
+    auto& sem = program_.Sem();
 
     std::array<const sem::GlobalVariable*, N> globals{};
     std::array<const sem::Parameter*, N> parameters{};
diff --git a/src/tint/lang/wgsl/inspector/inspector.h b/src/tint/lang/wgsl/inspector/inspector.h
index e1f6d6e..e4b78e2 100644
--- a/src/tint/lang/wgsl/inspector/inspector.h
+++ b/src/tint/lang/wgsl/inspector/inspector.h
@@ -43,7 +43,7 @@
   public:
     /// Constructor
     /// @param program Shader program to extract information from.
-    explicit Inspector(const Program* program);
+    explicit Inspector(const Program& program);
 
     /// Destructor
     ~Inspector();
@@ -144,7 +144,7 @@
     std::vector<std::pair<std::string, Source>> GetEnableDirectives();
 
   private:
-    const Program* program_;
+    const Program& program_;
     diag::List diagnostics_;
     std::unique_ptr<std::unordered_map<std::string, UniqueVector<SamplerTexturePair, 4>>>
         sampler_targets_;
diff --git a/src/tint/lang/wgsl/inspector/inspector_builder_test.cc b/src/tint/lang/wgsl/inspector/inspector_builder_test.cc
index 6e960a4..b93c9eb 100644
--- a/src/tint/lang/wgsl/inspector/inspector_builder_test.cc
+++ b/src/tint/lang/wgsl/inspector/inspector_builder_test.cc
@@ -356,8 +356,10 @@
         return *inspector_;
     }
     program_ = std::make_unique<Program>(resolver::Resolve(*this));
-    [&] { ASSERT_TRUE(program_->IsValid()) << program_->Diagnostics().str(); }();
-    inspector_ = std::make_unique<Inspector>(program_.get());
+    if (!program_->IsValid()) {
+        ADD_FAILURE() << program_->Diagnostics();
+    }
+    inspector_ = std::make_unique<Inspector>(*program_);
     return *inspector_;
 }
 
diff --git a/src/tint/lang/wgsl/inspector/inspector_runner_test.cc b/src/tint/lang/wgsl/inspector/inspector_runner_test.cc
index 8993320..b5cbe9a 100644
--- a/src/tint/lang/wgsl/inspector/inspector_runner_test.cc
+++ b/src/tint/lang/wgsl/inspector/inspector_runner_test.cc
@@ -27,8 +27,10 @@
 
     file_ = std::make_unique<Source::File>("test", shader);
     program_ = std::make_unique<Program>(wgsl::reader::Parse(file_.get()));
-    [&] { ASSERT_TRUE(program_->IsValid()) << program_->Diagnostics().str(); }();
-    inspector_ = std::make_unique<Inspector>(program_.get());
+    if (!program_->IsValid()) {
+        ADD_FAILURE() << program_->Diagnostics();
+    }
+    inspector_ = std::make_unique<Inspector>(*program_);
     return *inspector_;
 }
 
diff --git a/src/tint/lang/wgsl/ir_roundtrip_test.cc b/src/tint/lang/wgsl/ir_roundtrip_test.cc
index 8f08426..bbca278 100644
--- a/src/tint/lang/wgsl/ir_roundtrip_test.cc
+++ b/src/tint/lang/wgsl/ir_roundtrip_test.cc
@@ -32,9 +32,9 @@
         auto input = tint::TrimSpace(input_wgsl);
         Source::File file("test.wgsl", std::string(input));
         auto input_program = wgsl::reader::Parse(&file);
-        ASSERT_TRUE(input_program.IsValid()) << input_program.Diagnostics().str();
+        ASSERT_TRUE(input_program.IsValid()) << input_program.Diagnostics();
 
-        auto ir_module = wgsl::reader::ProgramToIR(&input_program);
+        auto ir_module = wgsl::reader::ProgramToIR(input_program);
         ASSERT_TRUE(ir_module) << (ir_module ? "" : ir_module.Failure());
 
         tint::core::ir::Disassembler d{ir_module.Get()};
@@ -42,16 +42,16 @@
 
         auto output_program = wgsl::writer::IRToProgram(ir_module.Get());
         if (!output_program.IsValid()) {
-            FAIL() << output_program.Diagnostics().str() << std::endl  //
-                   << "IR:" << std::endl                               //
-                   << disassembly << std::endl                         //
-                   << "AST:" << std::endl                              //
-                   << Program::printer(&output_program) << std::endl;
+            FAIL() << output_program.Diagnostics() << std::endl  //
+                   << "IR:" << std::endl                         //
+                   << disassembly << std::endl                   //
+                   << "AST:" << std::endl                        //
+                   << Program::printer(output_program) << std::endl;
         }
 
-        ASSERT_TRUE(output_program.IsValid()) << output_program.Diagnostics().str();
+        ASSERT_TRUE(output_program.IsValid()) << output_program.Diagnostics();
 
-        auto output = wgsl::writer::Generate(&output_program, {});
+        auto output = wgsl::writer::Generate(output_program, {});
         ASSERT_TRUE(output) << output.Failure();
 
         auto expected = expected_wgsl.empty() ? input : tint::TrimSpace(expected_wgsl);
diff --git a/src/tint/lang/wgsl/program/program.cc b/src/tint/lang/wgsl/program/program.cc
index 5c058c9..cb1ce95 100644
--- a/src/tint/lang/wgsl/program/program.cc
+++ b/src/tint/lang/wgsl/program/program.cc
@@ -24,7 +24,7 @@
 namespace tint {
 namespace {
 
-std::string DefaultPrinter(const Program*) {
+std::string DefaultPrinter(const Program&) {
     return "<no program printer assigned>";
 }
 
diff --git a/src/tint/lang/wgsl/program/program.h b/src/tint/lang/wgsl/program/program.h
index a367412..2674f58 100644
--- a/src/tint/lang/wgsl/program/program.h
+++ b/src/tint/lang/wgsl/program/program.h
@@ -152,7 +152,7 @@
     const core::type::Type* TypeOf(const ast::TypeDecl* type_decl) const;
 
     /// A function that can be used to print a program
-    using Printer = std::string (*)(const Program*);
+    using Printer = std::string (*)(const Program&);
 
     /// The Program printer used for testing and debugging.
     static Printer printer;
@@ -178,8 +178,8 @@
 
 /// @param program the Program
 /// @returns the GenerationID of the Program
-inline GenerationID GenerationIDOf(const Program* program) {
-    return program->ID();
+inline GenerationID GenerationIDOf(const Program& program) {
+    return program.ID();
 }
 
 }  // namespace tint
diff --git a/src/tint/lang/wgsl/program/program_builder.cc b/src/tint/lang/wgsl/program/program_builder.cc
index ff7b469..53cd098 100644
--- a/src/tint/lang/wgsl/program/program_builder.cc
+++ b/src/tint/lang/wgsl/program/program_builder.cc
@@ -46,16 +46,16 @@
     return *this;
 }
 
-ProgramBuilder ProgramBuilder::Wrap(const Program* program) {
+ProgramBuilder ProgramBuilder::Wrap(const Program& program) {
     ProgramBuilder builder;
-    builder.id_ = program->ID();
-    builder.last_ast_node_id_ = program->HighestASTNodeID();
-    builder.constants = core::constant::Manager::Wrap(program->Constants());
+    builder.id_ = program.ID();
+    builder.last_ast_node_id_ = program.HighestASTNodeID();
+    builder.constants = core::constant::Manager::Wrap(program.Constants());
     builder.ast_ =
-        builder.create<ast::Module>(program->AST().source, program->AST().GlobalDeclarations());
-    builder.sem_ = sem::Info::Wrap(program->Sem());
-    builder.symbols_.Wrap(program->Symbols());
-    builder.diagnostics_ = program->Diagnostics();
+        builder.create<ast::Module>(program.AST().source, program.AST().GlobalDeclarations());
+    builder.sem_ = sem::Info::Wrap(program.Sem());
+    builder.symbols_.Wrap(program.Symbols());
+    builder.diagnostics_ = program.Diagnostics();
     return builder;
 }
 
diff --git a/src/tint/lang/wgsl/program/program_builder.h b/src/tint/lang/wgsl/program/program_builder.h
index 81fdfc7..3b17967 100644
--- a/src/tint/lang/wgsl/program/program_builder.h
+++ b/src/tint/lang/wgsl/program/program_builder.h
@@ -90,7 +90,7 @@
     /// function. See crbug.com/tint/460.
     /// @param program the immutable Program to wrap
     /// @return the ProgramBuilder that wraps `program`
-    static ProgramBuilder Wrap(const Program* program);
+    static ProgramBuilder Wrap(const Program& program);
 
     /// @returns a reference to the program's types
     core::type::Manager& Types() {
diff --git a/src/tint/lang/wgsl/program/program_builder_test.cc b/src/tint/lang/wgsl/program/program_builder_test.cc
index b887ff6..c1c3099 100644
--- a/src/tint/lang/wgsl/program/program_builder_test.cc
+++ b/src/tint/lang/wgsl/program/program_builder_test.cc
@@ -42,7 +42,7 @@
     ASSERT_TRUE(inner.Symbols().Get("a").IsValid());
     ASSERT_FALSE(inner.Symbols().Get("b").IsValid());
 
-    ProgramBuilder outer = ProgramBuilder::Wrap(&inner);
+    ProgramBuilder outer = ProgramBuilder::Wrap(inner);
 
     ASSERT_EQ(inner.AST().Functions().Length(), 1u);
     ASSERT_EQ(outer.AST().Functions().Length(), 1u);
diff --git a/src/tint/lang/wgsl/reader/program_to_ir/program_to_ir.cc b/src/tint/lang/wgsl/reader/program_to_ir/program_to_ir.cc
index 4850db3..fa464bb 100644
--- a/src/tint/lang/wgsl/reader/program_to_ir/program_to_ir.cc
+++ b/src/tint/lang/wgsl/reader/program_to_ir/program_to_ir.cc
@@ -85,7 +85,7 @@
 #include "src/tint/lang/wgsl/ast/variable_decl_statement.h"
 #include "src/tint/lang/wgsl/ast/while_statement.h"
 #include "src/tint/lang/wgsl/program/program.h"
-#include "src/tint/lang/wgsl/sem/builtin.h"
+#include "src/tint/lang/wgsl/sem/builtin_fn.h"
 #include "src/tint/lang/wgsl/sem/call.h"
 #include "src/tint/lang/wgsl/sem/function.h"
 #include "src/tint/lang/wgsl/sem/index_accessor_expression.h"
@@ -118,7 +118,7 @@
   public:
     /// Constructor
     /// @param program the program to convert to IR
-    explicit Impl(const Program* program) : program_(program) {}
+    explicit Impl(const Program& program) : program_(program) {}
 
     /// Builds an IR module from the program passed to the constructor.
     /// @return the IR module or an error.
@@ -128,7 +128,7 @@
     enum class ControlFlags { kNone, kExcludeSwitch };
 
     // The input Program
-    const Program* program_ = nullptr;
+    const Program& program_;
 
     /// The IR module being built
     core::ir::Module mod;
@@ -139,7 +139,7 @@
     // The clone context used to clone data from #program_
     core::constant::CloneContext clone_ctx_{
         /* type_ctx */ core::type::CloneContext{
-            /* src */ {&program_->Symbols()},
+            /* src */ {&program_.Symbols()},
             /* dst */ {&builder_.ir.symbols, &builder_.ir.Types()},
         },
         /* dst */ {builder_.ir.constant_values},
@@ -216,7 +216,7 @@
     }
 
     ResultType EmitModule() {
-        auto* sem = program_->Sem().Module();
+        auto* sem = program_.Sem().Module();
 
         for (auto* decl : sem->DependencyOrderedDeclarations()) {
             tint::Switch(
@@ -258,7 +258,7 @@
     }
 
     core::Interpolation ExtractInterpolation(const ast::InterpolateAttribute* interp) {
-        auto type = program_->Sem()
+        auto type = program_.Sem()
                         .Get(interp->type)
                         ->As<sem::BuiltinEnumExpression<core::InterpolationType>>();
         core::InterpolationType interpolation_type = type->Value();
@@ -266,7 +266,7 @@
         core::InterpolationSampling interpolation_sampling =
             core::InterpolationSampling::kUndefined;
         if (interp->sampling) {
-            auto sampling = program_->Sem()
+            auto sampling = program_.Sem()
                                 .Get(interp->sampling)
                                 ->As<sem::BuiltinEnumExpression<core::InterpolationSampling>>();
             interpolation_sampling = sampling->Value();
@@ -279,7 +279,7 @@
         // The flow stack should have been emptied when the previous function finished building.
         TINT_ASSERT(control_stack_.IsEmpty());
 
-        const auto* sem = program_->Sem().Get(ast_func);
+        const auto* sem = program_.Sem().Get(ast_func);
 
         auto* ir_func = builder_.Function(ast_func->name->symbol.NameView(),
                                           sem->ReturnType()->Clone(clone_ctx_.type_ctx));
@@ -321,7 +321,7 @@
                     [&](const ast::InvariantAttribute*) { ir_func->SetReturnInvariant(true); },
                     [&](const ast::BuiltinAttribute* b) {
                         if (auto* ident_sem =
-                                program_->Sem()
+                                program_.Sem()
                                     .Get(b)
                                     ->As<sem::BuiltinEnumExpression<core::BuiltinValue>>()) {
                             switch (ident_sem->Value()) {
@@ -358,7 +358,7 @@
 
         Vector<core::ir::FunctionParam*, 1> params;
         for (auto* p : ast_func->params) {
-            const auto* param_sem = program_->Sem().Get(p)->As<sem::Parameter>();
+            const auto* param_sem = program_.Sem().Get(p)->As<sem::Parameter>();
             auto* ty = param_sem->Type()->Clone(clone_ctx_.type_ctx);
             auto* param = builder_.FunctionParam(p->name->symbol.NameView(), ty);
 
@@ -374,7 +374,7 @@
                     [&](const ast::InvariantAttribute*) { param->SetInvariant(true); },
                     [&](const ast::BuiltinAttribute* b) {
                         if (auto* ident_sem =
-                                program_->Sem()
+                                program_.Sem()
                                     .Get(b)
                                     ->As<sem::BuiltinEnumExpression<core::BuiltinValue>>()) {
                             switch (ident_sem->Value()) {
@@ -459,7 +459,7 @@
 
         // Add a terminator if one was not already created.
         if (NeedTerminator()) {
-            if (!program_->Sem().Get(ast_func->body)->Behaviors().Contains(sem::Behavior::kNext)) {
+            if (!program_.Sem().Get(ast_func->body)->Behaviors().Contains(sem::Behavior::kNext)) {
                 SetTerminator(builder_.Unreachable());
             } else {
                 SetTerminator(builder_.Return(current_function_));
@@ -475,7 +475,7 @@
         for (auto* s : stmts) {
             EmitStatement(s);
 
-            if (auto* sem = program_->Sem().Get(s);
+            if (auto* sem = program_.Sem().Get(s);
                 sem && !sem->Behaviors().Contains(sem::Behavior::kNext)) {
                 break;  // Unreachable statement.
             }
@@ -539,7 +539,7 @@
     void EmitIncrementDecrement(const ast::IncrementDecrementStatement* stmt) {
         auto lhs = EmitExpression(stmt->lhs);
 
-        auto* one = program_->TypeOf(stmt->lhs)->UnwrapRef()->is_signed_integer_scalar()
+        auto* one = program_.TypeOf(stmt->lhs)->UnwrapRef()->is_signed_integer_scalar()
                         ? builder_.Constant(1_i)
                         : builder_.Constant(1_u);
 
@@ -757,7 +757,7 @@
 
         ControlStackScope scope(this, switch_inst);
 
-        const auto* sem = program_->Sem().Get(stmt);
+        const auto* sem = program_.Sem().Get(stmt);
         for (const auto* c : sem->Cases()) {
             Vector<core::ir::Switch::CaseSelector, 4> selectors;
             for (const auto* selector : c->Selectors()) {
@@ -863,7 +863,7 @@
 
             void Bind(const ast::Expression* expr, core::ir::Value* value) {
                 // If this expression maps to sem::Load, insert a load instruction to get the result
-                if (impl.program_->Sem().Get<sem::Load>(expr)) {
+                if (impl.program_.Sem().Get<sem::Load>(expr)) {
                     auto* load = impl.builder_.Load(value);
                     impl.current_block_->Append(load);
                     value = load->Result();
@@ -873,7 +873,7 @@
 
             void Bind(const ast::Expression* expr, const VectorRefElementAccess& access) {
                 // If this expression maps to sem::Load, insert a load instruction to get the result
-                if (impl.program_->Sem().Get<sem::Load>(expr)) {
+                if (impl.program_.Sem().Get<sem::Load>(expr)) {
                     auto* load = impl.builder_.LoadVectorElement(access.vector, access.index);
                     impl.current_block_->Append(load);
                     bindings_.Add(expr, load->Result());
@@ -907,7 +907,7 @@
             void PopBlock() { impl.current_block_ = blocks.Pop(); }
 
             core::ir::Value* EmitConstant(const ast::Expression* expr) {
-                if (auto* sem = impl.program_->Sem().GetVal(expr)) {
+                if (auto* sem = impl.program_.Sem().GetVal(expr)) {
                     if (auto* v = sem->ConstantValue()) {
                         if (auto* cv = v->Clone(impl.clone_ctx_)) {
                             auto* val = impl.builder_.Constant(cv);
@@ -931,7 +931,7 @@
                     return;
                 }
 
-                auto* sem = impl.program_->Sem().Get(expr)->Unwrap();
+                auto* sem = impl.program_.Sem().Get(expr)->Unwrap();
 
                 // The access result type should match the source result type. If the source is a
                 // pointer, we generate a pointer.
@@ -1004,7 +1004,7 @@
             }
 
             void EmitBinary(const ast::BinaryExpression* b) {
-                auto* b_sem = impl.program_->Sem().Get(b);
+                auto* b_sem = impl.program_.Sem().Get(b);
                 auto* ty = b_sem->Type()->Clone(impl.clone_ctx_.type_ctx);
                 auto lhs = GetValue(b->lhs);
                 if (!lhs) {
@@ -1027,7 +1027,7 @@
                 if (!val) {
                     return;
                 }
-                auto* sem = impl.program_->Sem().Get(expr);
+                auto* sem = impl.program_.Sem().Get(expr);
                 auto* ty = sem->Type()->Clone(impl.clone_ctx_.type_ctx);
                 core::ir::Instruction* inst = nullptr;
                 switch (expr->op) {
@@ -1056,7 +1056,7 @@
                 if (!val) {
                     return;
                 }
-                auto* sem = impl.program_->Sem().Get(b);
+                auto* sem = impl.program_.Sem().Get(b);
                 auto* ty = sem->Type()->Clone(impl.clone_ctx_.type_ctx);
                 auto* inst = impl.builder_.Bitcast(ty, val);
                 impl.current_block_->Append(inst);
@@ -1065,7 +1065,7 @@
 
             void EmitCall(const ast::CallExpression* expr) {
                 // If this is a materialized semantic node, just use the constant value.
-                if (auto* mat = impl.program_->Sem().Get(expr)) {
+                if (auto* mat = impl.program_.Sem().Get(expr)) {
                     if (mat->ConstantValue()) {
                         auto* cv = mat->ConstantValue()->Clone(impl.clone_ctx_);
                         if (!cv) {
@@ -1088,7 +1088,7 @@
                     }
                     args.Push(value);
                 }
-                auto* sem = impl.program_->Sem().Get<sem::Call>(expr);
+                auto* sem = impl.program_.Sem().Get<sem::Call>(expr);
                 if (!sem) {
                     impl.add_error(expr->source, "failed to get semantic information for call " +
                                                      std::string(expr->TypeInfo().name));
@@ -1097,8 +1097,8 @@
                 auto* ty = sem->Target()->ReturnType()->Clone(impl.clone_ctx_.type_ctx);
                 core::ir::Instruction* inst = nullptr;
                 // If this is a builtin function, emit the specific builtin value
-                if (auto* b = sem->Target()->As<sem::Builtin>()) {
-                    inst = impl.builder_.Call(ty, b->Type(), args);
+                if (auto* b = sem->Target()->As<sem::BuiltinFn>()) {
+                    inst = impl.builder_.Call(ty, b->Fn(), args);
                 } else if (sem->Target()->As<sem::ValueConstructor>()) {
                     inst = impl.builder_.Construct(ty, std::move(args));
                 } else if (sem->Target()->Is<sem::ValueConversion>()) {
@@ -1131,7 +1131,7 @@
             }
 
             void EmitLiteral(const ast::LiteralExpression* lit) {
-                auto* sem = impl.program_->Sem().Get(lit);
+                auto* sem = impl.program_.Sem().Get(lit);
                 if (!sem) {
                     impl.add_error(lit->source, "failed to get semantic information for node " +
                                                     std::string(lit->TypeInfo().name));
@@ -1150,7 +1150,7 @@
             std::optional<VectorRefElementAccess> AsVectorRefElementAccess(
                 const ast::Expression* expr) {
                 return AsVectorRefElementAccess(
-                    impl.program_->Sem().Get<sem::ValueExpression>(expr)->UnwrapLoad());
+                    impl.program_.Sem().Get<sem::ValueExpression>(expr)->UnwrapLoad());
             }
 
             std::optional<VectorRefElementAccess> AsVectorRefElementAccess(
@@ -1294,7 +1294,7 @@
     void EmitCall(const ast::CallStatement* stmt) { (void)EmitValueExpression(stmt->expr); }
 
     void EmitVariable(const ast::Variable* var) {
-        auto* sem = program_->Sem().Get(var);
+        auto* sem = program_.Sem().Get(var);
 
         return tint::Switch(  //
             var,
@@ -1417,8 +1417,8 @@
 
 }  // namespace
 
-tint::Result<core::ir::Module, std::string> ProgramToIR(const Program* program) {
-    if (!program->IsValid()) {
+tint::Result<core::ir::Module, std::string> ProgramToIR(const Program& program) {
+    if (!program.IsValid()) {
         return std::string("input program is not valid");
     }
 
diff --git a/src/tint/lang/wgsl/reader/program_to_ir/program_to_ir.h b/src/tint/lang/wgsl/reader/program_to_ir/program_to_ir.h
index c09549b..d51f338 100644
--- a/src/tint/lang/wgsl/reader/program_to_ir/program_to_ir.h
+++ b/src/tint/lang/wgsl/reader/program_to_ir/program_to_ir.h
@@ -35,7 +35,7 @@
 /// @note this assumes the `program.IsValid()`, and has had const-eval done so
 /// any abstract values have been calculated and converted into the relevant
 /// concrete types.
-tint::Result<core::ir::Module, std::string> ProgramToIR(const Program* program);
+tint::Result<core::ir::Module, std::string> ProgramToIR(const Program& program);
 
 }  // namespace tint::wgsl::reader
 
diff --git a/src/tint/lang/wgsl/resolver/builtin_structs_test.cc b/src/tint/lang/wgsl/resolver/builtin_structs_test.cc
index 409b238..30b4642 100644
--- a/src/tint/lang/wgsl/resolver/builtin_structs_test.cc
+++ b/src/tint/lang/wgsl/resolver/builtin_structs_test.cc
@@ -25,7 +25,7 @@
 ////////////////////////////////////////////////////////////////////////////////
 // access
 ////////////////////////////////////////////////////////////////////////////////
-using ResolverBuiltinStructs = ResolverTestWithParam<core::Builtin>;
+using ResolverBuiltinStructs = ResolverTestWithParam<core::BuiltinType>;
 
 TEST_P(ResolverBuiltinStructs, Resolve) {
     Enable(wgsl::Extension::kF16);
@@ -42,32 +42,32 @@
 
 INSTANTIATE_TEST_SUITE_P(,
                          ResolverBuiltinStructs,
-                         testing::Values(core::Builtin::kAtomicCompareExchangeResultI32,
-                                         core::Builtin::kAtomicCompareExchangeResultU32,
-                                         core::Builtin::kFrexpResultAbstract,
-                                         core::Builtin::kFrexpResultF16,
-                                         core::Builtin::kFrexpResultF32,
-                                         core::Builtin::kFrexpResultVec2Abstract,
-                                         core::Builtin::kFrexpResultVec2F16,
-                                         core::Builtin::kFrexpResultVec2F32,
-                                         core::Builtin::kFrexpResultVec3Abstract,
-                                         core::Builtin::kFrexpResultVec3F16,
-                                         core::Builtin::kFrexpResultVec3F32,
-                                         core::Builtin::kFrexpResultVec4Abstract,
-                                         core::Builtin::kFrexpResultVec4F16,
-                                         core::Builtin::kFrexpResultVec4F32,
-                                         core::Builtin::kModfResultAbstract,
-                                         core::Builtin::kModfResultF16,
-                                         core::Builtin::kModfResultF32,
-                                         core::Builtin::kModfResultVec2Abstract,
-                                         core::Builtin::kModfResultVec2F16,
-                                         core::Builtin::kModfResultVec2F32,
-                                         core::Builtin::kModfResultVec3Abstract,
-                                         core::Builtin::kModfResultVec3F16,
-                                         core::Builtin::kModfResultVec3F32,
-                                         core::Builtin::kModfResultVec4Abstract,
-                                         core::Builtin::kModfResultVec4F16,
-                                         core::Builtin::kModfResultVec4F32));
+                         testing::Values(core::BuiltinType::kAtomicCompareExchangeResultI32,
+                                         core::BuiltinType::kAtomicCompareExchangeResultU32,
+                                         core::BuiltinType::kFrexpResultAbstract,
+                                         core::BuiltinType::kFrexpResultF16,
+                                         core::BuiltinType::kFrexpResultF32,
+                                         core::BuiltinType::kFrexpResultVec2Abstract,
+                                         core::BuiltinType::kFrexpResultVec2F16,
+                                         core::BuiltinType::kFrexpResultVec2F32,
+                                         core::BuiltinType::kFrexpResultVec3Abstract,
+                                         core::BuiltinType::kFrexpResultVec3F16,
+                                         core::BuiltinType::kFrexpResultVec3F32,
+                                         core::BuiltinType::kFrexpResultVec4Abstract,
+                                         core::BuiltinType::kFrexpResultVec4F16,
+                                         core::BuiltinType::kFrexpResultVec4F32,
+                                         core::BuiltinType::kModfResultAbstract,
+                                         core::BuiltinType::kModfResultF16,
+                                         core::BuiltinType::kModfResultF32,
+                                         core::BuiltinType::kModfResultVec2Abstract,
+                                         core::BuiltinType::kModfResultVec2F16,
+                                         core::BuiltinType::kModfResultVec2F32,
+                                         core::BuiltinType::kModfResultVec3Abstract,
+                                         core::BuiltinType::kModfResultVec3F16,
+                                         core::BuiltinType::kModfResultVec3F32,
+                                         core::BuiltinType::kModfResultVec4Abstract,
+                                         core::BuiltinType::kModfResultVec4F16,
+                                         core::BuiltinType::kModfResultVec4F32));
 
 }  // namespace
 }  // namespace tint::resolver
diff --git a/src/tint/lang/wgsl/resolver/builtin_test.cc b/src/tint/lang/wgsl/resolver/builtin_test.cc
index 1ec3b5f..f4175a8 100644
--- a/src/tint/lang/wgsl/resolver/builtin_test.cc
+++ b/src/tint/lang/wgsl/resolver/builtin_test.cc
@@ -55,7 +55,7 @@
 
 struct BuiltinData {
     const char* name;
-    core::Function builtin;
+    core::BuiltinFn builtin;
 };
 
 inline std::ostream& operator<<(std::ostream& out, BuiltinData data) {
@@ -78,9 +78,9 @@
     // let a = select(1_i, 2_i, true);
     // let b = select(3_i, 4_i, false);
     // let c = select(5_u, 6_u, true);
-    auto* select_a = Call(core::Function::kSelect, 1_i, 2_i, true);
-    auto* select_b = Call(core::Function::kSelect, 3_i, 4_i, false);
-    auto* select_c = Call(core::Function::kSelect, 5_u, 6_u, true);
+    auto* select_a = Call(core::BuiltinFn::kSelect, 1_i, 2_i, true);
+    auto* select_b = Call(core::BuiltinFn::kSelect, 3_i, 4_i, false);
+    auto* select_c = Call(core::BuiltinFn::kSelect, 5_u, 6_u, true);
     WrapInFunction(Decl(Let("i", Expr(42_i))),  //
                    Decl(Let("a", select_a)),    //
                    Decl(Let("b", select_b)),    //
@@ -273,7 +273,7 @@
 struct BuiltinDataWithParamNum {
     uint32_t args_number;
     const char* name;
-    core::Function builtin;
+    core::BuiltinFn builtin;
 };
 
 inline std::ostream& operator<<(std::ostream& out, BuiltinDataWithParamNum data) {
@@ -700,55 +700,55 @@
 INSTANTIATE_TEST_SUITE_P(
     ResolverTest,
     ResolverBuiltinTest_FloatBuiltin_IdenticalType,
-    testing::Values(BuiltinDataWithParamNum{1, "abs", core::Function::kAbs},
-                    BuiltinDataWithParamNum{1, "acos", core::Function::kAcos},
-                    BuiltinDataWithParamNum{1, "acosh", core::Function::kAcos},
-                    BuiltinDataWithParamNum{1, "asin", core::Function::kAsin},
-                    BuiltinDataWithParamNum{1, "asinh", core::Function::kAsin},
-                    BuiltinDataWithParamNum{1, "atan", core::Function::kAtan},
-                    BuiltinDataWithParamNum{1, "atanh", core::Function::kAtan},
-                    BuiltinDataWithParamNum{2, "atan2", core::Function::kAtan2},
-                    BuiltinDataWithParamNum{1, "ceil", core::Function::kCeil},
-                    BuiltinDataWithParamNum{3, "clamp", core::Function::kClamp},
-                    BuiltinDataWithParamNum{1, "cos", core::Function::kCos},
-                    BuiltinDataWithParamNum{1, "cosh", core::Function::kCosh},
+    testing::Values(BuiltinDataWithParamNum{1, "abs", core::BuiltinFn::kAbs},
+                    BuiltinDataWithParamNum{1, "acos", core::BuiltinFn::kAcos},
+                    BuiltinDataWithParamNum{1, "acosh", core::BuiltinFn::kAcos},
+                    BuiltinDataWithParamNum{1, "asin", core::BuiltinFn::kAsin},
+                    BuiltinDataWithParamNum{1, "asinh", core::BuiltinFn::kAsin},
+                    BuiltinDataWithParamNum{1, "atan", core::BuiltinFn::kAtan},
+                    BuiltinDataWithParamNum{1, "atanh", core::BuiltinFn::kAtan},
+                    BuiltinDataWithParamNum{2, "atan2", core::BuiltinFn::kAtan2},
+                    BuiltinDataWithParamNum{1, "ceil", core::BuiltinFn::kCeil},
+                    BuiltinDataWithParamNum{3, "clamp", core::BuiltinFn::kClamp},
+                    BuiltinDataWithParamNum{1, "cos", core::BuiltinFn::kCos},
+                    BuiltinDataWithParamNum{1, "cosh", core::BuiltinFn::kCosh},
                     // cross: (vec3<T>, vec3<T>) -> vec3<T>
-                    BuiltinDataWithParamNum{1, "degrees", core::Function::kDegrees},
+                    BuiltinDataWithParamNum{1, "degrees", core::BuiltinFn::kDegrees},
                     // distance: (T, T) -> T, (vecN<T>, vecN<T>) -> T
-                    BuiltinDataWithParamNum{1, "exp", core::Function::kExp},
-                    BuiltinDataWithParamNum{1, "exp2", core::Function::kExp2},
+                    BuiltinDataWithParamNum{1, "exp", core::BuiltinFn::kExp},
+                    BuiltinDataWithParamNum{1, "exp2", core::BuiltinFn::kExp2},
                     // faceForward: (vecN<T>, vecN<T>, vecN<T>) -> vecN<T>
-                    BuiltinDataWithParamNum{1, "floor", core::Function::kFloor},
-                    BuiltinDataWithParamNum{3, "fma", core::Function::kFma},
-                    BuiltinDataWithParamNum{1, "fract", core::Function::kFract},
+                    BuiltinDataWithParamNum{1, "floor", core::BuiltinFn::kFloor},
+                    BuiltinDataWithParamNum{3, "fma", core::BuiltinFn::kFma},
+                    BuiltinDataWithParamNum{1, "fract", core::BuiltinFn::kFract},
                     // frexp
-                    BuiltinDataWithParamNum{1, "inverseSqrt", core::Function::kInverseSqrt},
+                    BuiltinDataWithParamNum{1, "inverseSqrt", core::BuiltinFn::kInverseSqrt},
                     // ldexp: (T, i32) -> T, (vecN<T>, vecN<i32>) -> vecN<T>
                     // length: (vecN<T>) -> T
-                    BuiltinDataWithParamNum{1, "log", core::Function::kLog},
-                    BuiltinDataWithParamNum{1, "log2", core::Function::kLog2},
-                    BuiltinDataWithParamNum{2, "max", core::Function::kMax},
-                    BuiltinDataWithParamNum{2, "min", core::Function::kMin},
+                    BuiltinDataWithParamNum{1, "log", core::BuiltinFn::kLog},
+                    BuiltinDataWithParamNum{1, "log2", core::BuiltinFn::kLog2},
+                    BuiltinDataWithParamNum{2, "max", core::BuiltinFn::kMax},
+                    BuiltinDataWithParamNum{2, "min", core::BuiltinFn::kMin},
                     // Note that `mix(vecN<f32>, vecN<f32>, f32) -> vecN<f32>` is not tested here.
-                    BuiltinDataWithParamNum{3, "mix", core::Function::kMix},
+                    BuiltinDataWithParamNum{3, "mix", core::BuiltinFn::kMix},
                     // modf
                     // normalize: (vecN<T>) -> vecN<T>
-                    BuiltinDataWithParamNum{2, "pow", core::Function::kPow},
+                    BuiltinDataWithParamNum{2, "pow", core::BuiltinFn::kPow},
                     // quantizeToF16 is not implemented yet.
-                    BuiltinDataWithParamNum{1, "radians", core::Function::kRadians},
+                    BuiltinDataWithParamNum{1, "radians", core::BuiltinFn::kRadians},
                     // reflect: (vecN<T>, vecN<T>) -> vecN<T>
                     // refract: (vecN<T>, vecN<T>, T) -> vecN<T>
-                    BuiltinDataWithParamNum{1, "round", core::Function::kRound},
+                    BuiltinDataWithParamNum{1, "round", core::BuiltinFn::kRound},
                     // saturate not implemented yet.
-                    BuiltinDataWithParamNum{1, "sign", core::Function::kSign},
-                    BuiltinDataWithParamNum{1, "sin", core::Function::kSin},
-                    BuiltinDataWithParamNum{1, "sinh", core::Function::kSinh},
-                    BuiltinDataWithParamNum{3, "smoothstep", core::Function::kSmoothstep},
-                    BuiltinDataWithParamNum{1, "sqrt", core::Function::kSqrt},
-                    BuiltinDataWithParamNum{2, "step", core::Function::kStep},
-                    BuiltinDataWithParamNum{1, "tan", core::Function::kTan},
-                    BuiltinDataWithParamNum{1, "tanh", core::Function::kTanh},
-                    BuiltinDataWithParamNum{1, "trunc", core::Function::kTrunc}));
+                    BuiltinDataWithParamNum{1, "sign", core::BuiltinFn::kSign},
+                    BuiltinDataWithParamNum{1, "sin", core::BuiltinFn::kSin},
+                    BuiltinDataWithParamNum{1, "sinh", core::BuiltinFn::kSinh},
+                    BuiltinDataWithParamNum{3, "smoothstep", core::BuiltinFn::kSmoothstep},
+                    BuiltinDataWithParamNum{1, "sqrt", core::BuiltinFn::kSqrt},
+                    BuiltinDataWithParamNum{2, "step", core::BuiltinFn::kStep},
+                    BuiltinDataWithParamNum{1, "tan", core::BuiltinFn::kTan},
+                    BuiltinDataWithParamNum{1, "tanh", core::BuiltinFn::kTanh},
+                    BuiltinDataWithParamNum{1, "trunc", core::BuiltinFn::kTrunc}));
 
 using ResolverBuiltinFloatTest = ResolverTest;
 
@@ -1430,7 +1430,7 @@
 struct BuiltinDataWithParamNum {
     uint32_t args_number;
     const char* name;
-    core::Function builtin;
+    core::BuiltinFn builtin;
 };
 
 inline std::ostream& operator<<(std::ostream& out, BuiltinDataWithParamNum data) {
@@ -1826,18 +1826,18 @@
     ResolverTest,
     ResolverBuiltinTest_IntegerBuiltin_IdenticalType,
     testing::Values(
-        BuiltinDataWithParamNum{1, "abs", core::Function::kAbs},
-        BuiltinDataWithParamNum{3, "clamp", core::Function::kClamp},
-        BuiltinDataWithParamNum{1, "countLeadingZeros", core::Function::kCountLeadingZeros},
-        BuiltinDataWithParamNum{1, "countOneBits", core::Function::kCountOneBits},
-        BuiltinDataWithParamNum{1, "countTrailingZeros", core::Function::kCountTrailingZeros},
+        BuiltinDataWithParamNum{1, "abs", core::BuiltinFn::kAbs},
+        BuiltinDataWithParamNum{3, "clamp", core::BuiltinFn::kClamp},
+        BuiltinDataWithParamNum{1, "countLeadingZeros", core::BuiltinFn::kCountLeadingZeros},
+        BuiltinDataWithParamNum{1, "countOneBits", core::BuiltinFn::kCountOneBits},
+        BuiltinDataWithParamNum{1, "countTrailingZeros", core::BuiltinFn::kCountTrailingZeros},
         // extractBits: (T, u32, u32) -> T
-        BuiltinDataWithParamNum{1, "firstLeadingBit", core::Function::kFirstLeadingBit},
-        BuiltinDataWithParamNum{1, "firstTrailingBit", core::Function::kFirstTrailingBit},
+        BuiltinDataWithParamNum{1, "firstLeadingBit", core::BuiltinFn::kFirstLeadingBit},
+        BuiltinDataWithParamNum{1, "firstTrailingBit", core::BuiltinFn::kFirstTrailingBit},
         // insertBits: (T, T, u32, u32) -> T
-        BuiltinDataWithParamNum{2, "max", core::Function::kMax},
-        BuiltinDataWithParamNum{2, "min", core::Function::kMin},
-        BuiltinDataWithParamNum{1, "reverseBits", core::Function::kReverseBits}));
+        BuiltinDataWithParamNum{2, "max", core::BuiltinFn::kMax},
+        BuiltinDataWithParamNum{2, "min", core::BuiltinFn::kMin},
+        BuiltinDataWithParamNum{1, "reverseBits", core::BuiltinFn::kReverseBits}));
 
 }  // namespace integer_builtin_tests
 
@@ -2576,8 +2576,8 @@
 TEST_P(ResolverBuiltinTest_DataPacking, InferType) {
     auto param = GetParam();
 
-    bool pack4 = param.builtin == core::Function::kPack4X8Snorm ||
-                 param.builtin == core::Function::kPack4X8Unorm;
+    bool pack4 = param.builtin == core::BuiltinFn::kPack4X8Snorm ||
+                 param.builtin == core::BuiltinFn::kPack4X8Unorm;
 
     auto* call = pack4 ? Call(param.name, Call<vec4<f32>>(1_f, 2_f, 3_f, 4_f))
                        : Call(param.name, Call<vec2<f32>>(1_f, 2_f));
@@ -2591,8 +2591,8 @@
 TEST_P(ResolverBuiltinTest_DataPacking, Error_IncorrectParamType) {
     auto param = GetParam();
 
-    bool pack4 = param.builtin == core::Function::kPack4X8Snorm ||
-                 param.builtin == core::Function::kPack4X8Unorm;
+    bool pack4 = param.builtin == core::BuiltinFn::kPack4X8Snorm ||
+                 param.builtin == core::BuiltinFn::kPack4X8Unorm;
 
     auto* call = pack4 ? Call(param.name, Call<vec4<i32>>(1_i, 2_i, 3_i, 4_i))
                        : Call(param.name, Call<vec2<i32>>(1_i, 2_i));
@@ -2617,8 +2617,8 @@
 TEST_P(ResolverBuiltinTest_DataPacking, Error_TooManyParams) {
     auto param = GetParam();
 
-    bool pack4 = param.builtin == core::Function::kPack4X8Snorm ||
-                 param.builtin == core::Function::kPack4X8Unorm;
+    bool pack4 = param.builtin == core::BuiltinFn::kPack4X8Snorm ||
+                 param.builtin == core::BuiltinFn::kPack4X8Unorm;
 
     auto* call = pack4 ? Call(param.name, Call<vec4<f32>>(1_f, 2_f, 3_f, 4_f), 1_f)
                        : Call(param.name, Call<vec2<f32>>(1_f, 2_f), 1_f);
@@ -2632,11 +2632,11 @@
 INSTANTIATE_TEST_SUITE_P(
     ResolverTest,
     ResolverBuiltinTest_DataPacking,
-    testing::Values(BuiltinData{"pack4x8snorm", core::Function::kPack4X8Snorm},
-                    BuiltinData{"pack4x8unorm", core::Function::kPack4X8Unorm},
-                    BuiltinData{"pack2x16snorm", core::Function::kPack2X16Snorm},
-                    BuiltinData{"pack2x16unorm", core::Function::kPack2X16Unorm},
-                    BuiltinData{"pack2x16float", core::Function::kPack2X16Float}));
+    testing::Values(BuiltinData{"pack4x8snorm", core::BuiltinFn::kPack4X8Snorm},
+                    BuiltinData{"pack4x8unorm", core::BuiltinFn::kPack4X8Unorm},
+                    BuiltinData{"pack2x16snorm", core::BuiltinFn::kPack2X16Snorm},
+                    BuiltinData{"pack2x16unorm", core::BuiltinFn::kPack2X16Unorm},
+                    BuiltinData{"pack2x16float", core::BuiltinFn::kPack2X16Float}));
 
 }  // namespace data_packing_builtin_tests
 
@@ -2647,8 +2647,8 @@
 TEST_P(ResolverBuiltinTest_DataUnpacking, InferType) {
     auto param = GetParam();
 
-    bool pack4 = param.builtin == core::Function::kUnpack4X8Snorm ||
-                 param.builtin == core::Function::kUnpack4X8Unorm;
+    bool pack4 = param.builtin == core::BuiltinFn::kUnpack4X8Snorm ||
+                 param.builtin == core::BuiltinFn::kUnpack4X8Unorm;
 
     auto* call = Call(param.name, 1_u);
     WrapInFunction(call);
@@ -2666,11 +2666,11 @@
 INSTANTIATE_TEST_SUITE_P(
     ResolverTest,
     ResolverBuiltinTest_DataUnpacking,
-    testing::Values(BuiltinData{"unpack4x8snorm", core::Function::kUnpack4X8Snorm},
-                    BuiltinData{"unpack4x8unorm", core::Function::kUnpack4X8Unorm},
-                    BuiltinData{"unpack2x16snorm", core::Function::kUnpack2X16Snorm},
-                    BuiltinData{"unpack2x16unorm", core::Function::kUnpack2X16Unorm},
-                    BuiltinData{"unpack2x16float", core::Function::kUnpack2X16Float}));
+    testing::Values(BuiltinData{"unpack4x8snorm", core::BuiltinFn::kUnpack4X8Snorm},
+                    BuiltinData{"unpack4x8unorm", core::BuiltinFn::kUnpack4X8Unorm},
+                    BuiltinData{"unpack2x16snorm", core::BuiltinFn::kUnpack2X16Snorm},
+                    BuiltinData{"unpack2x16unorm", core::BuiltinFn::kUnpack2X16Unorm},
+                    BuiltinData{"unpack2x16float", core::BuiltinFn::kUnpack2X16Float}));
 
 }  // namespace data_unpacking_builtin_tests
 
@@ -2703,8 +2703,8 @@
 INSTANTIATE_TEST_SUITE_P(
     ResolverTest,
     ResolverBuiltinTest_Barrier,
-    testing::Values(BuiltinData{"storageBarrier", core::Function::kStorageBarrier},
-                    BuiltinData{"workgroupBarrier", core::Function::kWorkgroupBarrier}));
+    testing::Values(BuiltinData{"storageBarrier", core::BuiltinFn::kStorageBarrier},
+                    BuiltinData{"workgroupBarrier", core::BuiltinFn::kWorkgroupBarrier}));
 
 }  // namespace synchronization_builtin_tests
 
diff --git a/src/tint/lang/wgsl/resolver/dependency_graph.cc b/src/tint/lang/wgsl/resolver/dependency_graph.cc
index db74547..baab8cd 100644
--- a/src/tint/lang/wgsl/resolver/dependency_graph.cc
+++ b/src/tint/lang/wgsl/resolver/dependency_graph.cc
@@ -19,7 +19,7 @@
 #include <variant>
 #include <vector>
 
-#include "src/tint/lang/core/builtin.h"
+#include "src/tint/lang/core/builtin_type.h"
 #include "src/tint/lang/core/builtin_value.h"
 #include "src/tint/lang/wgsl/ast/alias.h"
 #include "src/tint/lang/wgsl/ast/assignment_statement.h"
@@ -60,7 +60,7 @@
 #include "src/tint/lang/wgsl/ast/variable_decl_statement.h"
 #include "src/tint/lang/wgsl/ast/while_statement.h"
 #include "src/tint/lang/wgsl/ast/workgroup_attribute.h"
-#include "src/tint/lang/wgsl/sem/builtin.h"
+#include "src/tint/lang/wgsl/sem/builtin_fn.h"
 #include "src/tint/utils/containers/map.h"
 #include "src/tint/utils/containers/scope_stack.h"
 #include "src/tint/utils/containers/unique_vector.h"
@@ -474,8 +474,8 @@
 
         BuiltinType type = BuiltinType::kNone;
         std::variant<std::monostate,
-                     core::Function,
-                     core::Builtin,
+                     core::BuiltinFn,
+                     core::BuiltinType,
                      core::BuiltinValue,
                      core::AddressSpace,
                      core::TexelFormat,
@@ -490,12 +490,12 @@
     /// @returns the builtin info
     DependencyScanner::BuiltinInfo GetBuiltinInfo(Symbol symbol) {
         return builtin_info_map.GetOrCreate(symbol, [&] {
-            if (auto builtin_fn = core::ParseFunction(symbol.NameView());
-                builtin_fn != core::Function::kNone) {
+            if (auto builtin_fn = core::ParseBuiltinFn(symbol.NameView());
+                builtin_fn != core::BuiltinFn::kNone) {
                 return BuiltinInfo{BuiltinType::kFunction, builtin_fn};
             }
-            if (auto builtin_ty = core::ParseBuiltin(symbol.NameView());
-                builtin_ty != core::Builtin::kUndefined) {
+            if (auto builtin_ty = core::ParseBuiltinType(symbol.NameView());
+                builtin_ty != core::BuiltinType::kUndefined) {
                 return BuiltinInfo{BuiltinType::kBuiltin, builtin_ty};
             }
             if (auto builtin_val = core::ParseBuiltinValue(symbol.NameView());
@@ -537,11 +537,11 @@
                     break;
                 case BuiltinType::kFunction:
                     graph_.resolved_identifiers.Add(
-                        from, ResolvedIdentifier(builtin_info.Value<core::Function>()));
+                        from, ResolvedIdentifier(builtin_info.Value<core::BuiltinFn>()));
                     break;
                 case BuiltinType::kBuiltin:
                     graph_.resolved_identifiers.Add(
-                        from, ResolvedIdentifier(builtin_info.Value<core::Builtin>()));
+                        from, ResolvedIdentifier(builtin_info.Value<core::BuiltinType>()));
                     break;
                 case BuiltinType::kBuiltinValue:
                     graph_.resolved_identifiers.Add(
@@ -921,10 +921,10 @@
                 return "<unknown>";
             });
     }
-    if (auto builtin_fn = BuiltinFunction(); builtin_fn != core::Function::kNone) {
+    if (auto builtin_fn = BuiltinFn(); builtin_fn != core::BuiltinFn::kNone) {
         return "builtin function '" + tint::ToString(builtin_fn) + "'";
     }
-    if (auto builtin_ty = BuiltinType(); builtin_ty != core::Builtin::kUndefined) {
+    if (auto builtin_ty = BuiltinType(); builtin_ty != core::BuiltinType::kUndefined) {
         return "builtin type '" + tint::ToString(builtin_ty) + "'";
     }
     if (auto builtin_val = BuiltinValue(); builtin_val != core::BuiltinValue::kUndefined) {
diff --git a/src/tint/lang/wgsl/resolver/dependency_graph.h b/src/tint/lang/wgsl/resolver/dependency_graph.h
index 7d145ba..3755f00 100644
--- a/src/tint/lang/wgsl/resolver/dependency_graph.h
+++ b/src/tint/lang/wgsl/resolver/dependency_graph.h
@@ -19,9 +19,9 @@
 #include <vector>
 
 #include "src/tint/lang/core/access.h"
-#include "src/tint/lang/core/builtin.h"
+#include "src/tint/lang/core/builtin_fn.h"
+#include "src/tint/lang/core/builtin_type.h"
 #include "src/tint/lang/core/builtin_value.h"
-#include "src/tint/lang/core/function.h"
 #include "src/tint/lang/core/interpolation_sampling.h"
 #include "src/tint/lang/core/interpolation_type.h"
 #include "src/tint/lang/core/texel_format.h"
@@ -43,10 +43,10 @@
 /// - const ast::TypeDecl*  (as const ast::Node*)
 /// - const ast::Variable*  (as const ast::Node*)
 /// - const ast::Function*  (as const ast::Node*)
-/// - core::Function
+/// - core::BuiltinFn
 /// - core::Access
 /// - core::AddressSpace
-/// - core::Builtin
+/// - core::BuiltinType
 /// - core::BuiltinValue
 /// - core::InterpolationSampling
 /// - core::InterpolationType
@@ -74,13 +74,13 @@
         return nullptr;
     }
 
-    /// @return the builtin function if the ResolvedIdentifier holds core::Function, otherwise
-    /// core::Function::kNone
-    core::Function BuiltinFunction() const {
-        if (auto n = std::get_if<core::Function>(&value_)) {
+    /// @return the builtin function if the ResolvedIdentifier holds core::BuiltinFn, otherwise
+    /// core::BuiltinFn::kNone
+    core::BuiltinFn BuiltinFn() const {
+        if (auto n = std::get_if<core::BuiltinFn>(&value_)) {
             return *n;
         }
-        return core::Function::kNone;
+        return core::BuiltinFn::kNone;
     }
 
     /// @return the access if the ResolvedIdentifier holds core::Access, otherwise
@@ -101,13 +101,13 @@
         return core::AddressSpace::kUndefined;
     }
 
-    /// @return the builtin type if the ResolvedIdentifier holds core::Builtin, otherwise
-    /// core::Builtin::kUndefined
-    core::Builtin BuiltinType() const {
-        if (auto n = std::get_if<core::Builtin>(&value_)) {
+    /// @return the builtin type if the ResolvedIdentifier holds core::BuiltinType, otherwise
+    /// core::BuiltinType::kUndefined
+    core::BuiltinType BuiltinType() const {
+        if (auto n = std::get_if<core::BuiltinType>(&value_)) {
             return *n;
         }
-        return core::Builtin::kUndefined;
+        return core::BuiltinType::kUndefined;
     }
 
     /// @return the builtin value if the ResolvedIdentifier holds core::BuiltinValue, otherwise
@@ -169,10 +169,10 @@
   private:
     std::variant<UnresolvedIdentifier,
                  const ast::Node*,
-                 core::Function,
+                 core::BuiltinFn,
                  core::Access,
                  core::AddressSpace,
-                 core::Builtin,
+                 core::BuiltinType,
                  core::BuiltinValue,
                  core::InterpolationSampling,
                  core::InterpolationType,
diff --git a/src/tint/lang/wgsl/resolver/dependency_graph_test.cc b/src/tint/lang/wgsl/resolver/dependency_graph_test.cc
index a22669f..605ffe1 100644
--- a/src/tint/lang/wgsl/resolver/dependency_graph_test.cc
+++ b/src/tint/lang/wgsl/resolver/dependency_graph_test.cc
@@ -37,7 +37,7 @@
         DependencyGraph graph;
         auto result = DependencyGraph::Build(this->AST(), this->Diagnostics(), graph);
         if (expected_error.empty()) {
-            EXPECT_TRUE(result) << this->Diagnostics().str();
+            EXPECT_TRUE(result) << this->Diagnostics();
         } else {
             EXPECT_FALSE(result);
             EXPECT_EQ(expected_error, this->Diagnostics().str());
@@ -1177,10 +1177,10 @@
 ////////////////////////////////////////////////////////////////////////////////
 namespace resolve_to_builtin_func {
 
-using ResolverDependencyGraphResolveToBuiltinFunc =
-    ResolverDependencyGraphTestWithParam<std::tuple<SymbolUseKind, core::Function>>;
+using ResolverDependencyGraphResolveToBuiltinFn =
+    ResolverDependencyGraphTestWithParam<std::tuple<SymbolUseKind, core::BuiltinFn>>;
 
-TEST_P(ResolverDependencyGraphResolveToBuiltinFunc, Resolve) {
+TEST_P(ResolverDependencyGraphResolveToBuiltinFn, Resolve) {
     const auto use = std::get<0>(GetParam());
     const auto builtin = std::get<1>(GetParam());
     const auto symbol = Symbols().New(tint::ToString(builtin));
@@ -1191,23 +1191,23 @@
 
     auto resolved = Build().resolved_identifiers.Get(ident);
     ASSERT_TRUE(resolved);
-    EXPECT_EQ(resolved->BuiltinFunction(), builtin) << resolved->String();
+    EXPECT_EQ(resolved->BuiltinFn(), builtin) << resolved->String();
 }
 
 INSTANTIATE_TEST_SUITE_P(Types,
-                         ResolverDependencyGraphResolveToBuiltinFunc,
+                         ResolverDependencyGraphResolveToBuiltinFn,
                          testing::Combine(testing::ValuesIn(kTypeUseKinds),
-                                          testing::ValuesIn(core::kFunctions)));
+                                          testing::ValuesIn(core::kBuiltinFns)));
 
 INSTANTIATE_TEST_SUITE_P(Values,
-                         ResolverDependencyGraphResolveToBuiltinFunc,
+                         ResolverDependencyGraphResolveToBuiltinFn,
                          testing::Combine(testing::ValuesIn(kValueUseKinds),
-                                          testing::ValuesIn(core::kFunctions)));
+                                          testing::ValuesIn(core::kBuiltinFns)));
 
 INSTANTIATE_TEST_SUITE_P(Functions,
-                         ResolverDependencyGraphResolveToBuiltinFunc,
+                         ResolverDependencyGraphResolveToBuiltinFn,
                          testing::Combine(testing::ValuesIn(kFuncUseKinds),
-                                          testing::ValuesIn(core::kFunctions)));
+                                          testing::ValuesIn(core::kBuiltinFns)));
 
 }  // namespace resolve_to_builtin_func
 
@@ -1230,23 +1230,23 @@
 
     auto resolved = Build().resolved_identifiers.Get(ident);
     ASSERT_TRUE(resolved);
-    EXPECT_EQ(resolved->BuiltinType(), core::ParseBuiltin(name)) << resolved->String();
+    EXPECT_EQ(resolved->BuiltinType(), core::ParseBuiltinType(name)) << resolved->String();
 }
 
 INSTANTIATE_TEST_SUITE_P(Types,
                          ResolverDependencyGraphResolveToBuiltinType,
                          testing::Combine(testing::ValuesIn(kTypeUseKinds),
-                                          testing::ValuesIn(core::kBuiltinStrings)));
+                                          testing::ValuesIn(core::kBuiltinTypeStrings)));
 
 INSTANTIATE_TEST_SUITE_P(Values,
                          ResolverDependencyGraphResolveToBuiltinType,
                          testing::Combine(testing::ValuesIn(kValueUseKinds),
-                                          testing::ValuesIn(core::kBuiltinStrings)));
+                                          testing::ValuesIn(core::kBuiltinTypeStrings)));
 
 INSTANTIATE_TEST_SUITE_P(Functions,
                          ResolverDependencyGraphResolveToBuiltinType,
                          testing::Combine(testing::ValuesIn(kFuncUseKinds),
-                                          testing::ValuesIn(core::kBuiltinStrings)));
+                                          testing::ValuesIn(core::kBuiltinTypeStrings)));
 
 }  // namespace resolve_to_builtin_type
 
@@ -1595,12 +1595,12 @@
 INSTANTIATE_TEST_SUITE_P(BuiltinType,
                          ResolverDependencyGraphShadowKindTest,
                          testing::Combine(testing::ValuesIn(kAllUseKinds),
-                                          testing::ValuesIn(core::kBuiltinStrings)));
+                                          testing::ValuesIn(core::kBuiltinTypeStrings)));
 
-INSTANTIATE_TEST_SUITE_P(BuiltinFunction,
+INSTANTIATE_TEST_SUITE_P(BuiltinFn,
                          ResolverDependencyGraphShadowKindTest,
                          testing::Combine(testing::ValuesIn(kAllUseKinds),
-                                          testing::ValuesIn(core::kBuiltinStrings)));
+                                          testing::ValuesIn(core::kBuiltinTypeStrings)));
 
 INSTANTIATE_TEST_SUITE_P(InterpolationSampling,
                          ResolverDependencyGraphShadowKindTest,
diff --git a/src/tint/lang/wgsl/resolver/materialize_test.cc b/src/tint/lang/wgsl/resolver/materialize_test.cc
index a8fed54..6116ada 100644
--- a/src/tint/lang/wgsl/resolver/materialize_test.cc
+++ b/src/tint/lang/wgsl/resolver/materialize_test.cc
@@ -953,7 +953,7 @@
             break;
         }
         case Method::kTintMaterializeBuiltin: {
-            auto* call = Call(core::str(core::Function::kTintMaterialize), abstract_expr());
+            auto* call = Call(core::str(core::BuiltinFn::kTintMaterialize), abstract_expr());
             WrapInFunction(Decl(Const("c", call)));
             break;
         }
diff --git a/src/tint/lang/wgsl/resolver/resolver.cc b/src/tint/lang/wgsl/resolver/resolver.cc
index 3552a7c..8edaf01 100644
--- a/src/tint/lang/wgsl/resolver/resolver.cc
+++ b/src/tint/lang/wgsl/resolver/resolver.cc
@@ -20,7 +20,7 @@
 #include <limits>
 #include <utility>
 
-#include "src/tint/lang/core/builtin.h"
+#include "src/tint/lang/core/builtin_type.h"
 #include "src/tint/lang/core/constant/scalar.h"
 #include "src/tint/lang/core/fluent_types.h"
 #include "src/tint/lang/core/intrinsic/data/data.h"
@@ -2325,43 +2325,43 @@
                 });
         }
 
-        if (auto f = resolved->BuiltinFunction(); f != core::Function::kNone) {
+        if (auto f = resolved->BuiltinFn(); f != core::BuiltinFn::kNone) {
             if (!TINT_LIKELY(CheckNotTemplated("builtin", ident))) {
                 return nullptr;
             }
             return BuiltinCall(expr, f, args);
         }
 
-        if (auto b = resolved->BuiltinType(); b != core::Builtin::kUndefined) {
+        if (auto b = resolved->BuiltinType(); b != core::BuiltinType::kUndefined) {
             if (!ident->Is<ast::TemplatedIdentifier>()) {
                 // No template arguments provided.
                 // Check to see if this is an inferred-element-type call.
                 switch (b) {
-                    case core::Builtin::kArray:
+                    case core::BuiltinType::kArray:
                         return inferred_array();
-                    case core::Builtin::kVec2:
+                    case core::BuiltinType::kVec2:
                         return ctor_or_conv(CtorConvIntrinsic::kVec2, nullptr);
-                    case core::Builtin::kVec3:
+                    case core::BuiltinType::kVec3:
                         return ctor_or_conv(CtorConvIntrinsic::kVec3, nullptr);
-                    case core::Builtin::kVec4:
+                    case core::BuiltinType::kVec4:
                         return ctor_or_conv(CtorConvIntrinsic::kVec4, nullptr);
-                    case core::Builtin::kMat2X2:
+                    case core::BuiltinType::kMat2X2:
                         return ctor_or_conv(CtorConvIntrinsic::kMat2x2, nullptr);
-                    case core::Builtin::kMat2X3:
+                    case core::BuiltinType::kMat2X3:
                         return ctor_or_conv(CtorConvIntrinsic::kMat2x3, nullptr);
-                    case core::Builtin::kMat2X4:
+                    case core::BuiltinType::kMat2X4:
                         return ctor_or_conv(CtorConvIntrinsic::kMat2x4, nullptr);
-                    case core::Builtin::kMat3X2:
+                    case core::BuiltinType::kMat3X2:
                         return ctor_or_conv(CtorConvIntrinsic::kMat3x2, nullptr);
-                    case core::Builtin::kMat3X3:
+                    case core::BuiltinType::kMat3X3:
                         return ctor_or_conv(CtorConvIntrinsic::kMat3x3, nullptr);
-                    case core::Builtin::kMat3X4:
+                    case core::BuiltinType::kMat3X4:
                         return ctor_or_conv(CtorConvIntrinsic::kMat3x4, nullptr);
-                    case core::Builtin::kMat4X2:
+                    case core::BuiltinType::kMat4X2:
                         return ctor_or_conv(CtorConvIntrinsic::kMat4x2, nullptr);
-                    case core::Builtin::kMat4X3:
+                    case core::BuiltinType::kMat4X3:
                         return ctor_or_conv(CtorConvIntrinsic::kMat4x3, nullptr);
-                    case core::Builtin::kMat4X4:
+                    case core::BuiltinType::kMat4X4:
                         return ctor_or_conv(CtorConvIntrinsic::kMat4x4, nullptr);
                     default:
                         break;
@@ -2400,7 +2400,7 @@
 
 template <size_t N>
 sem::Call* Resolver::BuiltinCall(const ast::CallExpression* expr,
-                                 core::Function fn,
+                                 core::BuiltinFn fn,
                                  Vector<const sem::ValueExpression*, N>& args) {
     auto arg_stage = core::EvaluationStage::kConstant;
     for (auto* arg : args) {
@@ -2434,12 +2434,12 @@
         }
         auto eval_stage = overload->const_eval_fn ? core::EvaluationStage::kConstant
                                                   : core::EvaluationStage::kRuntime;
-        return builder_->create<sem::Builtin>(
+        return builder_->create<sem::BuiltinFn>(
             fn, overload->return_type, std::move(params), eval_stage, supported_stages,
             flags.Contains(OverloadFlag::kIsDeprecated), flags.Contains(OverloadFlag::kMustUse));
     });
 
-    if (fn == core::Function::kTintMaterialize) {
+    if (fn == core::BuiltinFn::kTintMaterialize) {
         args[0] = Materialize(args[0]);
         if (!args[0]) {
             return nullptr;
@@ -2487,24 +2487,24 @@
         current_function_->AddDirectCall(call);
     }
 
-    if (!validator_.RequiredExtensionForBuiltinFunction(call)) {
+    if (!validator_.RequiredExtensionForBuiltinFn(call)) {
         return nullptr;
     }
 
-    if (IsTextureBuiltin(fn)) {
-        if (!validator_.TextureBuiltinFunction(call)) {
+    if (IsTexture(fn)) {
+        if (!validator_.TextureBuiltinFn(call)) {
             return nullptr;
         }
         CollectTextureSamplerPairs(target, call->Arguments());
     }
 
-    if (fn == core::Function::kWorkgroupUniformLoad) {
+    if (fn == core::BuiltinFn::kWorkgroupUniformLoad) {
         if (!validator_.WorkgroupUniformLoad(call)) {
             return nullptr;
         }
     }
 
-    if (fn == core::Function::kSubgroupBroadcast) {
+    if (fn == core::BuiltinFn::kSubgroupBroadcast) {
         if (!validator_.SubgroupBroadcast(call)) {
             return nullptr;
         }
@@ -2517,7 +2517,8 @@
     return call;
 }
 
-core::type::Type* Resolver::BuiltinType(core::Builtin builtin_ty, const ast::Identifier* ident) {
+core::type::Type* Resolver::BuiltinType(core::BuiltinType builtin_ty,
+                                        const ast::Identifier* ident) {
     auto& b = *builder_;
 
     auto check_no_tmpl_args = [&](core::type::Type* ty) -> core::type::Type* {
@@ -2784,226 +2785,226 @@
     };
 
     switch (builtin_ty) {
-        case core::Builtin::kBool:
+        case core::BuiltinType::kBool:
             return check_no_tmpl_args(b.create<core::type::Bool>());
-        case core::Builtin::kI32:
+        case core::BuiltinType::kI32:
             return check_no_tmpl_args(i32());
-        case core::Builtin::kU32:
+        case core::BuiltinType::kU32:
             return check_no_tmpl_args(u32());
-        case core::Builtin::kF16:
+        case core::BuiltinType::kF16:
             return check_no_tmpl_args(f16());
-        case core::Builtin::kF32:
+        case core::BuiltinType::kF32:
             return check_no_tmpl_args(b.create<core::type::F32>());
-        case core::Builtin::kVec2:
+        case core::BuiltinType::kVec2:
             return vec_t(2);
-        case core::Builtin::kVec3:
+        case core::BuiltinType::kVec3:
             return vec_t(3);
-        case core::Builtin::kVec4:
+        case core::BuiltinType::kVec4:
             return vec_t(4);
-        case core::Builtin::kMat2X2:
+        case core::BuiltinType::kMat2X2:
             return mat_t(2, 2);
-        case core::Builtin::kMat2X3:
+        case core::BuiltinType::kMat2X3:
             return mat_t(2, 3);
-        case core::Builtin::kMat2X4:
+        case core::BuiltinType::kMat2X4:
             return mat_t(2, 4);
-        case core::Builtin::kMat3X2:
+        case core::BuiltinType::kMat3X2:
             return mat_t(3, 2);
-        case core::Builtin::kMat3X3:
+        case core::BuiltinType::kMat3X3:
             return mat_t(3, 3);
-        case core::Builtin::kMat3X4:
+        case core::BuiltinType::kMat3X4:
             return mat_t(3, 4);
-        case core::Builtin::kMat4X2:
+        case core::BuiltinType::kMat4X2:
             return mat_t(4, 2);
-        case core::Builtin::kMat4X3:
+        case core::BuiltinType::kMat4X3:
             return mat_t(4, 3);
-        case core::Builtin::kMat4X4:
+        case core::BuiltinType::kMat4X4:
             return mat_t(4, 4);
-        case core::Builtin::kMat2X2F:
+        case core::BuiltinType::kMat2X2F:
             return check_no_tmpl_args(mat(f32(), 2u, 2u));
-        case core::Builtin::kMat2X3F:
+        case core::BuiltinType::kMat2X3F:
             return check_no_tmpl_args(mat(f32(), 2u, 3u));
-        case core::Builtin::kMat2X4F:
+        case core::BuiltinType::kMat2X4F:
             return check_no_tmpl_args(mat(f32(), 2u, 4u));
-        case core::Builtin::kMat3X2F:
+        case core::BuiltinType::kMat3X2F:
             return check_no_tmpl_args(mat(f32(), 3u, 2u));
-        case core::Builtin::kMat3X3F:
+        case core::BuiltinType::kMat3X3F:
             return check_no_tmpl_args(mat(f32(), 3u, 3u));
-        case core::Builtin::kMat3X4F:
+        case core::BuiltinType::kMat3X4F:
             return check_no_tmpl_args(mat(f32(), 3u, 4u));
-        case core::Builtin::kMat4X2F:
+        case core::BuiltinType::kMat4X2F:
             return check_no_tmpl_args(mat(f32(), 4u, 2u));
-        case core::Builtin::kMat4X3F:
+        case core::BuiltinType::kMat4X3F:
             return check_no_tmpl_args(mat(f32(), 4u, 3u));
-        case core::Builtin::kMat4X4F:
+        case core::BuiltinType::kMat4X4F:
             return check_no_tmpl_args(mat(f32(), 4u, 4u));
-        case core::Builtin::kMat2X2H:
+        case core::BuiltinType::kMat2X2H:
             return check_no_tmpl_args(mat(f16(), 2u, 2u));
-        case core::Builtin::kMat2X3H:
+        case core::BuiltinType::kMat2X3H:
             return check_no_tmpl_args(mat(f16(), 2u, 3u));
-        case core::Builtin::kMat2X4H:
+        case core::BuiltinType::kMat2X4H:
             return check_no_tmpl_args(mat(f16(), 2u, 4u));
-        case core::Builtin::kMat3X2H:
+        case core::BuiltinType::kMat3X2H:
             return check_no_tmpl_args(mat(f16(), 3u, 2u));
-        case core::Builtin::kMat3X3H:
+        case core::BuiltinType::kMat3X3H:
             return check_no_tmpl_args(mat(f16(), 3u, 3u));
-        case core::Builtin::kMat3X4H:
+        case core::BuiltinType::kMat3X4H:
             return check_no_tmpl_args(mat(f16(), 3u, 4u));
-        case core::Builtin::kMat4X2H:
+        case core::BuiltinType::kMat4X2H:
             return check_no_tmpl_args(mat(f16(), 4u, 2u));
-        case core::Builtin::kMat4X3H:
+        case core::BuiltinType::kMat4X3H:
             return check_no_tmpl_args(mat(f16(), 4u, 3u));
-        case core::Builtin::kMat4X4H:
+        case core::BuiltinType::kMat4X4H:
             return check_no_tmpl_args(mat(f16(), 4u, 4u));
-        case core::Builtin::kVec2F:
+        case core::BuiltinType::kVec2F:
             return check_no_tmpl_args(vec(f32(), 2u));
-        case core::Builtin::kVec3F:
+        case core::BuiltinType::kVec3F:
             return check_no_tmpl_args(vec(f32(), 3u));
-        case core::Builtin::kVec4F:
+        case core::BuiltinType::kVec4F:
             return check_no_tmpl_args(vec(f32(), 4u));
-        case core::Builtin::kVec2H:
+        case core::BuiltinType::kVec2H:
             return check_no_tmpl_args(vec(f16(), 2u));
-        case core::Builtin::kVec3H:
+        case core::BuiltinType::kVec3H:
             return check_no_tmpl_args(vec(f16(), 3u));
-        case core::Builtin::kVec4H:
+        case core::BuiltinType::kVec4H:
             return check_no_tmpl_args(vec(f16(), 4u));
-        case core::Builtin::kVec2I:
+        case core::BuiltinType::kVec2I:
             return check_no_tmpl_args(vec(i32(), 2u));
-        case core::Builtin::kVec3I:
+        case core::BuiltinType::kVec3I:
             return check_no_tmpl_args(vec(i32(), 3u));
-        case core::Builtin::kVec4I:
+        case core::BuiltinType::kVec4I:
             return check_no_tmpl_args(vec(i32(), 4u));
-        case core::Builtin::kVec2U:
+        case core::BuiltinType::kVec2U:
             return check_no_tmpl_args(vec(u32(), 2u));
-        case core::Builtin::kVec3U:
+        case core::BuiltinType::kVec3U:
             return check_no_tmpl_args(vec(u32(), 3u));
-        case core::Builtin::kVec4U:
+        case core::BuiltinType::kVec4U:
             return check_no_tmpl_args(vec(u32(), 4u));
-        case core::Builtin::kArray:
+        case core::BuiltinType::kArray:
             return array();
-        case core::Builtin::kAtomic:
+        case core::BuiltinType::kAtomic:
             return atomic();
-        case core::Builtin::kPtr:
+        case core::BuiltinType::kPtr:
             return ptr();
-        case core::Builtin::kSampler:
+        case core::BuiltinType::kSampler:
             return check_no_tmpl_args(
                 builder_->create<core::type::Sampler>(core::type::SamplerKind::kSampler));
-        case core::Builtin::kSamplerComparison:
+        case core::BuiltinType::kSamplerComparison:
             return check_no_tmpl_args(
                 builder_->create<core::type::Sampler>(core::type::SamplerKind::kComparisonSampler));
-        case core::Builtin::kTexture1D:
+        case core::BuiltinType::kTexture1D:
             return sampled_texture(core::type::TextureDimension::k1d);
-        case core::Builtin::kTexture2D:
+        case core::BuiltinType::kTexture2D:
             return sampled_texture(core::type::TextureDimension::k2d);
-        case core::Builtin::kTexture2DArray:
+        case core::BuiltinType::kTexture2DArray:
             return sampled_texture(core::type::TextureDimension::k2dArray);
-        case core::Builtin::kTexture3D:
+        case core::BuiltinType::kTexture3D:
             return sampled_texture(core::type::TextureDimension::k3d);
-        case core::Builtin::kTextureCube:
+        case core::BuiltinType::kTextureCube:
             return sampled_texture(core::type::TextureDimension::kCube);
-        case core::Builtin::kTextureCubeArray:
+        case core::BuiltinType::kTextureCubeArray:
             return sampled_texture(core::type::TextureDimension::kCubeArray);
-        case core::Builtin::kTextureDepth2D:
+        case core::BuiltinType::kTextureDepth2D:
             return check_no_tmpl_args(
                 builder_->create<core::type::DepthTexture>(core::type::TextureDimension::k2d));
-        case core::Builtin::kTextureDepth2DArray:
+        case core::BuiltinType::kTextureDepth2DArray:
             return check_no_tmpl_args(
                 builder_->create<core::type::DepthTexture>(core::type::TextureDimension::k2dArray));
-        case core::Builtin::kTextureDepthCube:
+        case core::BuiltinType::kTextureDepthCube:
             return check_no_tmpl_args(
                 builder_->create<core::type::DepthTexture>(core::type::TextureDimension::kCube));
-        case core::Builtin::kTextureDepthCubeArray:
+        case core::BuiltinType::kTextureDepthCubeArray:
             return check_no_tmpl_args(builder_->create<core::type::DepthTexture>(
                 core::type::TextureDimension::kCubeArray));
-        case core::Builtin::kTextureDepthMultisampled2D:
+        case core::BuiltinType::kTextureDepthMultisampled2D:
             return check_no_tmpl_args(builder_->create<core::type::DepthMultisampledTexture>(
                 core::type::TextureDimension::k2d));
-        case core::Builtin::kTextureExternal:
+        case core::BuiltinType::kTextureExternal:
             return check_no_tmpl_args(builder_->create<core::type::ExternalTexture>());
-        case core::Builtin::kTextureMultisampled2D:
+        case core::BuiltinType::kTextureMultisampled2D:
             return multisampled_texture(core::type::TextureDimension::k2d);
-        case core::Builtin::kTextureStorage1D:
+        case core::BuiltinType::kTextureStorage1D:
             return storage_texture(core::type::TextureDimension::k1d);
-        case core::Builtin::kTextureStorage2D:
+        case core::BuiltinType::kTextureStorage2D:
             return storage_texture(core::type::TextureDimension::k2d);
-        case core::Builtin::kTextureStorage2DArray:
+        case core::BuiltinType::kTextureStorage2DArray:
             return storage_texture(core::type::TextureDimension::k2dArray);
-        case core::Builtin::kTextureStorage3D:
+        case core::BuiltinType::kTextureStorage3D:
             return storage_texture(core::type::TextureDimension::k3d);
-        case core::Builtin::kPackedVec3:
+        case core::BuiltinType::kPackedVec3:
             return packed_vec3_t();
-        case core::Builtin::kAtomicCompareExchangeResultI32:
+        case core::BuiltinType::kAtomicCompareExchangeResultI32:
             return core::type::CreateAtomicCompareExchangeResult(builder_->Types(),
                                                                  builder_->Symbols(), i32());
-        case core::Builtin::kAtomicCompareExchangeResultU32:
+        case core::BuiltinType::kAtomicCompareExchangeResultU32:
             return core::type::CreateAtomicCompareExchangeResult(builder_->Types(),
                                                                  builder_->Symbols(), u32());
-        case core::Builtin::kFrexpResultAbstract:
+        case core::BuiltinType::kFrexpResultAbstract:
             return core::type::CreateFrexpResult(builder_->Types(), builder_->Symbols(), af());
-        case core::Builtin::kFrexpResultF16:
+        case core::BuiltinType::kFrexpResultF16:
             return core::type::CreateFrexpResult(builder_->Types(), builder_->Symbols(), f16());
-        case core::Builtin::kFrexpResultF32:
+        case core::BuiltinType::kFrexpResultF32:
             return core::type::CreateFrexpResult(builder_->Types(), builder_->Symbols(), f32());
-        case core::Builtin::kFrexpResultVec2Abstract:
+        case core::BuiltinType::kFrexpResultVec2Abstract:
             return core::type::CreateFrexpResult(builder_->Types(), builder_->Symbols(),
                                                  vec(af(), 2));
-        case core::Builtin::kFrexpResultVec2F16:
+        case core::BuiltinType::kFrexpResultVec2F16:
             return core::type::CreateFrexpResult(builder_->Types(), builder_->Symbols(),
                                                  vec(f16(), 2));
-        case core::Builtin::kFrexpResultVec2F32:
+        case core::BuiltinType::kFrexpResultVec2F32:
             return core::type::CreateFrexpResult(builder_->Types(), builder_->Symbols(),
                                                  vec(f32(), 2));
-        case core::Builtin::kFrexpResultVec3Abstract:
+        case core::BuiltinType::kFrexpResultVec3Abstract:
             return core::type::CreateFrexpResult(builder_->Types(), builder_->Symbols(),
                                                  vec(af(), 3));
-        case core::Builtin::kFrexpResultVec3F16:
+        case core::BuiltinType::kFrexpResultVec3F16:
             return core::type::CreateFrexpResult(builder_->Types(), builder_->Symbols(),
                                                  vec(f16(), 3));
-        case core::Builtin::kFrexpResultVec3F32:
+        case core::BuiltinType::kFrexpResultVec3F32:
             return core::type::CreateFrexpResult(builder_->Types(), builder_->Symbols(),
                                                  vec(f32(), 3));
-        case core::Builtin::kFrexpResultVec4Abstract:
+        case core::BuiltinType::kFrexpResultVec4Abstract:
             return core::type::CreateFrexpResult(builder_->Types(), builder_->Symbols(),
                                                  vec(af(), 4));
-        case core::Builtin::kFrexpResultVec4F16:
+        case core::BuiltinType::kFrexpResultVec4F16:
             return core::type::CreateFrexpResult(builder_->Types(), builder_->Symbols(),
                                                  vec(f16(), 4));
-        case core::Builtin::kFrexpResultVec4F32:
+        case core::BuiltinType::kFrexpResultVec4F32:
             return core::type::CreateFrexpResult(builder_->Types(), builder_->Symbols(),
                                                  vec(f32(), 4));
-        case core::Builtin::kModfResultAbstract:
+        case core::BuiltinType::kModfResultAbstract:
             return core::type::CreateModfResult(builder_->Types(), builder_->Symbols(), af());
-        case core::Builtin::kModfResultF16:
+        case core::BuiltinType::kModfResultF16:
             return core::type::CreateModfResult(builder_->Types(), builder_->Symbols(), f16());
-        case core::Builtin::kModfResultF32:
+        case core::BuiltinType::kModfResultF32:
             return core::type::CreateModfResult(builder_->Types(), builder_->Symbols(), f32());
-        case core::Builtin::kModfResultVec2Abstract:
+        case core::BuiltinType::kModfResultVec2Abstract:
             return core::type::CreateModfResult(builder_->Types(), builder_->Symbols(),
                                                 vec(af(), 2));
-        case core::Builtin::kModfResultVec2F16:
+        case core::BuiltinType::kModfResultVec2F16:
             return core::type::CreateModfResult(builder_->Types(), builder_->Symbols(),
                                                 vec(f16(), 2));
-        case core::Builtin::kModfResultVec2F32:
+        case core::BuiltinType::kModfResultVec2F32:
             return core::type::CreateModfResult(builder_->Types(), builder_->Symbols(),
                                                 vec(f32(), 2));
-        case core::Builtin::kModfResultVec3Abstract:
+        case core::BuiltinType::kModfResultVec3Abstract:
             return core::type::CreateModfResult(builder_->Types(), builder_->Symbols(),
                                                 vec(af(), 3));
-        case core::Builtin::kModfResultVec3F16:
+        case core::BuiltinType::kModfResultVec3F16:
             return core::type::CreateModfResult(builder_->Types(), builder_->Symbols(),
                                                 vec(f16(), 3));
-        case core::Builtin::kModfResultVec3F32:
+        case core::BuiltinType::kModfResultVec3F32:
             return core::type::CreateModfResult(builder_->Types(), builder_->Symbols(),
                                                 vec(f32(), 3));
-        case core::Builtin::kModfResultVec4Abstract:
+        case core::BuiltinType::kModfResultVec4Abstract:
             return core::type::CreateModfResult(builder_->Types(), builder_->Symbols(),
                                                 vec(af(), 4));
-        case core::Builtin::kModfResultVec4F16:
+        case core::BuiltinType::kModfResultVec4F16:
             return core::type::CreateModfResult(builder_->Types(), builder_->Symbols(),
                                                 vec(f16(), 4));
-        case core::Builtin::kModfResultVec4F32:
+        case core::BuiltinType::kModfResultVec4F32:
             return core::type::CreateModfResult(builder_->Types(), builder_->Symbols(),
                                                 vec(f32(), 4));
-        case core::Builtin::kUndefined:
+        case core::BuiltinType::kUndefined:
             break;
     }
 
@@ -3027,7 +3028,7 @@
         });
 }
 
-void Resolver::CollectTextureSamplerPairs(const sem::Builtin* builtin,
+void Resolver::CollectTextureSamplerPairs(const sem::BuiltinFn* builtin,
                                           VectorRef<const sem::ValueExpression*> args) const {
     // Collect a texture/sampler pair for this builtin.
     const auto& signature = builtin->Signature();
@@ -3324,7 +3325,7 @@
             });
     }
 
-    if (auto builtin_ty = resolved->BuiltinType(); builtin_ty != core::Builtin::kUndefined) {
+    if (auto builtin_ty = resolved->BuiltinType(); builtin_ty != core::BuiltinType::kUndefined) {
         auto* ty = BuiltinType(builtin_ty, ident);
         if (!ty) {
             return nullptr;
@@ -3332,7 +3333,7 @@
         return builder_->create<sem::TypeExpression>(expr, current_statement_, ty);
     }
 
-    if (resolved->BuiltinFunction() != core::Function::kNone) {
+    if (resolved->BuiltinFn() != core::BuiltinFn::kNone) {
         AddError("missing '(' for builtin function call", expr->source.End());
         return nullptr;
     }
diff --git a/src/tint/lang/wgsl/resolver/resolver.h b/src/tint/lang/wgsl/resolver/resolver.h
index 77b5d9e..5dd2029 100644
--- a/src/tint/lang/wgsl/resolver/resolver.h
+++ b/src/tint/lang/wgsl/resolver/resolver.h
@@ -59,7 +59,7 @@
 namespace tint::sem {
 class Array;
 class BlockStatement;
-class Builtin;
+class BuiltinFn;
 class CaseStatement;
 class ForLoopStatement;
 class IfStatement;
@@ -212,7 +212,7 @@
     sem::Expression* Identifier(const ast::IdentifierExpression*);
     template <size_t N>
     sem::Call* BuiltinCall(const ast::CallExpression*,
-                           core::Function,
+                           core::BuiltinFn,
                            Vector<const sem::ValueExpression*, N>& args);
     sem::ValueExpression* Literal(const ast::LiteralExpression*);
     sem::ValueExpression* MemberAccessor(const ast::MemberAccessorExpression*);
@@ -312,7 +312,7 @@
     // / builtin, and records these on the current function by calling AddTextureSamplerPair().
     void CollectTextureSamplerPairs(sem::Function* func,
                                     VectorRef<const sem::ValueExpression*> args) const;
-    void CollectTextureSamplerPairs(const sem::Builtin* builtin,
+    void CollectTextureSamplerPairs(const sem::BuiltinFn* builtin,
                                     VectorRef<const sem::ValueExpression*> args) const;
 
     /// Resolves the WorkgroupSize for the given function, assigning it to
@@ -559,7 +559,7 @@
     /// @returns the core::type::Type for the builtin type @p builtin_ty with the identifier @p
     /// ident
     /// @note: Will raise an ICE if @p symbol is not a builtin type.
-    core::type::Type* BuiltinType(core::Builtin builtin_ty, const ast::Identifier* ident);
+    core::type::Type* BuiltinType(core::BuiltinType builtin_ty, const ast::Identifier* ident);
 
     /// @returns the nesting depth of @ty as defined in
     /// https://gpuweb.github.io/gpuweb/wgsl/#composite-types
@@ -635,7 +635,7 @@
     Hashset<const ast::Expression*, 8> skip_const_eval_;
     IdentifierResolveHint identifier_resolve_hint_;
     Hashmap<const core::type::Type*, size_t, 8> nest_depth_;
-    Hashmap<std::pair<core::intrinsic::Overload, core::Function>, sem::Builtin*, 64> builtins_;
+    Hashmap<std::pair<core::intrinsic::Overload, core::BuiltinFn>, sem::BuiltinFn*, 64> builtins_;
     Hashmap<core::intrinsic::Overload, sem::ValueConstructor*, 16> constructors_;
     Hashmap<core::intrinsic::Overload, sem::ValueConversion*, 16> converters_;
 };
diff --git a/src/tint/lang/wgsl/resolver/uniformity.cc b/src/tint/lang/wgsl/resolver/uniformity.cc
index 9b8385b..0ce82d0 100644
--- a/src/tint/lang/wgsl/resolver/uniformity.cc
+++ b/src/tint/lang/wgsl/resolver/uniformity.cc
@@ -23,7 +23,7 @@
 #include "src/tint/lang/wgsl/program/program_builder.h"
 #include "src/tint/lang/wgsl/resolver/dependency_graph.h"
 #include "src/tint/lang/wgsl/sem/block_statement.h"
-#include "src/tint/lang/wgsl/sem/builtin.h"
+#include "src/tint/lang/wgsl/sem/builtin_fn.h"
 #include "src/tint/lang/wgsl/sem/for_loop_statement.h"
 #include "src/tint/lang/wgsl/sem/function.h"
 #include "src/tint/lang/wgsl/sem/if_statement.h"
@@ -1547,17 +1547,17 @@
         const FunctionInfo* func_info = nullptr;
         Switch(
             sem->Target(),
-            [&](const sem::Builtin* builtin) {
+            [&](const sem::BuiltinFn* builtin) {
                 // Most builtins have no restrictions. The exceptions are barriers, derivatives,
                 // some texture sampling builtins, and atomics.
                 if (builtin->IsBarrier()) {
                     callsite_tag = {CallSiteTag::CallSiteRequiredToBeUniform, default_severity};
-                } else if (builtin->Type() == core::Function::kWorkgroupUniformLoad) {
+                } else if (builtin->Fn() == core::BuiltinFn::kWorkgroupUniformLoad) {
                     callsite_tag = {CallSiteTag::CallSiteRequiredToBeUniform, default_severity};
                 } else if (builtin->IsDerivative() ||
-                           builtin->Type() == core::Function::kTextureSample ||
-                           builtin->Type() == core::Function::kTextureSampleBias ||
-                           builtin->Type() == core::Function::kTextureSampleCompare) {
+                           builtin->Fn() == core::BuiltinFn::kTextureSample ||
+                           builtin->Fn() == core::BuiltinFn::kTextureSampleBias ||
+                           builtin->Fn() == core::BuiltinFn::kTextureSampleCompare) {
                     // Get the severity of derivative uniformity violations in this context.
                     auto severity = sem_.DiagnosticSeverity(
                         call, wgsl::CoreDiagnosticRule::kDerivativeUniformity);
@@ -1568,7 +1568,7 @@
                 } else if (builtin->IsAtomic()) {
                     callsite_tag = {CallSiteTag::CallSiteNoRestriction};
                     function_tag = ReturnValueMayBeNonUniform;
-                } else if (builtin->Type() == core::Function::kTextureLoad) {
+                } else if (builtin->Fn() == core::BuiltinFn::kTextureLoad) {
                     // Loading from a read-write storage texture may produce a non-uniform value.
                     auto* storage =
                         builtin->Parameters()[0]->Type()->As<core::type::StorageTexture>();
@@ -1669,8 +1669,8 @@
                     current_function_->variables.Set(root_ident, ptr_result);
                 }
             } else {
-                auto* builtin = sem->Target()->As<sem::Builtin>();
-                if (builtin && builtin->Type() == core::Function::kWorkgroupUniformLoad) {
+                auto* builtin = sem->Target()->As<sem::BuiltinFn>();
+                if (builtin && builtin->Fn() == core::BuiltinFn::kWorkgroupUniformLoad) {
                     // The workgroupUniformLoad builtin requires its parameter to be uniform.
                     current_function_->RequiredToBeUniform(default_severity)->AddEdge(args[i]);
                 } else {
@@ -1736,7 +1736,7 @@
         const ast::CallExpression* call,
         wgsl::DiagnosticSeverity severity) {
         auto* target = SemCall(call)->Target();
-        if (target->Is<sem::Builtin>()) {
+        if (target->Is<sem::BuiltinFn>()) {
             // This is a call to a builtin, so we must be done.
             return call;
         } else if (auto* user = target->As<sem::Function>()) {
diff --git a/src/tint/lang/wgsl/resolver/validator.cc b/src/tint/lang/wgsl/resolver/validator.cc
index c21c6d1..67aed69 100644
--- a/src/tint/lang/wgsl/resolver/validator.cc
+++ b/src/tint/lang/wgsl/resolver/validator.cc
@@ -1519,8 +1519,8 @@
                          call->Declaration()->source);
                 sem_.NoteDeclarationSource(fn->Declaration());
             },
-            [&](const sem::Builtin* b) {
-                AddError("ignoring return value of builtin '" + tint::ToString(b->Type()) + "'",
+            [&](const sem::BuiltinFn* b) {
+                AddError("ignoring return value of builtin '" + tint::ToString(b->Fn()) + "'",
                          call->Declaration()->source);
             },
             [&](const sem::ValueConversion*) {
@@ -1633,8 +1633,8 @@
             // https://gpuweb.github.io/gpuweb/wgsl/#function-call-expr
             // If the called function does not return a value, a function call statement should be
             // used instead.
-            auto* builtin = call->Target()->As<sem::Builtin>();
-            auto name = tint::ToString(builtin->Type());
+            auto* builtin = call->Target()->As<sem::BuiltinFn>();
+            auto name = tint::ToString(builtin->Fn());
             AddError("builtin '" + name + "' does not return a value", call->Declaration()->source);
             return false;
         }
@@ -1643,8 +1643,8 @@
     return true;
 }
 
-bool Validator::TextureBuiltinFunction(const sem::Call* call) const {
-    auto* builtin = call->Target()->As<sem::Builtin>();
+bool Validator::TextureBuiltinFn(const sem::Call* call) const {
+    auto* builtin = call->Target()->As<sem::BuiltinFn>();
     if (!builtin) {
         return false;
     }
@@ -1695,7 +1695,7 @@
 }
 
 bool Validator::WorkgroupUniformLoad(const sem::Call* call) const {
-    auto* builtin = call->Target()->As<sem::Builtin>();
+    auto* builtin = call->Target()->As<sem::BuiltinFn>();
     if (!builtin) {
         return false;
     }
@@ -1717,7 +1717,7 @@
 }
 
 bool Validator::SubgroupBroadcast(const sem::Call* call) const {
-    auto* builtin = call->Target()->As<sem::Builtin>();
+    auto* builtin = call->Target()->As<sem::BuiltinFn>();
     if (!builtin) {
         return false;
     }
@@ -1733,8 +1733,8 @@
     return true;
 }
 
-bool Validator::RequiredExtensionForBuiltinFunction(const sem::Call* call) const {
-    const auto* builtin = call->Target()->As<sem::Builtin>();
+bool Validator::RequiredExtensionForBuiltinFn(const sem::Call* call) const {
+    const auto* builtin = call->Target()->As<sem::BuiltinFn>();
     if (!builtin) {
         return true;
     }
diff --git a/src/tint/lang/wgsl/resolver/validator.h b/src/tint/lang/wgsl/resolver/validator.h
index 38bb8f1..ecce5ba 100644
--- a/src/tint/lang/wgsl/resolver/validator.h
+++ b/src/tint/lang/wgsl/resolver/validator.h
@@ -52,7 +52,7 @@
 class Array;
 class BlockStatement;
 class BreakIfStatement;
-class Builtin;
+class BuiltinFn;
 class Call;
 class CaseStatement;
 class ForLoopStatement;
@@ -457,7 +457,7 @@
     /// Validates a texture builtin function
     /// @param call the builtin call to validate
     /// @returns true on success, false otherwise
-    bool TextureBuiltinFunction(const sem::Call* call) const;
+    bool TextureBuiltinFn(const sem::Call* call) const;
 
     /// Validates a workgroupUniformLoad builtin function
     /// @param call the builtin call to validate
@@ -472,7 +472,7 @@
     /// Validates an optional builtin function and its required extension.
     /// @param call the builtin call to validate
     /// @returns true on success, false otherwise
-    bool RequiredExtensionForBuiltinFunction(const sem::Call* call) const;
+    bool RequiredExtensionForBuiltinFn(const sem::Call* call) const;
 
     /// Validates that 'f16' extension is enabled for f16 usage at @p source
     /// @param source the source of the f16 usage
diff --git a/src/tint/lang/wgsl/sem/BUILD.bazel b/src/tint/lang/wgsl/sem/BUILD.bazel
index 75b0dae..a014edf 100644
--- a/src/tint/lang/wgsl/sem/BUILD.bazel
+++ b/src/tint/lang/wgsl/sem/BUILD.bazel
@@ -31,8 +31,8 @@
     "behavior.cc",
     "block_statement.cc",
     "break_if_statement.cc",
-    "builtin.cc",
     "builtin_enum_expression.cc",
+    "builtin_fn.cc",
     "call.cc",
     "call_target.cc",
     "expression.cc",
@@ -64,8 +64,8 @@
     "behavior.h",
     "block_statement.h",
     "break_if_statement.h",
-    "builtin.h",
     "builtin_enum_expression.h",
+    "builtin_fn.h",
     "call.h",
     "call_target.h",
     "expression.h",
@@ -122,7 +122,7 @@
   name = "test",
   alwayslink = True,
   srcs = [
-    "builtin_test.cc",
+    "builtin_fn_test.cc",
     "diagnostic_severity_test.cc",
     "helper_test.h",
     "struct_test.cc",
diff --git a/src/tint/lang/wgsl/sem/BUILD.cmake b/src/tint/lang/wgsl/sem/BUILD.cmake
index 306ba36..6e545fa 100644
--- a/src/tint/lang/wgsl/sem/BUILD.cmake
+++ b/src/tint/lang/wgsl/sem/BUILD.cmake
@@ -36,10 +36,10 @@
   lang/wgsl/sem/block_statement.h
   lang/wgsl/sem/break_if_statement.cc
   lang/wgsl/sem/break_if_statement.h
-  lang/wgsl/sem/builtin.cc
-  lang/wgsl/sem/builtin.h
   lang/wgsl/sem/builtin_enum_expression.cc
   lang/wgsl/sem/builtin_enum_expression.h
+  lang/wgsl/sem/builtin_fn.cc
+  lang/wgsl/sem/builtin_fn.h
   lang/wgsl/sem/call.cc
   lang/wgsl/sem/call.h
   lang/wgsl/sem/call_target.cc
@@ -120,7 +120,7 @@
 # Kind:      test
 ################################################################################
 tint_add_target(tint_lang_wgsl_sem_test test
-  lang/wgsl/sem/builtin_test.cc
+  lang/wgsl/sem/builtin_fn_test.cc
   lang/wgsl/sem/diagnostic_severity_test.cc
   lang/wgsl/sem/helper_test.h
   lang/wgsl/sem/struct_test.cc
diff --git a/src/tint/lang/wgsl/sem/BUILD.gn b/src/tint/lang/wgsl/sem/BUILD.gn
index 87f0a99..2e87dae 100644
--- a/src/tint/lang/wgsl/sem/BUILD.gn
+++ b/src/tint/lang/wgsl/sem/BUILD.gn
@@ -41,10 +41,10 @@
     "block_statement.h",
     "break_if_statement.cc",
     "break_if_statement.h",
-    "builtin.cc",
-    "builtin.h",
     "builtin_enum_expression.cc",
     "builtin_enum_expression.h",
+    "builtin_fn.cc",
+    "builtin_fn.h",
     "call.cc",
     "call.h",
     "call_target.cc",
@@ -123,7 +123,7 @@
   tint_unittests_source_set("unittests") {
     testonly = true
     sources = [
-      "builtin_test.cc",
+      "builtin_fn_test.cc",
       "diagnostic_severity_test.cc",
       "helper_test.h",
       "struct_test.cc",
diff --git a/src/tint/lang/wgsl/sem/builtin.cc b/src/tint/lang/wgsl/sem/builtin.cc
deleted file mode 100644
index a3e5fa5..0000000
--- a/src/tint/lang/wgsl/sem/builtin.cc
+++ /dev/null
@@ -1,110 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Doxygen seems to trip over this file for some unknown reason. Disable.
-//! @cond Doxygen_Suppress
-
-#include "src/tint/lang/wgsl/sem/builtin.h"
-
-#include <utility>
-#include <vector>
-
-#include "src/tint/utils/containers/transform.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::sem::Builtin);
-
-namespace tint::sem {
-
-const char* Builtin::str() const {
-    return core::str(type_);
-}
-
-Builtin::Builtin(core::Function type,
-                 const core::type::Type* return_type,
-                 VectorRef<Parameter*> parameters,
-                 core::EvaluationStage eval_stage,
-                 PipelineStageSet supported_stages,
-                 bool is_deprecated,
-                 bool must_use)
-    : Base(return_type, std::move(parameters), eval_stage, must_use),
-      type_(type),
-      supported_stages_(supported_stages),
-      is_deprecated_(is_deprecated) {}
-
-Builtin::~Builtin() = default;
-
-bool Builtin::IsCoarseDerivative() const {
-    return IsCoarseDerivativeBuiltin(type_);
-}
-
-bool Builtin::IsFineDerivative() const {
-    return IsFineDerivativeBuiltin(type_);
-}
-
-bool Builtin::IsDerivative() const {
-    return IsDerivativeBuiltin(type_);
-}
-
-bool Builtin::IsTexture() const {
-    return IsTextureBuiltin(type_);
-}
-
-bool Builtin::IsImageQuery() const {
-    return IsImageQueryBuiltin(type_);
-}
-
-bool Builtin::IsDataPacking() const {
-    return IsDataPackingBuiltin(type_);
-}
-
-bool Builtin::IsDataUnpacking() const {
-    return IsDataUnpackingBuiltin(type_);
-}
-
-bool Builtin::IsBarrier() const {
-    return IsBarrierBuiltin(type_);
-}
-
-bool Builtin::IsAtomic() const {
-    return IsAtomicBuiltin(type_);
-}
-
-bool Builtin::IsDP4a() const {
-    return IsDP4aBuiltin(type_);
-}
-
-bool Builtin::IsSubgroup() const {
-    return IsSubgroupBuiltin(type_);
-}
-
-bool Builtin::HasSideEffects() const {
-    return core::HasSideEffects(type_);
-}
-
-wgsl::Extension Builtin::RequiredExtension() const {
-    if (IsDP4a()) {
-        return wgsl::Extension::kChromiumExperimentalDp4A;
-    }
-    if (IsSubgroup()) {
-        return wgsl::Extension::kChromiumExperimentalSubgroups;
-    }
-    if (type_ == core::Function::kTextureBarrier) {
-        return wgsl::Extension::kChromiumExperimentalReadWriteStorageTexture;
-    }
-    return wgsl::Extension::kUndefined;
-}
-
-}  // namespace tint::sem
-
-//! @endcond
diff --git a/src/tint/lang/wgsl/sem/builtin_fn.cc b/src/tint/lang/wgsl/sem/builtin_fn.cc
new file mode 100644
index 0000000..b7c9909
--- /dev/null
+++ b/src/tint/lang/wgsl/sem/builtin_fn.cc
@@ -0,0 +1,110 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Doxygen seems to trip over this file for some unknown reason. Disable.
+//! @cond Doxygen_Suppress
+
+#include "src/tint/lang/wgsl/sem/builtin_fn.h"
+
+#include <utility>
+#include <vector>
+
+#include "src/tint/utils/containers/transform.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::sem::BuiltinFn);
+
+namespace tint::sem {
+
+const char* BuiltinFn::str() const {
+    return core::str(fn_);
+}
+
+BuiltinFn::BuiltinFn(core::BuiltinFn type,
+                     const core::type::Type* return_type,
+                     VectorRef<Parameter*> parameters,
+                     core::EvaluationStage eval_stage,
+                     PipelineStageSet supported_stages,
+                     bool is_deprecated,
+                     bool must_use)
+    : Base(return_type, std::move(parameters), eval_stage, must_use),
+      fn_(type),
+      supported_stages_(supported_stages),
+      is_deprecated_(is_deprecated) {}
+
+BuiltinFn::~BuiltinFn() = default;
+
+bool BuiltinFn::IsCoarseDerivative() const {
+    return core::IsCoarseDerivative(fn_);
+}
+
+bool BuiltinFn::IsFineDerivative() const {
+    return core::IsFineDerivative(fn_);
+}
+
+bool BuiltinFn::IsDerivative() const {
+    return core::IsDerivative(fn_);
+}
+
+bool BuiltinFn::IsTexture() const {
+    return core::IsTexture(fn_);
+}
+
+bool BuiltinFn::IsImageQuery() const {
+    return core::IsImageQuery(fn_);
+}
+
+bool BuiltinFn::IsDataPacking() const {
+    return core::IsDataPacking(fn_);
+}
+
+bool BuiltinFn::IsDataUnpacking() const {
+    return core::IsDataUnpacking(fn_);
+}
+
+bool BuiltinFn::IsBarrier() const {
+    return core::IsBarrier(fn_);
+}
+
+bool BuiltinFn::IsAtomic() const {
+    return core::IsAtomic(fn_);
+}
+
+bool BuiltinFn::IsDP4a() const {
+    return core::IsDP4a(fn_);
+}
+
+bool BuiltinFn::IsSubgroup() const {
+    return core::IsSubgroup(fn_);
+}
+
+bool BuiltinFn::HasSideEffects() const {
+    return core::HasSideEffects(fn_);
+}
+
+wgsl::Extension BuiltinFn::RequiredExtension() const {
+    if (IsDP4a()) {
+        return wgsl::Extension::kChromiumExperimentalDp4A;
+    }
+    if (IsSubgroup()) {
+        return wgsl::Extension::kChromiumExperimentalSubgroups;
+    }
+    if (fn_ == core::BuiltinFn::kTextureBarrier) {
+        return wgsl::Extension::kChromiumExperimentalReadWriteStorageTexture;
+    }
+    return wgsl::Extension::kUndefined;
+}
+
+}  // namespace tint::sem
+
+//! @endcond
diff --git a/src/tint/lang/wgsl/sem/builtin.h b/src/tint/lang/wgsl/sem/builtin_fn.h
similarity index 82%
rename from src/tint/lang/wgsl/sem/builtin.h
rename to src/tint/lang/wgsl/sem/builtin_fn.h
index 838288a..525403c 100644
--- a/src/tint/lang/wgsl/sem/builtin.h
+++ b/src/tint/lang/wgsl/sem/builtin_fn.h
@@ -12,13 +12,13 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef SRC_TINT_LANG_WGSL_SEM_BUILTIN_H_
-#define SRC_TINT_LANG_WGSL_SEM_BUILTIN_H_
+#ifndef SRC_TINT_LANG_WGSL_SEM_BUILTIN_FN_H_
+#define SRC_TINT_LANG_WGSL_SEM_BUILTIN_FN_H_
 
 #include <string>
 #include <vector>
 
-#include "src/tint/lang/core/function.h"
+#include "src/tint/lang/core/builtin_fn.h"
 #include "src/tint/lang/wgsl/extension.h"
 #include "src/tint/lang/wgsl/sem/call_target.h"
 #include "src/tint/lang/wgsl/sem/pipeline_stage_set.h"
@@ -26,8 +26,8 @@
 
 namespace tint::sem {
 
-/// Builtin holds the semantic information for a builtin function.
-class Builtin final : public Castable<Builtin, CallTarget> {
+/// BuiltinFn holds the semantic information for a builtin function.
+class BuiltinFn final : public Castable<BuiltinFn, CallTarget> {
   public:
     /// Constructor
     /// @param type the builtin type
@@ -37,19 +37,19 @@
     /// @param supported_stages the pipeline stages that this builtin can be used in
     /// @param is_deprecated true if the particular overload is considered deprecated
     /// @param must_use true if the builtin was annotated with `@must_use`
-    Builtin(core::Function type,
-            const core::type::Type* return_type,
-            VectorRef<Parameter*> parameters,
-            core::EvaluationStage eval_stage,
-            PipelineStageSet supported_stages,
-            bool is_deprecated,
-            bool must_use);
+    BuiltinFn(core::BuiltinFn type,
+              const core::type::Type* return_type,
+              VectorRef<Parameter*> parameters,
+              core::EvaluationStage eval_stage,
+              PipelineStageSet supported_stages,
+              bool is_deprecated,
+              bool must_use);
 
     /// Destructor
-    ~Builtin() override;
+    ~BuiltinFn() override;
 
     /// @return the type of the builtin
-    core::Function Type() const { return type_; }
+    core::BuiltinFn Fn() const { return fn_; }
 
     /// @return the pipeline stages that this builtin can be used in
     PipelineStageSet SupportedStages() const { return supported_stages_; }
@@ -106,11 +106,11 @@
 
     /// @return the hash code for this object
     std::size_t HashCode() const {
-        return Hash(Type(), SupportedStages(), ReturnType(), Parameters(), IsDeprecated());
+        return Hash(Fn(), SupportedStages(), ReturnType(), Parameters(), IsDeprecated());
     }
 
   private:
-    const core::Function type_;
+    const core::BuiltinFn fn_;
     const PipelineStageSet supported_stages_;
     const bool is_deprecated_;
 };
@@ -123,4 +123,4 @@
 
 }  // namespace tint::sem
 
-#endif  // SRC_TINT_LANG_WGSL_SEM_BUILTIN_H_
+#endif  // SRC_TINT_LANG_WGSL_SEM_BUILTIN_FN_H_
diff --git a/src/tint/lang/wgsl/sem/builtin_fn_test.cc b/src/tint/lang/wgsl/sem/builtin_fn_test.cc
new file mode 100644
index 0000000..f10a9f9
--- /dev/null
+++ b/src/tint/lang/wgsl/sem/builtin_fn_test.cc
@@ -0,0 +1,127 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/lang/wgsl/sem/builtin_fn.h"
+
+#include "gtest/gtest.h"
+
+namespace tint::sem {
+namespace {
+
+struct BuiltinData {
+    const char* name;
+    core::BuiltinFn builtin;
+};
+
+inline std::ostream& operator<<(std::ostream& out, BuiltinData data) {
+    out << data.name;
+    return out;
+}
+
+using BuiltinFunctionTest = testing::TestWithParam<BuiltinData>;
+
+TEST_P(BuiltinFunctionTest, Parse) {
+    auto param = GetParam();
+    EXPECT_EQ(core::ParseBuiltinFn(param.name), param.builtin);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    BuiltinFunctionTest,
+    BuiltinFunctionTest,
+    testing::Values(BuiltinData{"abs", core::BuiltinFn::kAbs},
+                    BuiltinData{"acos", core::BuiltinFn::kAcos},
+                    BuiltinData{"all", core::BuiltinFn::kAll},
+                    BuiltinData{"any", core::BuiltinFn::kAny},
+                    BuiltinData{"arrayLength", core::BuiltinFn::kArrayLength},
+                    BuiltinData{"asin", core::BuiltinFn::kAsin},
+                    BuiltinData{"atan", core::BuiltinFn::kAtan},
+                    BuiltinData{"atan2", core::BuiltinFn::kAtan2},
+                    BuiltinData{"ceil", core::BuiltinFn::kCeil},
+                    BuiltinData{"clamp", core::BuiltinFn::kClamp},
+                    BuiltinData{"cos", core::BuiltinFn::kCos},
+                    BuiltinData{"cosh", core::BuiltinFn::kCosh},
+                    BuiltinData{"countOneBits", core::BuiltinFn::kCountOneBits},
+                    BuiltinData{"cross", core::BuiltinFn::kCross},
+                    BuiltinData{"determinant", core::BuiltinFn::kDeterminant},
+                    BuiltinData{"distance", core::BuiltinFn::kDistance},
+                    BuiltinData{"dot", core::BuiltinFn::kDot},
+                    BuiltinData{"dot4I8Packed", core::BuiltinFn::kDot4I8Packed},
+                    BuiltinData{"dot4U8Packed", core::BuiltinFn::kDot4U8Packed},
+                    BuiltinData{"dpdx", core::BuiltinFn::kDpdx},
+                    BuiltinData{"dpdxCoarse", core::BuiltinFn::kDpdxCoarse},
+                    BuiltinData{"dpdxFine", core::BuiltinFn::kDpdxFine},
+                    BuiltinData{"dpdy", core::BuiltinFn::kDpdy},
+                    BuiltinData{"dpdyCoarse", core::BuiltinFn::kDpdyCoarse},
+                    BuiltinData{"dpdyFine", core::BuiltinFn::kDpdyFine},
+                    BuiltinData{"exp", core::BuiltinFn::kExp},
+                    BuiltinData{"exp2", core::BuiltinFn::kExp2},
+                    BuiltinData{"faceForward", core::BuiltinFn::kFaceForward},
+                    BuiltinData{"floor", core::BuiltinFn::kFloor},
+                    BuiltinData{"fma", core::BuiltinFn::kFma},
+                    BuiltinData{"fract", core::BuiltinFn::kFract},
+                    BuiltinData{"frexp", core::BuiltinFn::kFrexp},
+                    BuiltinData{"fwidth", core::BuiltinFn::kFwidth},
+                    BuiltinData{"fwidthCoarse", core::BuiltinFn::kFwidthCoarse},
+                    BuiltinData{"fwidthFine", core::BuiltinFn::kFwidthFine},
+                    BuiltinData{"inverseSqrt", core::BuiltinFn::kInverseSqrt},
+                    BuiltinData{"ldexp", core::BuiltinFn::kLdexp},
+                    BuiltinData{"length", core::BuiltinFn::kLength},
+                    BuiltinData{"log", core::BuiltinFn::kLog},
+                    BuiltinData{"log2", core::BuiltinFn::kLog2},
+                    BuiltinData{"max", core::BuiltinFn::kMax},
+                    BuiltinData{"min", core::BuiltinFn::kMin},
+                    BuiltinData{"mix", core::BuiltinFn::kMix},
+                    BuiltinData{"modf", core::BuiltinFn::kModf},
+                    BuiltinData{"normalize", core::BuiltinFn::kNormalize},
+                    BuiltinData{"pow", core::BuiltinFn::kPow},
+                    BuiltinData{"reflect", core::BuiltinFn::kReflect},
+                    BuiltinData{"reverseBits", core::BuiltinFn::kReverseBits},
+                    BuiltinData{"round", core::BuiltinFn::kRound},
+                    BuiltinData{"select", core::BuiltinFn::kSelect},
+                    BuiltinData{"sign", core::BuiltinFn::kSign},
+                    BuiltinData{"sin", core::BuiltinFn::kSin},
+                    BuiltinData{"sinh", core::BuiltinFn::kSinh},
+                    BuiltinData{"smoothstep", core::BuiltinFn::kSmoothstep},
+                    BuiltinData{"sqrt", core::BuiltinFn::kSqrt},
+                    BuiltinData{"step", core::BuiltinFn::kStep},
+                    BuiltinData{"storageBarrier", core::BuiltinFn::kStorageBarrier},
+                    BuiltinData{"tan", core::BuiltinFn::kTan},
+                    BuiltinData{"tanh", core::BuiltinFn::kTanh},
+                    BuiltinData{"textureDimensions", core::BuiltinFn::kTextureDimensions},
+                    BuiltinData{"textureLoad", core::BuiltinFn::kTextureLoad},
+                    BuiltinData{"textureNumLayers", core::BuiltinFn::kTextureNumLayers},
+                    BuiltinData{"textureNumLevels", core::BuiltinFn::kTextureNumLevels},
+                    BuiltinData{"textureNumSamples", core::BuiltinFn::kTextureNumSamples},
+                    BuiltinData{"textureSample", core::BuiltinFn::kTextureSample},
+                    BuiltinData{"textureSampleBias", core::BuiltinFn::kTextureSampleBias},
+                    BuiltinData{"textureSampleCompare", core::BuiltinFn::kTextureSampleCompare},
+                    BuiltinData{"textureSampleCompareLevel",
+                                core::BuiltinFn::kTextureSampleCompareLevel},
+                    BuiltinData{"textureSampleGrad", core::BuiltinFn::kTextureSampleGrad},
+                    BuiltinData{"textureSampleLevel", core::BuiltinFn::kTextureSampleLevel},
+                    BuiltinData{"trunc", core::BuiltinFn::kTrunc},
+                    BuiltinData{"unpack2x16float", core::BuiltinFn::kUnpack2X16Float},
+                    BuiltinData{"unpack2x16snorm", core::BuiltinFn::kUnpack2X16Snorm},
+                    BuiltinData{"unpack2x16unorm", core::BuiltinFn::kUnpack2X16Unorm},
+                    BuiltinData{"unpack4x8snorm", core::BuiltinFn::kUnpack4X8Snorm},
+                    BuiltinData{"unpack4x8unorm", core::BuiltinFn::kUnpack4X8Unorm},
+                    BuiltinData{"workgroupBarrier", core::BuiltinFn::kWorkgroupBarrier},
+                    BuiltinData{"workgroupUniformLoad", core::BuiltinFn::kWorkgroupUniformLoad}));
+
+TEST_F(BuiltinFunctionTest, ParseNoMatch) {
+    EXPECT_EQ(core::ParseBuiltinFn("not_builtin"), core::BuiltinFn::kNone);
+}
+
+}  // namespace
+}  // namespace tint::sem
diff --git a/src/tint/lang/wgsl/sem/builtin_test.cc b/src/tint/lang/wgsl/sem/builtin_test.cc
deleted file mode 100644
index ae77105..0000000
--- a/src/tint/lang/wgsl/sem/builtin_test.cc
+++ /dev/null
@@ -1,127 +0,0 @@
-// Copyright 2021 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/tint/lang/wgsl/sem/builtin.h"
-
-#include "gtest/gtest.h"
-
-namespace tint::sem {
-namespace {
-
-struct BuiltinData {
-    const char* name;
-    core::Function builtin;
-};
-
-inline std::ostream& operator<<(std::ostream& out, BuiltinData data) {
-    out << data.name;
-    return out;
-}
-
-using BuiltinFunctionTest = testing::TestWithParam<BuiltinData>;
-
-TEST_P(BuiltinFunctionTest, Parse) {
-    auto param = GetParam();
-    EXPECT_EQ(core::ParseFunction(param.name), param.builtin);
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    BuiltinFunctionTest,
-    BuiltinFunctionTest,
-    testing::Values(BuiltinData{"abs", core::Function::kAbs},
-                    BuiltinData{"acos", core::Function::kAcos},
-                    BuiltinData{"all", core::Function::kAll},
-                    BuiltinData{"any", core::Function::kAny},
-                    BuiltinData{"arrayLength", core::Function::kArrayLength},
-                    BuiltinData{"asin", core::Function::kAsin},
-                    BuiltinData{"atan", core::Function::kAtan},
-                    BuiltinData{"atan2", core::Function::kAtan2},
-                    BuiltinData{"ceil", core::Function::kCeil},
-                    BuiltinData{"clamp", core::Function::kClamp},
-                    BuiltinData{"cos", core::Function::kCos},
-                    BuiltinData{"cosh", core::Function::kCosh},
-                    BuiltinData{"countOneBits", core::Function::kCountOneBits},
-                    BuiltinData{"cross", core::Function::kCross},
-                    BuiltinData{"determinant", core::Function::kDeterminant},
-                    BuiltinData{"distance", core::Function::kDistance},
-                    BuiltinData{"dot", core::Function::kDot},
-                    BuiltinData{"dot4I8Packed", core::Function::kDot4I8Packed},
-                    BuiltinData{"dot4U8Packed", core::Function::kDot4U8Packed},
-                    BuiltinData{"dpdx", core::Function::kDpdx},
-                    BuiltinData{"dpdxCoarse", core::Function::kDpdxCoarse},
-                    BuiltinData{"dpdxFine", core::Function::kDpdxFine},
-                    BuiltinData{"dpdy", core::Function::kDpdy},
-                    BuiltinData{"dpdyCoarse", core::Function::kDpdyCoarse},
-                    BuiltinData{"dpdyFine", core::Function::kDpdyFine},
-                    BuiltinData{"exp", core::Function::kExp},
-                    BuiltinData{"exp2", core::Function::kExp2},
-                    BuiltinData{"faceForward", core::Function::kFaceForward},
-                    BuiltinData{"floor", core::Function::kFloor},
-                    BuiltinData{"fma", core::Function::kFma},
-                    BuiltinData{"fract", core::Function::kFract},
-                    BuiltinData{"frexp", core::Function::kFrexp},
-                    BuiltinData{"fwidth", core::Function::kFwidth},
-                    BuiltinData{"fwidthCoarse", core::Function::kFwidthCoarse},
-                    BuiltinData{"fwidthFine", core::Function::kFwidthFine},
-                    BuiltinData{"inverseSqrt", core::Function::kInverseSqrt},
-                    BuiltinData{"ldexp", core::Function::kLdexp},
-                    BuiltinData{"length", core::Function::kLength},
-                    BuiltinData{"log", core::Function::kLog},
-                    BuiltinData{"log2", core::Function::kLog2},
-                    BuiltinData{"max", core::Function::kMax},
-                    BuiltinData{"min", core::Function::kMin},
-                    BuiltinData{"mix", core::Function::kMix},
-                    BuiltinData{"modf", core::Function::kModf},
-                    BuiltinData{"normalize", core::Function::kNormalize},
-                    BuiltinData{"pow", core::Function::kPow},
-                    BuiltinData{"reflect", core::Function::kReflect},
-                    BuiltinData{"reverseBits", core::Function::kReverseBits},
-                    BuiltinData{"round", core::Function::kRound},
-                    BuiltinData{"select", core::Function::kSelect},
-                    BuiltinData{"sign", core::Function::kSign},
-                    BuiltinData{"sin", core::Function::kSin},
-                    BuiltinData{"sinh", core::Function::kSinh},
-                    BuiltinData{"smoothstep", core::Function::kSmoothstep},
-                    BuiltinData{"sqrt", core::Function::kSqrt},
-                    BuiltinData{"step", core::Function::kStep},
-                    BuiltinData{"storageBarrier", core::Function::kStorageBarrier},
-                    BuiltinData{"tan", core::Function::kTan},
-                    BuiltinData{"tanh", core::Function::kTanh},
-                    BuiltinData{"textureDimensions", core::Function::kTextureDimensions},
-                    BuiltinData{"textureLoad", core::Function::kTextureLoad},
-                    BuiltinData{"textureNumLayers", core::Function::kTextureNumLayers},
-                    BuiltinData{"textureNumLevels", core::Function::kTextureNumLevels},
-                    BuiltinData{"textureNumSamples", core::Function::kTextureNumSamples},
-                    BuiltinData{"textureSample", core::Function::kTextureSample},
-                    BuiltinData{"textureSampleBias", core::Function::kTextureSampleBias},
-                    BuiltinData{"textureSampleCompare", core::Function::kTextureSampleCompare},
-                    BuiltinData{"textureSampleCompareLevel",
-                                core::Function::kTextureSampleCompareLevel},
-                    BuiltinData{"textureSampleGrad", core::Function::kTextureSampleGrad},
-                    BuiltinData{"textureSampleLevel", core::Function::kTextureSampleLevel},
-                    BuiltinData{"trunc", core::Function::kTrunc},
-                    BuiltinData{"unpack2x16float", core::Function::kUnpack2X16Float},
-                    BuiltinData{"unpack2x16snorm", core::Function::kUnpack2X16Snorm},
-                    BuiltinData{"unpack2x16unorm", core::Function::kUnpack2X16Unorm},
-                    BuiltinData{"unpack4x8snorm", core::Function::kUnpack4X8Snorm},
-                    BuiltinData{"unpack4x8unorm", core::Function::kUnpack4X8Unorm},
-                    BuiltinData{"workgroupBarrier", core::Function::kWorkgroupBarrier},
-                    BuiltinData{"workgroupUniformLoad", core::Function::kWorkgroupUniformLoad}));
-
-TEST_F(BuiltinFunctionTest, ParseNoMatch) {
-    EXPECT_EQ(core::ParseFunction("not_builtin"), core::Function::kNone);
-}
-
-}  // namespace
-}  // namespace tint::sem
diff --git a/src/tint/lang/wgsl/sem/call.h b/src/tint/lang/wgsl/sem/call.h
index 419a5ae..3956589 100644
--- a/src/tint/lang/wgsl/sem/call.h
+++ b/src/tint/lang/wgsl/sem/call.h
@@ -18,7 +18,7 @@
 #include <vector>
 
 #include "src/tint/lang/wgsl/ast/call_expression.h"
-#include "src/tint/lang/wgsl/sem/builtin.h"
+#include "src/tint/lang/wgsl/sem/builtin_fn.h"
 #include "src/tint/lang/wgsl/sem/value_expression.h"
 #include "src/tint/utils/containers/vector.h"
 
diff --git a/src/tint/lang/wgsl/sem/diagnostic_severity_test.cc b/src/tint/lang/wgsl/sem/diagnostic_severity_test.cc
index adb17f2..511a408 100644
--- a/src/tint/lang/wgsl/sem/diagnostic_severity_test.cc
+++ b/src/tint/lang/wgsl/sem/diagnostic_severity_test.cc
@@ -130,7 +130,7 @@
         auto* bar = Func("bar", {}, ty.void_(), tint::Vector{return_bar});
 
         auto p = Build();
-        EXPECT_TRUE(p.IsValid()) << p.Diagnostics().str();
+        EXPECT_TRUE(p.IsValid()) << p.Diagnostics();
 
         EXPECT_EQ(p.Sem().DiagnosticSeverity(foo, rule), func_severity);
         EXPECT_EQ(p.Sem().DiagnosticSeverity(block_1, rule), block_severity);
diff --git a/src/tint/lang/wgsl/sem/function.h b/src/tint/lang/wgsl/sem/function.h
index c21a76e..585ede0 100644
--- a/src/tint/lang/wgsl/sem/function.h
+++ b/src/tint/lang/wgsl/sem/function.h
@@ -35,7 +35,7 @@
 class ReturnStatement;
 }  // namespace tint::ast
 namespace tint::sem {
-class Builtin;
+class BuiltinFn;
 class Variable;
 }  // namespace tint::sem
 
@@ -117,13 +117,13 @@
     }
 
     /// @returns the list of builtins that this function directly calls.
-    const UniqueVector<const Builtin*, 4>& DirectlyCalledBuiltins() const {
+    const UniqueVector<const BuiltinFn*, 4>& DirectlyCalledBuiltins() const {
         return directly_called_builtins_;
     }
 
     /// Records that this function transitively calls `builtin`.
     /// @param builtin the builtin this function directly calls
-    void AddDirectlyCalledBuiltin(const Builtin* builtin) {
+    void AddDirectlyCalledBuiltin(const BuiltinFn* builtin) {
         directly_called_builtins_.Add(builtin);
     }
 
@@ -288,7 +288,7 @@
     UniqueVector<const GlobalVariable*, 4> directly_referenced_globals_;
     UniqueVector<const GlobalVariable*, 8> transitively_referenced_globals_;
     UniqueVector<const Function*, 8> transitively_called_functions_;
-    UniqueVector<const Builtin*, 4> directly_called_builtins_;
+    UniqueVector<const BuiltinFn*, 4> directly_called_builtins_;
     UniqueVector<VariablePair, 8> texture_sampler_pairs_;
     std::vector<const Call*> direct_calls_;
     std::vector<const Call*> callsites_;
diff --git a/src/tint/lang/wgsl/sem/helper_test.h b/src/tint/lang/wgsl/sem/helper_test.h
index 583cbde..ece8694 100644
--- a/src/tint/lang/wgsl/sem/helper_test.h
+++ b/src/tint/lang/wgsl/sem/helper_test.h
@@ -30,9 +30,9 @@
     /// Builds and returns the program. Must only be called once per test
     /// @return the built program
     Program Build() {
-        [&] {
-            ASSERT_TRUE(IsValid()) << "Builder program is not valid\n" << Diagnostics().str();
-        }();
+        if (!IsValid()) {
+            ADD_FAILURE() << "ProgramBuilder is not valid: " << Diagnostics();
+        }
         return resolver::Resolve(*this);
     }
 };
diff --git a/src/tint/lang/wgsl/writer/ast_printer/ast_printer.cc b/src/tint/lang/wgsl/writer/ast_printer/ast_printer.cc
index 815442a..f3b8c5d 100644
--- a/src/tint/lang/wgsl/writer/ast_printer/ast_printer.cc
+++ b/src/tint/lang/wgsl/writer/ast_printer/ast_printer.cc
@@ -78,18 +78,18 @@
 
 namespace tint::wgsl::writer {
 
-ASTPrinter::ASTPrinter(const Program* program) : program_(program) {}
+ASTPrinter::ASTPrinter(const Program& program) : program_(program) {}
 
 ASTPrinter::~ASTPrinter() = default;
 
 bool ASTPrinter::Generate() {
     // Generate directives before any other global declarations.
     bool has_directives = false;
-    for (auto enable : program_->AST().Enables()) {
+    for (auto enable : program_.AST().Enables()) {
         EmitEnable(enable);
         has_directives = true;
     }
-    for (auto diagnostic : program_->AST().DiagnosticDirectives()) {
+    for (auto diagnostic : program_.AST().DiagnosticDirectives()) {
         auto out = Line();
         EmitDiagnosticControl(out, diagnostic->control);
         out << ";";
@@ -99,7 +99,7 @@
         Line();
     }
     // Generate global declarations in the order they appear in the module.
-    for (auto* decl : program_->AST().GlobalDeclarations()) {
+    for (auto* decl : program_.AST().GlobalDeclarations()) {
         if (decl->IsAnyOf<ast::DiagnosticDirective, ast::Enable>()) {
             continue;
         }
@@ -110,7 +110,7 @@
             [&](const ast::Variable* var) { return EmitVariable(Line(), var); },
             [&](const ast::ConstAssert* ca) { return EmitConstAssert(ca); },
             [&](Default) { TINT_UNREACHABLE(); });
-        if (decl != program_->AST().GlobalDeclarations().Back()) {
+        if (decl != program_.AST().GlobalDeclarations().Back()) {
             Line();
         }
     }
@@ -361,7 +361,7 @@
     for (auto* mem : str->members) {
         // TODO(crbug.com/tint/798) move the @offset attribute handling to the transform::Wgsl
         // sanitizer.
-        if (auto* mem_sem = program_->Sem().Get(mem)) {
+        if (auto* mem_sem = program_.Sem().Get(mem)) {
             offset = tint::RoundUp(mem_sem->Align(), offset);
             if (uint32_t padding = mem_sem->Offset() - offset) {
                 add_padding(padding);
diff --git a/src/tint/lang/wgsl/writer/ast_printer/ast_printer.h b/src/tint/lang/wgsl/writer/ast_printer/ast_printer.h
index 525b683..4b3dffe 100644
--- a/src/tint/lang/wgsl/writer/ast_printer/ast_printer.h
+++ b/src/tint/lang/wgsl/writer/ast_printer/ast_printer.h
@@ -77,7 +77,7 @@
   public:
     /// Constructor
     /// @param program the program
-    explicit ASTPrinter(const Program* program);
+    explicit ASTPrinter(const Program& program);
     ~ASTPrinter() override;
 
     /// Generates the result data
@@ -219,7 +219,7 @@
     void EmitAttributes(StringStream& out, VectorRef<const ast::Attribute*> attrs);
 
   private:
-    Program const* const program_;
+    const Program& program_;
 };
 
 }  // namespace tint::wgsl::writer
diff --git a/src/tint/lang/wgsl/writer/ast_printer/helper_test.h b/src/tint/lang/wgsl/writer/ast_printer/helper_test.h
index 861f42c..644b2f5 100644
--- a/src/tint/lang/wgsl/writer/ast_printer/helper_test.h
+++ b/src/tint/lang/wgsl/writer/ast_printer/helper_test.h
@@ -47,8 +47,10 @@
         } else {
             program = std::make_unique<Program>(std::move(*this));
         }
-        [&] { ASSERT_TRUE(program->IsValid()) << program->Diagnostics().str(); }();
-        gen_ = std::make_unique<ASTPrinter>(program.get());
+        if (!program->IsValid()) {
+            ADD_FAILURE() << program->Diagnostics();
+        }
+        gen_ = std::make_unique<ASTPrinter>(*program);
         return *gen_;
     }
 
diff --git a/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program.cc b/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program.cc
index 726eb6d..4623d29 100644
--- a/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program.cc
+++ b/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program.cc
@@ -18,7 +18,7 @@
 #include <tuple>
 #include <utility>
 
-#include "src/tint/lang/core/builtin.h"
+#include "src/tint/lang/core/builtin_type.h"
 #include "src/tint/lang/core/constant/splat.h"
 #include "src/tint/lang/core/fluent_types.h"
 #include "src/tint/lang/core/ir/access.h"
@@ -878,7 +878,7 @@
                 auto el = Type(v->type());
                 if (v->Packed()) {
                     TINT_ASSERT(v->Width() == 3u);
-                    return b.ty(core::Builtin::kPackedVec3, el);
+                    return b.ty(core::BuiltinType::kPackedVec3, el);
                 } else {
                     return b.ty.vec(el, v->Width());
                 }
@@ -1141,20 +1141,20 @@
         return b.IndexAccessor(expr, Expr(index));
     }
 
-    bool RequiresDerivativeUniformity(core::Function fn) {
+    bool RequiresDerivativeUniformity(core::BuiltinFn fn) {
         switch (fn) {
-            case core::Function::kDpdxCoarse:
-            case core::Function::kDpdyCoarse:
-            case core::Function::kFwidthCoarse:
-            case core::Function::kDpdxFine:
-            case core::Function::kDpdyFine:
-            case core::Function::kFwidthFine:
-            case core::Function::kDpdx:
-            case core::Function::kDpdy:
-            case core::Function::kFwidth:
-            case core::Function::kTextureSample:
-            case core::Function::kTextureSampleBias:
-            case core::Function::kTextureSampleCompare:
+            case core::BuiltinFn::kDpdxCoarse:
+            case core::BuiltinFn::kDpdyCoarse:
+            case core::BuiltinFn::kFwidthCoarse:
+            case core::BuiltinFn::kDpdxFine:
+            case core::BuiltinFn::kDpdyFine:
+            case core::BuiltinFn::kFwidthFine:
+            case core::BuiltinFn::kDpdx:
+            case core::BuiltinFn::kDpdy:
+            case core::BuiltinFn::kFwidth:
+            case core::BuiltinFn::kTextureSample:
+            case core::BuiltinFn::kTextureSampleBias:
+            case core::BuiltinFn::kTextureSampleCompare:
                 return true;
             default:
                 return false;
diff --git a/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program_test.cc b/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program_test.cc
index d4d758b..44ffdd2 100644
--- a/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program_test.cc
+++ b/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program_test.cc
@@ -35,11 +35,11 @@
     auto output_program = IRToProgram(mod);
     if (!output_program.IsValid()) {
         result.err = output_program.Diagnostics().str();
-        result.ast = Program::printer(&output_program);
+        result.ast = Program::printer(output_program);
         return result;
     }
 
-    auto output = wgsl::writer::Generate(&output_program, {});
+    auto output = wgsl::writer::Generate(output_program, {});
     if (!output) {
         std::stringstream ss;
         ss << "wgsl::Generate() errored: " << output.Failure();
diff --git a/src/tint/lang/wgsl/writer/ir_to_program/rename_conflicts_test.cc b/src/tint/lang/wgsl/writer/ir_to_program/rename_conflicts_test.cc
index fcabf9a..e5866f4 100644
--- a/src/tint/lang/wgsl/writer/ir_to_program/rename_conflicts_test.cc
+++ b/src/tint/lang/wgsl/writer/ir_to_program/rename_conflicts_test.cc
@@ -1019,7 +1019,7 @@
 
     auto* fn = b.Function("f", ty.i32());
     b.Append(fn->Block(), [&] {  //
-        auto* res = b.Call(ty.i32(), core::Function::kMax, 1_i, 2_i)->Result();
+        auto* res = b.Call(ty.i32(), core::BuiltinFn::kMax, 1_i, 2_i)->Result();
         b.Return(fn, res);
     });
 
@@ -1052,7 +1052,7 @@
 
     auto* fn = b.Function("f", ty.i32());
     b.Append(fn->Block(), [&] {  //
-        auto* res = b.Call(ty.i32(), core::Function::kMax, 1_i, 2_i)->Result();
+        auto* res = b.Call(ty.i32(), core::BuiltinFn::kMax, 1_i, 2_i)->Result();
         b.Return(fn, res);
     });
 
diff --git a/src/tint/lang/wgsl/writer/syntax_tree_printer/syntax_tree_printer.cc b/src/tint/lang/wgsl/writer/syntax_tree_printer/syntax_tree_printer.cc
index 16735f2..85e88b8 100644
--- a/src/tint/lang/wgsl/writer/syntax_tree_printer/syntax_tree_printer.cc
+++ b/src/tint/lang/wgsl/writer/syntax_tree_printer/syntax_tree_printer.cc
@@ -76,13 +76,13 @@
 
 namespace tint::wgsl::writer {
 
-SyntaxTreePrinter::SyntaxTreePrinter(const Program* program) : program_(program) {}
+SyntaxTreePrinter::SyntaxTreePrinter(const Program& program) : program_(program) {}
 
 SyntaxTreePrinter::~SyntaxTreePrinter() = default;
 
 bool SyntaxTreePrinter::Generate() {
     // Generate global declarations in the order they appear in the module.
-    for (auto* decl : program_->AST().GlobalDeclarations()) {
+    for (auto* decl : program_.AST().GlobalDeclarations()) {
         Switch(
             decl,  //
             [&](const ast::DiagnosticDirective* dd) { EmitDiagnosticControl(dd->control); },
@@ -93,7 +93,7 @@
             [&](const ast::ConstAssert* ca) { EmitConstAssert(ca); },
             [&](Default) { TINT_UNREACHABLE(); });
 
-        if (decl != program_->AST().GlobalDeclarations().Back()) {
+        if (decl != program_.AST().GlobalDeclarations().Back()) {
             Line();
         }
     }
diff --git a/src/tint/lang/wgsl/writer/syntax_tree_printer/syntax_tree_printer.h b/src/tint/lang/wgsl/writer/syntax_tree_printer/syntax_tree_printer.h
index 4ef93a1..aff7fca 100644
--- a/src/tint/lang/wgsl/writer/syntax_tree_printer/syntax_tree_printer.h
+++ b/src/tint/lang/wgsl/writer/syntax_tree_printer/syntax_tree_printer.h
@@ -76,7 +76,7 @@
   public:
     /// Constructor
     /// @param program the program
-    explicit SyntaxTreePrinter(const Program* program);
+    explicit SyntaxTreePrinter(const Program& program);
     ~SyntaxTreePrinter() override;
 
     /// Generates the result data
@@ -202,7 +202,7 @@
     void EmitAttributes(VectorRef<const ast::Attribute*> attrs);
 
   private:
-    Program const* const program_;
+    const Program& program_;
 };
 
 }  // namespace tint::wgsl::writer
diff --git a/src/tint/lang/wgsl/writer/writer.cc b/src/tint/lang/wgsl/writer/writer.cc
index 1a7204a..48cdca0 100644
--- a/src/tint/lang/wgsl/writer/writer.cc
+++ b/src/tint/lang/wgsl/writer/writer.cc
@@ -26,7 +26,7 @@
 
 namespace tint::wgsl::writer {
 
-Result<Output, std::string> Generate(const Program* program, const Options& options) {
+Result<Output, std::string> Generate(const Program& program, const Options& options) {
     (void)options;
 
     Output output;
diff --git a/src/tint/lang/wgsl/writer/writer.h b/src/tint/lang/wgsl/writer/writer.h
index 135388e..f1e6988 100644
--- a/src/tint/lang/wgsl/writer/writer.h
+++ b/src/tint/lang/wgsl/writer/writer.h
@@ -33,7 +33,7 @@
 /// @param program the program to translate to WGSL
 /// @param options the configuration options to use when generating WGSL
 /// @returns the resulting WGSL, or an error string
-Result<Output, std::string> Generate(const Program* program, const Options& options);
+Result<Output, std::string> Generate(const Program& program, const Options& options);
 
 }  // namespace tint::wgsl::writer
 
diff --git a/src/tint/lang/wgsl/writer/writer_bench.cc b/src/tint/lang/wgsl/writer/writer_bench.cc
index 8996be2..c3fd5d4 100644
--- a/src/tint/lang/wgsl/writer/writer_bench.cc
+++ b/src/tint/lang/wgsl/writer/writer_bench.cc
@@ -28,7 +28,7 @@
     }
     auto& program = std::get<bench::ProgramAndFile>(res).program;
     for (auto _ : state) {
-        auto res = Generate(&program, {});
+        auto res = Generate(program, {});
         if (!res) {
             state.SkipWithError(res.Failure().c_str());
         }