[Tint] Cleanup Builtin Value classes

Remove the BuiltinValueName AST node class and the BuiltinAttribute
semantic node class which were just added in:
https://dawn-review.googlesource.com/c/dawn/+/197477

Move parsing of the builtin value names to the parser rather than the
resolver and update tests.

Bug: 42251275
Change-Id: I3552617e314098a578588648241e698fb628be0c
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/197775
Reviewed-by: dan sinclair <dsinclair@chromium.org>
Commit-Queue: Natalie Chouinard <chouinard@google.com>
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 bbbf006..1552e36 100644
--- a/src/tint/lang/glsl/writer/ast_printer/ast_printer.cc
+++ b/src/tint/lang/glsl/writer/ast_printer/ast_printer.cc
@@ -2086,9 +2086,8 @@
     auto* decl = var->Declaration();
 
     if (auto* attr = ast::GetAttribute<ast::BuiltinAttribute>(decl->attributes)) {
-        auto builtin = builder_.Sem().Get(attr)->Value();
         // Use of gl_SampleID requires the GL_OES_sample_variables extension
-        if (RequiresOESSampleVariables(builtin)) {
+        if (RequiresOESSampleVariables(attr->builtin)) {
             requires_oes_sample_variables_ = true;
         }
         // Do not emit builtin (gl_) variables.
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 bee8ba5..24c718f 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
@@ -52,7 +52,7 @@
 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 (attr->builtin == core::BuiltinValue::kNumWorkgroups) {
                 return true;
             }
         }
diff --git a/src/tint/lang/hlsl/writer/ast_raise/pixel_local.cc b/src/tint/lang/hlsl/writer/ast_raise/pixel_local.cc
index ab29724..128cfef 100644
--- a/src/tint/lang/hlsl/writer/ast_raise/pixel_local.cc
+++ b/src/tint/lang/hlsl/writer/ast_raise/pixel_local.cc
@@ -441,8 +441,7 @@
             // 2. `@builtin(position)` is declared as an individual input parameter
             if (auto* attribute = ast::GetAttribute<ast::BuiltinAttribute>(
                     parameter->Declaration()->attributes)) {
-                auto builtin = sem.Get(attribute)->Value();
-                if (builtin == core::BuiltinValue::kPosition) {
+                if (attribute->builtin == core::BuiltinValue::kPosition) {
                     return b.Decl(
                         b.Let(variable_with_position_symbol, b.Expr(new_entry_point_params[i])));
                 }
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 4788c8e..c1d12f2 100644
--- a/src/tint/lang/msl/writer/ast_printer/ast_printer.cc
+++ b/src/tint/lang/msl/writer/ast_printer/ast_printer.cc
@@ -2063,11 +2063,10 @@
                         if (!builtin_attr) {
                             continue;
                         }
-                        auto builtin = builder_.Sem().Get(builtin_attr)->Value();
 
                         builtin_found = true;
 
-                        auto name = BuiltinToAttribute(builtin);
+                        auto name = BuiltinToAttribute(builtin_attr->builtin);
                         if (name.empty()) {
                             diagnostics_.AddError(Source{}) << "unknown builtin";
                             return false;
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 77f401c..877b671 100644
--- a/src/tint/lang/msl/writer/ast_raise/subgroup_ballot.cc
+++ b/src/tint/lang/msl/writer/ast_raise/subgroup_ballot.cc
@@ -157,7 +157,7 @@
         Symbol subgroup_size;
         for (auto* param : ep->params) {
             auto* builtin = ast::GetAttribute<ast::BuiltinAttribute>(param->attributes);
-            if (builtin && src.Sem().Get(builtin)->Value() == core::BuiltinValue::kSubgroupSize) {
+            if (builtin && builtin->builtin == core::BuiltinValue::kSubgroupSize) {
                 subgroup_size = ctx.Clone(param->name->symbol);
             }
         }
diff --git a/src/tint/lang/spirv/reader/ast_lower/pass_workgroup_id_as_argument.cc b/src/tint/lang/spirv/reader/ast_lower/pass_workgroup_id_as_argument.cc
index 8424e0b..63308cb 100644
--- a/src/tint/lang/spirv/reader/ast_lower/pass_workgroup_id_as_argument.cc
+++ b/src/tint/lang/spirv/reader/ast_lower/pass_workgroup_id_as_argument.cc
@@ -70,7 +70,7 @@
                 for (auto* param : func->params) {
                     if (auto* builtin =
                             ast::GetAttribute<ast::BuiltinAttribute>(param->attributes)) {
-                        if (sem.Get(builtin)->Value() == core::BuiltinValue::kWorkgroupId) {
+                        if (builtin->builtin == core::BuiltinValue::kWorkgroupId) {
                             ProcessBuiltin(func, param);
                             made_changes = true;
                         }
diff --git a/src/tint/lang/spirv/writer/ast_printer/builder.cc b/src/tint/lang/spirv/writer/ast_printer/builder.cc
index 40041a5..2568113 100644
--- a/src/tint/lang/spirv/writer/ast_printer/builder.cc
+++ b/src/tint/lang/spirv/writer/ast_printer/builder.cc
@@ -495,8 +495,7 @@
     }
 
     for (auto it : func_sem->TransitivelyReferencedBuiltinVariables()) {
-        auto builtin = builder_.Sem().Get(it.second)->Value();
-        if (builtin == core::BuiltinValue::kFragDepth) {
+        if (it.second->builtin == core::BuiltinValue::kFragDepth) {
             module_.PushExecutionMode(spv::Op::OpExecutionMode,
                                       {Operand(id), U32Operand(SpvExecutionModeDepthReplacing)});
             break;
@@ -793,10 +792,10 @@
         bool ok = Switch(
             attr,
             [&](const ast::BuiltinAttribute* builtin_attr) {
-                auto builtin = builder_.Sem().Get(builtin_attr)->Value();
-                module_.PushAnnot(spv::Op::OpDecorate,
-                                  {Operand(var_id), U32Operand(SpvDecorationBuiltIn),
-                                   U32Operand(ConvertBuiltin(builtin, sem->AddressSpace()))});
+                module_.PushAnnot(
+                    spv::Op::OpDecorate,
+                    {Operand(var_id), U32Operand(SpvDecorationBuiltIn),
+                     U32Operand(ConvertBuiltin(builtin_attr->builtin, sem->AddressSpace()))});
                 return true;
             },
             [&](const ast::LocationAttribute*) {
diff --git a/src/tint/lang/wgsl/ast/BUILD.bazel b/src/tint/lang/wgsl/ast/BUILD.bazel
index 9baea14..e038bf6 100644
--- a/src/tint/lang/wgsl/ast/BUILD.bazel
+++ b/src/tint/lang/wgsl/ast/BUILD.bazel
@@ -52,7 +52,6 @@
     "break_statement.cc",
     "builder.cc",
     "builtin_attribute.cc",
-    "builtin_value_name.cc",
     "call_expression.cc",
     "call_statement.cc",
     "case_selector.cc",
@@ -134,7 +133,6 @@
     "break_statement.h",
     "builder.h",
     "builtin_attribute.h",
-    "builtin_value_name.h",
     "call_expression.h",
     "call_statement.h",
     "case_selector.h",
@@ -243,7 +241,6 @@
     "builtin_attribute_test.cc",
     "builtin_texture_helper_test.cc",
     "builtin_texture_helper_test.h",
-    "builtin_value_name_test.cc",
     "call_expression_test.cc",
     "call_statement_test.cc",
     "case_selector_test.cc",
diff --git a/src/tint/lang/wgsl/ast/BUILD.cmake b/src/tint/lang/wgsl/ast/BUILD.cmake
index de65c9c..6afc090 100644
--- a/src/tint/lang/wgsl/ast/BUILD.cmake
+++ b/src/tint/lang/wgsl/ast/BUILD.cmake
@@ -67,8 +67,6 @@
   lang/wgsl/ast/builder.h
   lang/wgsl/ast/builtin_attribute.cc
   lang/wgsl/ast/builtin_attribute.h
-  lang/wgsl/ast/builtin_value_name.cc
-  lang/wgsl/ast/builtin_value_name.h
   lang/wgsl/ast/call_expression.cc
   lang/wgsl/ast/call_expression.h
   lang/wgsl/ast/call_statement.cc
@@ -243,7 +241,6 @@
   lang/wgsl/ast/builtin_attribute_test.cc
   lang/wgsl/ast/builtin_texture_helper_test.cc
   lang/wgsl/ast/builtin_texture_helper_test.h
-  lang/wgsl/ast/builtin_value_name_test.cc
   lang/wgsl/ast/call_expression_test.cc
   lang/wgsl/ast/call_statement_test.cc
   lang/wgsl/ast/case_selector_test.cc
diff --git a/src/tint/lang/wgsl/ast/BUILD.gn b/src/tint/lang/wgsl/ast/BUILD.gn
index 05c02bb..a1b43d7 100644
--- a/src/tint/lang/wgsl/ast/BUILD.gn
+++ b/src/tint/lang/wgsl/ast/BUILD.gn
@@ -70,8 +70,6 @@
     "builder.h",
     "builtin_attribute.cc",
     "builtin_attribute.h",
-    "builtin_value_name.cc",
-    "builtin_value_name.h",
     "call_expression.cc",
     "call_expression.h",
     "call_statement.cc",
@@ -243,7 +241,6 @@
       "builtin_attribute_test.cc",
       "builtin_texture_helper_test.cc",
       "builtin_texture_helper_test.h",
-      "builtin_value_name_test.cc",
       "call_expression_test.cc",
       "call_statement_test.cc",
       "case_selector_test.cc",
diff --git a/src/tint/lang/wgsl/ast/builder.h b/src/tint/lang/wgsl/ast/builder.h
index 56e42a1..7d5f6cf 100644
--- a/src/tint/lang/wgsl/ast/builder.h
+++ b/src/tint/lang/wgsl/ast/builder.h
@@ -48,7 +48,6 @@
 #include "src/tint/lang/wgsl/ast/bool_literal_expression.h"
 #include "src/tint/lang/wgsl/ast/break_if_statement.h"
 #include "src/tint/lang/wgsl/ast/break_statement.h"
-#include "src/tint/lang/wgsl/ast/builtin_value_name.h"
 #include "src/tint/lang/wgsl/ast/call_expression.h"
 #include "src/tint/lang/wgsl/ast/call_statement.h"
 #include "src/tint/lang/wgsl/ast/case_statement.h"
@@ -3056,44 +3055,14 @@
     /// @returns the selector pointer
     const ast::CaseSelector* DefaultCaseSelector() { return create<ast::CaseSelector>(nullptr); }
 
-    /// Passthrough overload
-    /// @param name the builtin value name
-    /// @returns @p name
-    const ast::BuiltinValueName* BuiltinValueName(const ast::BuiltinValueName* name) {
-        return name;
-    }
-
-    /// Creates an ast::BuiltinValueName
-    /// @param name the builtin value name
-    /// @returns the builtin value name
-    template <typename NAME>
-    const ast::BuiltinValueName* BuiltinValueName(NAME&& name) {
-        static_assert(!traits::IsType<traits::PtrElTy<NAME>, ast::TemplatedIdentifier>,
-                      "it is invalid for a builtin value name to be templated");
-        auto* name_ident = Ident(std::forward<NAME>(name));
-        return create<ast::BuiltinValueName>(name_ident ? name_ident->source : source_, name_ident);
-    }
-
-    /// Creates an ast::BuiltinValueName
-    /// @param source the source information
-    /// @param name the builtin value name
-    /// @returns the builtin value name
-    template <typename NAME>
-    const ast::BuiltinValueName* BuiltinValueName(const Source& source, NAME&& name) {
-        static_assert(!traits::IsType<traits::PtrElTy<NAME>, ast::TemplatedIdentifier>,
-                      "it is invalid for a builtin value name to be templated");
-        auto* name_ident = Ident(std::forward<NAME>(name));
-        return create<ast::BuiltinValueName>(source, name_ident);
-    }
-
     /// Creates an ast::BuiltinAttribute
     /// @param source the source information
     /// @param builtin the builtin value
     /// @returns the builtin attribute pointer
     template <typename BUILTIN>
     const ast::BuiltinAttribute* Builtin(const Source& source, BUILTIN&& builtin) {
-        return create<ast::BuiltinAttribute>(source,
-                                             BuiltinValueName(std::forward<BUILTIN>(builtin)));
+        core::BuiltinValue value{std::forward<BUILTIN>(builtin)};
+        return create<ast::BuiltinAttribute>(source, value);
     }
 
     /// Creates an ast::BuiltinAttribute
@@ -3101,8 +3070,7 @@
     /// @returns the builtin attribute pointer
     template <typename BUILTIN>
     const ast::BuiltinAttribute* Builtin(BUILTIN&& builtin) {
-        return create<ast::BuiltinAttribute>(source_,
-                                             BuiltinValueName(std::forward<BUILTIN>(builtin)));
+        return Builtin(source_, std::forward<BUILTIN>(builtin));
     }
 
     /// Creates an ast::InterpolateAttribute
diff --git a/src/tint/lang/wgsl/ast/builtin_attribute.cc b/src/tint/lang/wgsl/ast/builtin_attribute.cc
index b39ecd2..ddb69dc 100644
--- a/src/tint/lang/wgsl/ast/builtin_attribute.cc
+++ b/src/tint/lang/wgsl/ast/builtin_attribute.cc
@@ -30,7 +30,6 @@
 #include <string>
 
 #include "src/tint/lang/wgsl/ast/builder.h"
-#include "src/tint/lang/wgsl/ast/builtin_value_name.h"
 #include "src/tint/lang/wgsl/ast/clone_context.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::ast::BuiltinAttribute);
@@ -40,10 +39,8 @@
 BuiltinAttribute::BuiltinAttribute(GenerationID pid,
                                    NodeID nid,
                                    const Source& src,
-                                   const BuiltinValueName* b)
-    : Base(pid, nid, src), builtin(b) {
-    TINT_ASSERT_GENERATION_IDS_EQUAL(b, generation_id);
-}
+                                   const core::BuiltinValue b)
+    : Base(pid, nid, src), builtin(b) {}
 
 BuiltinAttribute::~BuiltinAttribute() = default;
 
@@ -54,8 +51,7 @@
 const BuiltinAttribute* BuiltinAttribute::Clone(CloneContext& ctx) const {
     // Clone arguments outside of create() call to have deterministic ordering
     auto src = ctx.Clone(source);
-    auto b = ctx.Clone(builtin);
-    return ctx.dst->create<BuiltinAttribute>(src, b);
+    return ctx.dst->create<BuiltinAttribute>(src, builtin);
 }
 
 }  // namespace tint::ast
diff --git a/src/tint/lang/wgsl/ast/builtin_attribute.h b/src/tint/lang/wgsl/ast/builtin_attribute.h
index 8f80137..0ce5f5d 100644
--- a/src/tint/lang/wgsl/ast/builtin_attribute.h
+++ b/src/tint/lang/wgsl/ast/builtin_attribute.h
@@ -30,13 +30,9 @@
 
 #include <string>
 
+#include "src/tint/lang/core/builtin_value.h"
 #include "src/tint/lang/wgsl/ast/attribute.h"
 
-// Forward declarations
-namespace tint::ast {
-class BuiltinValueName;
-}
-
 namespace tint::ast {
 
 /// A builtin attribute
@@ -50,7 +46,7 @@
     BuiltinAttribute(GenerationID pid,
                      NodeID nid,
                      const Source& src,
-                     const BuiltinValueName* builtin);
+                     const core::BuiltinValue builtin);
     ~BuiltinAttribute() override;
 
     /// @returns the WGSL name for the attribute
@@ -63,7 +59,7 @@
     const BuiltinAttribute* Clone(CloneContext& ctx) const override;
 
     /// The builtin value
-    const BuiltinValueName* const builtin;
+    const core::BuiltinValue builtin;
 };
 
 }  // namespace tint::ast
diff --git a/src/tint/lang/wgsl/ast/builtin_attribute_test.cc b/src/tint/lang/wgsl/ast/builtin_attribute_test.cc
index 5561224..a01cc4c 100644
--- a/src/tint/lang/wgsl/ast/builtin_attribute_test.cc
+++ b/src/tint/lang/wgsl/ast/builtin_attribute_test.cc
@@ -26,7 +26,6 @@
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 #include "src/tint/lang/core/builtin_value.h"
-#include "src/tint/lang/wgsl/ast/builtin_value_name.h"
 #include "src/tint/lang/wgsl/ast/helper_test.h"
 
 namespace tint::ast {
@@ -37,26 +36,7 @@
 
 TEST_F(BuiltinAttributeTest, Creation) {
     auto* d = Builtin(core::BuiltinValue::kFragDepth);
-    CheckIdentifier(d->builtin->name, "frag_depth");
-}
-
-TEST_F(BuiltinAttributeDeathTest, Assert_Null_Builtin) {
-    EXPECT_DEATH_IF_SUPPORTED(
-        {
-            ProgramBuilder b;
-            b.Builtin(nullptr);
-        },
-        "internal compiler error");
-}
-
-TEST_F(BuiltinAttributeDeathTest, Assert_DifferentGenerationID_Builtin) {
-    EXPECT_DEATH_IF_SUPPORTED(
-        {
-            ProgramBuilder b1;
-            ProgramBuilder b2;
-            b1.Builtin(b2.BuiltinValueName("bang"));
-        },
-        "internal compiler error");
+    EXPECT_EQ(d->builtin, core::BuiltinValue::kFragDepth);
 }
 
 }  // namespace
diff --git a/src/tint/lang/wgsl/ast/builtin_value_name.cc b/src/tint/lang/wgsl/ast/builtin_value_name.cc
deleted file mode 100644
index 28a94a1..0000000
--- a/src/tint/lang/wgsl/ast/builtin_value_name.cc
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright 2024 The Dawn & Tint Authors
-//
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are met:
-//
-// 1. Redistributions of source code must retain the above copyright notice, this
-//    list of conditions and the following disclaimer.
-//
-// 2. Redistributions in binary form must reproduce the above copyright notice,
-//    this list of conditions and the following disclaimer in the documentation
-//    and/or other materials provided with the distribution.
-//
-// 3. Neither the name of the copyright holder nor the names of its
-//    contributors may be used to endorse or promote products derived from
-//    this software without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
-// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-#include "src/tint/lang/wgsl/ast/builtin_value_name.h"
-
-#include <string>
-
-#include "src/tint/lang/wgsl/ast/builder.h"
-#include "src/tint/lang/wgsl/ast/clone_context.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::BuiltinValueName);
-
-namespace tint::ast {
-
-BuiltinValueName::BuiltinValueName(GenerationID pid,
-                                   NodeID nid,
-                                   const Source& src,
-                                   const Identifier* n)
-    : Base(pid, nid, src), name(n) {
-    TINT_ASSERT(name != nullptr);
-    TINT_ASSERT_GENERATION_IDS_EQUAL_IF_VALID(name, generation_id);
-}
-
-const BuiltinValueName* BuiltinValueName::Clone(CloneContext& ctx) const {
-    auto src = ctx.Clone(source);
-    auto n = ctx.Clone(name);
-    return ctx.dst->create<BuiltinValueName>(src, n);
-}
-
-std::string BuiltinValueName::String() const {
-    return name->symbol.Name();
-}
-
-}  // namespace tint::ast
diff --git a/src/tint/lang/wgsl/ast/builtin_value_name.h b/src/tint/lang/wgsl/ast/builtin_value_name.h
deleted file mode 100644
index 5655967..0000000
--- a/src/tint/lang/wgsl/ast/builtin_value_name.h
+++ /dev/null
@@ -1,66 +0,0 @@
-// Copyright 2024 The Dawn & Tint Authors
-//
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are met:
-//
-// 1. Redistributions of source code must retain the above copyright notice, this
-//    list of conditions and the following disclaimer.
-//
-// 2. Redistributions in binary form must reproduce the above copyright notice,
-//    this list of conditions and the following disclaimer in the documentation
-//    and/or other materials provided with the distribution.
-//
-// 3. Neither the name of the copyright holder nor the names of its
-//    contributors may be used to endorse or promote products derived from
-//    this software without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
-// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-#ifndef SRC_TINT_LANG_WGSL_AST_BUILTIN_VALUE_NAME_H_
-#define SRC_TINT_LANG_WGSL_AST_BUILTIN_VALUE_NAME_H_
-
-#include <string>
-
-#include "src/tint/lang/wgsl/ast/node.h"
-
-// Forward declarations
-namespace tint::ast {
-class Identifier;
-}  // namespace tint::ast
-
-namespace tint::ast {
-
-/// A builtin value name used for builtin value attributes.
-class BuiltinValueName final : public Castable<BuiltinValueName, Node> {
-  public:
-    /// Constructor
-    /// @param pid the identifier of the program that owns this node
-    /// @param nid the unique node identifier
-    /// @param src the source of this node
-    /// @param name the rule name
-    BuiltinValueName(GenerationID pid, NodeID nid, const Source& src, const Identifier* name);
-
-    /// Clones this node and all transitive child nodes using the `CloneContext` `ctx`.
-    /// @param ctx the clone context
-    /// @return the newly cloned node
-    const BuiltinValueName* Clone(CloneContext& ctx) const override;
-
-    /// @return the full name of this builtin value.
-    std::string String() const;
-
-    /// The builtin value name.
-    Identifier const* const name;
-};
-
-}  // namespace tint::ast
-
-#endif  // SRC_TINT_LANG_WGSL_AST_BUILTIN_VALUE_NAME_H_
diff --git a/src/tint/lang/wgsl/ast/builtin_value_name_test.cc b/src/tint/lang/wgsl/ast/builtin_value_name_test.cc
deleted file mode 100644
index 22d859b..0000000
--- a/src/tint/lang/wgsl/ast/builtin_value_name_test.cc
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright 2024 The Dawn & Tint Authors
-//
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are met:
-//
-// 1. Redistributions of source code must retain the above copyright notice, this
-//    list of conditions and the following disclaimer.
-//
-// 2. Redistributions in binary form must reproduce the above copyright notice,
-//    this list of conditions and the following disclaimer in the documentation
-//    and/or other materials provided with the distribution.
-//
-// 3. Neither the name of the copyright holder nor the names of its
-//    contributors may be used to endorse or promote products derived from
-//    this software without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
-// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-#include "src/tint/lang/wgsl/ast/builtin_value_name.h"
-#include "src/tint/lang/core/builtin_value.h"
-#include "src/tint/lang/wgsl/ast/helper_test.h"
-
-namespace tint::ast {
-namespace {
-
-using BuiltinValueNameTest = TestHelper;
-using BuiltinValueNameDeathTest = BuiltinValueNameTest;
-
-TEST_F(BuiltinValueNameTest, Creation) {
-    auto* builtin = BuiltinValueName(core::BuiltinValue::kNumWorkgroups);
-    CheckIdentifier(builtin->name, "num_workgroups");
-}
-
-TEST_F(BuiltinValueNameDeathTest, Assert_Null) {
-    EXPECT_DEATH_IF_SUPPORTED(
-        {
-            ProgramBuilder b;
-            b.BuiltinValueName(nullptr);
-        },
-        "internal compiler error");
-}
-
-TEST_F(BuiltinValueNameDeathTest, Assert_DifferentGenerationID) {
-    EXPECT_DEATH_IF_SUPPORTED(
-        {
-            ProgramBuilder b1;
-            ProgramBuilder b2;
-            b1.BuiltinValueName(b2.Ident("bang"));
-        },
-        "internal compiler error");
-}
-
-}  // namespace
-}  // namespace tint::ast
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 02bf9b7..9900ac3 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
@@ -188,7 +188,7 @@
         auto* cloned = ctx.Clone(in);
         out.Push(cloned);
         if (auto* builtin = in->As<BuiltinAttribute>()) {
-            builtin_attrs.Add(cloned->As<BuiltinAttribute>(), ctx.src->Sem().Get(builtin)->Value());
+            builtin_attrs.Add(cloned->As<BuiltinAttribute>(), builtin->builtin);
         }
     }
 
@@ -220,7 +220,7 @@
         } else {
             // attr belongs to the source program.
             // Obtain the builtin value from the semantic info.
-            return ctx.src->Sem().Get(attr)->Value();
+            return attr->builtin;
         }
         TINT_ICE() << "could not obtain builtin value from attribute";
     }
diff --git a/src/tint/lang/wgsl/ast/transform/clamp_frag_depth.cc b/src/tint/lang/wgsl/ast/transform/clamp_frag_depth.cc
index 1fad428..13528c0 100644
--- a/src/tint/lang/wgsl/ast/transform/clamp_frag_depth.cc
+++ b/src/tint/lang/wgsl/ast/transform/clamp_frag_depth.cc
@@ -180,8 +180,7 @@
     bool ContainsFragDepth(VectorRef<const ast::Attribute*> attrs) {
         for (auto* attribute : attrs) {
             if (auto* builtin_attr = attribute->As<ast::BuiltinAttribute>()) {
-                auto builtin = sem.Get(builtin_attr)->Value();
-                if (builtin == core::BuiltinValue::kFragDepth) {
+                if (builtin_attr->builtin == core::BuiltinValue::kFragDepth) {
                     return true;
                 }
             }
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 a7d912c..feaa575 100644
--- a/src/tint/lang/wgsl/ast/transform/first_index_offset.cc
+++ b/src/tint/lang/wgsl/ast/transform/first_index_offset.cc
@@ -109,7 +109,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 = builtin_attr->builtin;
                     if (builtin == core::BuiltinValue::kVertexIndex) {
                         auto* sem_var = ctx.src->Sem().Get(var);
                         builtin_vars.emplace(sem_var, kFirstVertexName);
@@ -126,7 +126,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 = builtin_attr->builtin;
                     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/offset_first_index.cc b/src/tint/lang/wgsl/ast/transform/offset_first_index.cc
index 9c9cfab..9c2c4b2 100644
--- a/src/tint/lang/wgsl/ast/transform/offset_first_index.cc
+++ b/src/tint/lang/wgsl/ast/transform/offset_first_index.cc
@@ -83,7 +83,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 = builtin_attr->builtin;
                     if (builtin == core::BuiltinValue::kVertexIndex && cfg &&
                         cfg->first_vertex_offset.has_value()) {
                         auto* sem_var = src.Sem().Get(var);
@@ -100,7 +100,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 = builtin_attr->builtin;
                     if (builtin == core::BuiltinValue::kVertexIndex && cfg &&
                         cfg->first_vertex_offset.has_value()) {
                         auto* sem_mem = src.Sem().Get(member);
diff --git a/src/tint/lang/wgsl/ast/transform/renamer.cc b/src/tint/lang/wgsl/ast/transform/renamer.cc
index 296f66c..25485b2 100644
--- a/src/tint/lang/wgsl/ast/transform/renamer.cc
+++ b/src/tint/lang/wgsl/ast/transform/renamer.cc
@@ -1340,9 +1340,6 @@
                     [&](const sem::ValueConstructor*) {
                         preserve_if_builtin_type(call->target->identifier);
                     });
-            },
-            [&](const BuiltinAttribute* builtin) {
-                preserved_identifiers.Add(builtin->builtin->name);
             });
     }
 
diff --git a/src/tint/lang/wgsl/ast/transform/vertex_pulling.cc b/src/tint/lang/wgsl/ast/transform/vertex_pulling.cc
index 3e34b10..9962ea4 100644
--- a/src/tint/lang/wgsl/ast/transform/vertex_pulling.cc
+++ b/src/tint/lang/wgsl/ast/transform/vertex_pulling.cc
@@ -795,7 +795,7 @@
             if (TINT_UNLIKELY(!builtin_attr)) {
                 TINT_ICE() << "Invalid entry point parameter";
             }
-            auto builtin = src.Sem().Get(builtin_attr)->Value();
+            auto builtin = builtin_attr->builtin;
             // Check for existing vertex_index and instance_index builtins.
             if (builtin == core::BuiltinValue::kVertexIndex) {
                 vertex_index_expr = [this, param] {
@@ -848,7 +848,7 @@
                 if (TINT_UNLIKELY(!builtin_attr)) {
                     TINT_ICE() << "Invalid entry point parameter";
                 }
-                auto builtin = src.Sem().Get(builtin_attr)->Value();
+                auto builtin = builtin_attr->builtin;
                 // Check for existing vertex_index and instance_index builtins.
                 if (builtin == core::BuiltinValue::kVertexIndex) {
                     vertex_index_expr = member_expr;
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 72efe20..0f08afa 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
@@ -298,8 +298,7 @@
         std::function<const ast::Expression*()> local_index;
         for (auto* param : fn->params) {
             if (auto* builtin_attr = GetAttribute<BuiltinAttribute>(param->attributes)) {
-                auto builtin = sem.Get(builtin_attr)->Value();
-                if (builtin == core::BuiltinValue::kLocalInvocationIndex) {
+                if (builtin_attr->builtin == core::BuiltinValue::kLocalInvocationIndex) {
                     return b.Expr(ctx.Clone(param->name->symbol));
                 }
             }
diff --git a/src/tint/lang/wgsl/inspector/inspector.cc b/src/tint/lang/wgsl/inspector/inspector.cc
index b0a50d7..3929fcd 100644
--- a/src/tint/lang/wgsl/inspector/inspector.cc
+++ b/src/tint/lang/wgsl/inspector/inspector.cc
@@ -687,7 +687,7 @@
     if (!builtin_declaration) {
         return false;
     }
-    return program_.Sem().Get(builtin_declaration)->Value() == builtin;
+    return builtin_declaration->builtin == builtin;
 }
 
 std::vector<ResourceBinding> Inspector::GetStorageBufferResourceBindingsImpl(
diff --git a/src/tint/lang/wgsl/reader/parser/param_list_test.cc b/src/tint/lang/wgsl/reader/parser/param_list_test.cc
index 512bd4a..31d4e7e 100644
--- a/src/tint/lang/wgsl/reader/parser/param_list_test.cc
+++ b/src/tint/lang/wgsl/reader/parser/param_list_test.cc
@@ -25,7 +25,6 @@
 // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
-#include "src/tint/lang/wgsl/ast/builtin_value_name.h"
 #include "src/tint/lang/wgsl/ast/helper_test.h"
 #include "src/tint/lang/wgsl/reader/parser/helper_test.h"
 
@@ -116,7 +115,7 @@
     auto attrs_0 = e.value[0]->attributes;
     ASSERT_EQ(attrs_0.Length(), 1u);
     EXPECT_TRUE(attrs_0[0]->Is<ast::BuiltinAttribute>());
-    ast::CheckIdentifier(attrs_0[0]->As<ast::BuiltinAttribute>()->builtin->name, "position");
+    EXPECT_EQ(attrs_0[0]->As<ast::BuiltinAttribute>()->builtin, core::BuiltinValue::kPosition);
 
     ASSERT_EQ(e.value[0]->source.range.begin.line, 1u);
     ASSERT_EQ(e.value[0]->source.range.begin.column, 20u);
diff --git a/src/tint/lang/wgsl/reader/parser/parser.cc b/src/tint/lang/wgsl/reader/parser/parser.cc
index 9902acb..a112464 100644
--- a/src/tint/lang/wgsl/reader/parser/parser.cc
+++ b/src/tint/lang/wgsl/reader/parser/parser.cc
@@ -3038,16 +3038,19 @@
             break;
     }
 
+    // builtin_attr :
+    //   '@' 'builtin' '(' builtin_value_name ',' ? ')'
     if (attr.value == core::Attribute::kBuiltin) {
         return expect_paren_block(
             "builtin attribute", [&]() -> Expect<const ast::BuiltinAttribute*> {
-                auto name = expect_builtin_value_name();
+                auto name = expect_enum("builtin value name", core::ParseBuiltinValue,
+                                        core::kBuiltinValueStrings);
                 if (name.errored) {
                     return Failure::kErrored;
                 }
                 match(Token::Type::kComma);
 
-                return create<ast::BuiltinAttribute>(t.source(), std::move(name.value));
+                return builder_.Builtin(name.value);
             });
     }
 
@@ -3225,17 +3228,6 @@
                        wgsl::kDiagnosticSeverityStrings);
 }
 
-// builtin_value_name :
-// | ident_pattern_token
-Expect<const ast::BuiltinValueName*> Parser::expect_builtin_value_name() {
-    auto name = expect_ident("", "builtin value name");
-    if (name.errored) {
-        return Failure::kErrored;
-    }
-
-    return builder_.BuiltinValueName(name.value);
-}
-
 // diagnostic_control
 // : PAREN_LEFT severity_control_name COMMA diagnostic_rule_name COMMA ? PAREN_RIGHT
 Expect<ast::DiagnosticControl> Parser::expect_diagnostic_control() {
diff --git a/src/tint/lang/wgsl/reader/parser/parser.h b/src/tint/lang/wgsl/reader/parser/parser.h
index 205c6dd..993c544 100644
--- a/src/tint/lang/wgsl/reader/parser/parser.h
+++ b/src/tint/lang/wgsl/reader/parser/parser.h
@@ -683,9 +683,6 @@
     /// Parses a diagnostic_rule_name grammar element.
     /// @return the parsed diagnostic rule name.
     Expect<const ast::DiagnosticRuleName*> expect_diagnostic_rule_name();
-    /// Parses a builtin_value_name grammar element.
-    /// @return the parsed builtin value name.
-    Expect<const ast::BuiltinValueName*> expect_builtin_value_name();
 
     /// Splits a peekable token into to parts filling in the peekable fields.
     /// @param lhs the token to set in the current position
diff --git a/src/tint/lang/wgsl/reader/parser/variable_attribute_list_test.cc b/src/tint/lang/wgsl/reader/parser/variable_attribute_list_test.cc
index c72fa38..5e8195c 100644
--- a/src/tint/lang/wgsl/reader/parser/variable_attribute_list_test.cc
+++ b/src/tint/lang/wgsl/reader/parser/variable_attribute_list_test.cc
@@ -52,7 +52,7 @@
     EXPECT_EQ(exp->value, 4u);
 
     ASSERT_TRUE(attr_1->Is<ast::BuiltinAttribute>());
-    ast::CheckIdentifier(attr_1->As<ast::BuiltinAttribute>()->builtin->name, "position");
+    EXPECT_EQ(attr_1->As<ast::BuiltinAttribute>()->builtin, core::BuiltinValue::kPosition);
 }
 
 TEST_F(WGSLParserTest, VariableAttributeList_Invalid) {
diff --git a/src/tint/lang/wgsl/reader/parser/variable_attribute_test.cc b/src/tint/lang/wgsl/reader/parser/variable_attribute_test.cc
index fa7de39..64b7375 100644
--- a/src/tint/lang/wgsl/reader/parser/variable_attribute_test.cc
+++ b/src/tint/lang/wgsl/reader/parser/variable_attribute_test.cc
@@ -233,8 +233,8 @@
 class BuiltinTest : public WGSLParserTestWithParam<core::BuiltinValue> {};
 
 TEST_P(BuiltinTest, Attribute_Builtin) {
-    auto str = tint::ToString(GetParam());
-    auto p = parser("builtin(" + str + ")");
+    auto param = GetParam();
+    auto p = parser("builtin(" + tint::ToString(param) + ")");
 
     auto attr = p->attribute();
     EXPECT_TRUE(attr.matched);
@@ -246,11 +246,11 @@
     ASSERT_TRUE(var_attr->Is<ast::BuiltinAttribute>());
 
     auto* builtin = var_attr->As<ast::BuiltinAttribute>();
-    ast::CheckIdentifier(builtin->builtin->name, str);
+    EXPECT_EQ(builtin->builtin, param);
 }
 TEST_P(BuiltinTest, Attribute_Builtin_TrailingComma) {
-    auto str = tint::ToString(GetParam());
-    auto p = parser("builtin(" + str + ",)");
+    auto param = GetParam();
+    auto p = parser("builtin(" + tint::ToString(param) + ",)");
 
     auto attr = p->attribute();
     EXPECT_TRUE(attr.matched);
@@ -262,7 +262,7 @@
     ASSERT_TRUE(var_attr->Is<ast::BuiltinAttribute>());
 
     auto* builtin = var_attr->As<ast::BuiltinAttribute>();
-    ast::CheckIdentifier(builtin->builtin->name, str);
+    EXPECT_EQ(builtin->builtin, param);
 }
 INSTANTIATE_TEST_SUITE_P(WGSLParserTest,
                          BuiltinTest,
@@ -306,7 +306,20 @@
     EXPECT_TRUE(attr.errored);
     EXPECT_EQ(attr.value, nullptr);
     EXPECT_TRUE(p->has_error());
-    EXPECT_EQ(p->error(), "1:9: expected builtin value name");
+    EXPECT_EQ(p->error(), R"(1:9: expected builtin value name
+Possible values: '__point_size', 'frag_depth', 'front_facing', 'global_invocation_id', 'instance_index', 'local_invocation_id', 'local_invocation_index', 'num_workgroups', 'position', 'sample_index', 'sample_mask', 'subgroup_invocation_id', 'subgroup_size', 'vertex_index', 'workgroup_id')");
+}
+
+TEST_F(WGSLParserTest, Attribute_Builtin_MisspelledValue) {
+    auto p = parser("builtin(positon)");
+    auto attr = p->attribute();
+    EXPECT_FALSE(attr.matched);
+    EXPECT_TRUE(attr.errored);
+    EXPECT_EQ(attr.value, nullptr);
+    EXPECT_TRUE(p->has_error());
+    EXPECT_EQ(p->error(), R"(1:9: expected builtin value name
+Did you mean 'position'?
+Possible values: '__point_size', 'frag_depth', 'front_facing', 'global_invocation_id', 'instance_index', 'local_invocation_id', 'local_invocation_index', 'num_workgroups', 'position', 'sample_index', 'sample_mask', 'subgroup_invocation_id', 'subgroup_size', 'vertex_index', 'workgroup_id')");
 }
 
 TEST_F(WGSLParserTest, Attribute_Interpolate_Flat) {
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 cfe793d..1d03802 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
@@ -298,13 +298,7 @@
                         ir_func->SetReturnInterpolation(interp->interpolation);
                     },
                     [&](const ast::InvariantAttribute*) { ir_func->SetReturnInvariant(true); },
-                    [&](const ast::BuiltinAttribute* b) {
-                        if (auto* ident_sem = program_.Sem().Get(b)->As<sem::BuiltinAttribute>()) {
-                            ir_func->SetReturnBuiltin(ident_sem->Value());
-                        } else {
-                            TINT_ICE() << "Builtin attribute sem invalid";
-                        }
-                    });
+                    [&](const ast::BuiltinAttribute* b) { ir_func->SetReturnBuiltin(b->builtin); });
             }
             ir_func->SetReturnLocation(sem->ReturnLocation());
         }
@@ -325,13 +319,7 @@
                         param->SetInterpolation(interp->interpolation);
                     },
                     [&](const ast::InvariantAttribute*) { param->SetInvariant(true); },
-                    [&](const ast::BuiltinAttribute* b) {
-                        if (auto* ident_sem = program_.Sem().Get(b)->As<sem::BuiltinAttribute>()) {
-                            param->SetBuiltin(ident_sem->Value());
-                        } else {
-                            TINT_ICE() << "Builtin attribute sem invalid";
-                        }
-                    });
+                    [&](const ast::BuiltinAttribute* b) { param->SetBuiltin(b->builtin); });
 
                 param->SetLocation(param_sem->Attributes().location);
                 param->SetColor(param_sem->Attributes().color);
diff --git a/src/tint/lang/wgsl/resolver/builtin_enum_test.cc b/src/tint/lang/wgsl/resolver/builtin_enum_test.cc
index de1531d..2fd7743 100644
--- a/src/tint/lang/wgsl/resolver/builtin_enum_test.cc
+++ b/src/tint/lang/wgsl/resolver/builtin_enum_test.cc
@@ -27,7 +27,6 @@
 
 #include "src/tint/lang/core/access.h"
 #include "src/tint/lang/core/address_space.h"
-#include "src/tint/lang/core/builtin_value.h"
 #include "src/tint/lang/core/fluent_types.h"
 #include "src/tint/lang/core/texel_format.h"
 #include "src/tint/lang/wgsl/resolver/resolver.h"
@@ -80,25 +79,6 @@
                          testing::ValuesIn(core::kAddressSpaceStrings));
 
 ////////////////////////////////////////////////////////////////////////////////
-// builtin value
-////////////////////////////////////////////////////////////////////////////////
-using ResolverBuiltinValueUsedWithTemplateArgs = ResolverTestWithParam<std::string_view>;
-
-TEST_P(ResolverBuiltinValueUsedWithTemplateArgs, Test) {
-    // fn f(@builtin(BUILTIN<T>) p : vec4<f32>) {}
-    auto* tmpl = Ident(Source{{12, 34}}, GetParam(), "T");
-    Func("f", Vector{Param("p", ty.vec4<f32>(), Vector{Builtin(tmpl)})}, ty.void_(), tint::Empty,
-         Vector{Stage(ast::PipelineStage::kFragment)});
-    EXPECT_FALSE(r()->Resolve());
-    EXPECT_EQ(r()->error(), "12:34 error: builtin value '" + std::string(GetParam()) +
-                                "' does not take template arguments");
-}
-
-INSTANTIATE_TEST_SUITE_P(,
-                         ResolverBuiltinValueUsedWithTemplateArgs,
-                         testing::ValuesIn(core::kBuiltinValueStrings));
-
-////////////////////////////////////////////////////////////////////////////////
 // texel format
 ////////////////////////////////////////////////////////////////////////////////
 using ResolverTexelFormatUsedWithTemplateArgs = ResolverTestWithParam<std::string_view>;
diff --git a/src/tint/lang/wgsl/resolver/builtins_validation_test.cc b/src/tint/lang/wgsl/resolver/builtins_validation_test.cc
index c108ab8..c106221 100644
--- a/src/tint/lang/wgsl/resolver/builtins_validation_test.cc
+++ b/src/tint/lang/wgsl/resolver/builtins_validation_test.cc
@@ -827,48 +827,6 @@
     EXPECT_EQ(r()->error(), "12:34 error: store type of '@builtin(front_facing)' must be 'bool'");
 }
 
-// TODO(crbug.com/tint/1846): This isn't a validation test, but this sits next to other @builtin
-// tests. Clean this up.
-TEST_F(ResolverBuiltinsValidationTest, StructMemberAttributeMapsToSemBuiltinEnum) {
-    // struct S {
-    //   @builtin(front_facing) b : bool;
-    // };
-    // @fragment
-    // fn f(s : S) {}
-
-    auto* builtin = Builtin(core::BuiltinValue::kFrontFacing);
-    auto* s = Structure("S", Vector{
-                                 Member("f", ty.bool_(), Vector{builtin}),
-                             });
-    Func("f", Vector{Param("b", ty.Of(s))}, ty.void_(), tint::Empty,
-         Vector{
-             Stage(ast::PipelineStage::kFragment),
-         });
-
-    EXPECT_TRUE(r()->Resolve()) << r()->error();
-    auto* builtin_expr = Sem().Get(builtin);
-    ASSERT_NE(builtin_expr, nullptr);
-    EXPECT_EQ(builtin_expr->Value(), core::BuiltinValue::kFrontFacing);
-}
-
-// TODO(crbug.com/tint/1846): This isn't a validation test, but this sits next to other @builtin
-// tests. Clean this up.
-TEST_F(ResolverBuiltinsValidationTest, ParamAttributeMapsToSemBuiltinEnum) {
-    // @fragment
-    // fn f(@builtin(front_facing) b : bool) {}
-
-    auto* builtin = Builtin(core::BuiltinValue::kFrontFacing);
-    Func("f", Vector{Param("b", ty.bool_(), Vector{builtin})}, ty.void_(), tint::Empty,
-         Vector{
-             Stage(ast::PipelineStage::kFragment),
-         });
-
-    EXPECT_TRUE(r()->Resolve()) << r()->error();
-    auto* builtin_expr = Sem().Get(builtin);
-    ASSERT_NE(builtin_expr, nullptr);
-    EXPECT_EQ(builtin_expr->Value(), core::BuiltinValue::kFrontFacing);
-}
-
 TEST_F(ResolverBuiltinsValidationTest, Length_Float_Scalar) {
     auto* builtin = Call("length", 1_f);
     WrapInFunction(builtin);
diff --git a/src/tint/lang/wgsl/resolver/compatibility_mode_test.cc b/src/tint/lang/wgsl/resolver/compatibility_mode_test.cc
index c24821c..1fa085af 100644
--- a/src/tint/lang/wgsl/resolver/compatibility_mode_test.cc
+++ b/src/tint/lang/wgsl/resolver/compatibility_mode_test.cc
@@ -101,11 +101,11 @@
     // }
 
     Func("main",
-         Vector{Param("mask", ty.i32(),
-                      Vector{
-                          create<ast::BuiltinAttribute>(
-                              {}, BuiltinValueName(Source{{12, 34}}, "sample_mask")),
-                      })},
+         Vector{Param(
+             "mask", ty.i32(),
+             Vector{
+                 create<ast::BuiltinAttribute>(Source{{12, 34}}, core::BuiltinValue::kSampleMask),
+             })},
          ty.void_(), Empty,
          Vector{
              Stage(ast::PipelineStage::kFragment),
@@ -134,7 +134,7 @@
              Stage(ast::PipelineStage::kFragment),
          },
          Vector{
-             create<ast::BuiltinAttribute>({}, BuiltinValueName(Source{{12, 34}}, "sample_mask")),
+             create<ast::BuiltinAttribute>(Source{{12, 34}}, core::BuiltinValue::kSampleMask),
          });
 
     EXPECT_FALSE(r()->Resolve());
@@ -151,8 +151,8 @@
     Structure("S", Vector{
                        Member("mask", ty.u32(),
                               Vector{
-                                  create<ast::BuiltinAttribute>(
-                                      {}, BuiltinValueName(Source{{12, 34}}, "sample_mask")),
+                                  create<ast::BuiltinAttribute>(Source{{12, 34}},
+                                                                core::BuiltinValue::kSampleMask),
                               }),
                    });
 
@@ -168,11 +168,11 @@
     // }
 
     Func("main",
-         Vector{Param("mask", ty.i32(),
-                      Vector{
-                          create<ast::BuiltinAttribute>(
-                              {}, BuiltinValueName(Source{{12, 34}}, "sample_index")),
-                      })},
+         Vector{Param(
+             "mask", ty.i32(),
+             Vector{
+                 create<ast::BuiltinAttribute>(Source{{12, 34}}, core::BuiltinValue::kSampleIndex),
+             })},
          ty.void_(), Empty,
          Vector{
              Stage(ast::PipelineStage::kFragment),
@@ -201,7 +201,7 @@
              Stage(ast::PipelineStage::kFragment),
          },
          Vector{
-             create<ast::BuiltinAttribute>({}, BuiltinValueName(Source{{12, 34}}, "sample_index")),
+             create<ast::BuiltinAttribute>(Source{{12, 34}}, core::BuiltinValue::kSampleIndex),
          });
 
     EXPECT_FALSE(r()->Resolve());
@@ -218,8 +218,8 @@
     Structure("S", Vector{
                        Member("mask", ty.u32(),
                               Vector{
-                                  create<ast::BuiltinAttribute>(
-                                      {}, BuiltinValueName(Source{{12, 34}}, "sample_index")),
+                                  create<ast::BuiltinAttribute>(Source{{12, 34}},
+                                                                core::BuiltinValue::kSampleIndex),
                               }),
                    });
 
diff --git a/src/tint/lang/wgsl/resolver/resolver.cc b/src/tint/lang/wgsl/resolver/resolver.cc
index e322e0d..796d7ce 100644
--- a/src/tint/lang/wgsl/resolver/resolver.cc
+++ b/src/tint/lang/wgsl/resolver/resolver.cc
@@ -58,7 +58,6 @@
 #include "src/tint/lang/wgsl/ast/assignment_statement.h"
 #include "src/tint/lang/wgsl/ast/attribute.h"
 #include "src/tint/lang/wgsl/ast/break_statement.h"
-#include "src/tint/lang/wgsl/ast/builtin_value_name.h"
 #include "src/tint/lang/wgsl/ast/call_statement.h"
 #include "src/tint/lang/wgsl/ast/continue_statement.h"
 #include "src/tint/lang/wgsl/ast/disable_validation_attribute.h"
@@ -84,7 +83,6 @@
 #include "src/tint/lang/wgsl/resolver/unresolved_identifier.h"
 #include "src/tint/lang/wgsl/sem/array.h"
 #include "src/tint/lang/wgsl/sem/break_if_statement.h"
-#include "src/tint/lang/wgsl/sem/builtin_attribute.h"
 #include "src/tint/lang/wgsl/sem/builtin_enum_expression.h"
 #include "src/tint/lang/wgsl/sem/call.h"
 #include "src/tint/lang/wgsl/sem/for_loop_statement.h"
@@ -683,11 +681,11 @@
                     global->Attributes().color = value.Get();
                     return kSuccess;
                 },
-                [&](const ast::BuiltinAttribute* attr) {
+                [&](const ast::BuiltinAttribute*) {
                     if (!has_io_address_space) {
                         return kInvalid;
                     }
-                    return BuiltinAttribute(attr) == Success ? kSuccess : kErrored;
+                    return kSuccess;
                 },
                 [&](const ast::InterpolateAttribute*) {
                     if (!has_io_address_space) {
@@ -781,9 +779,7 @@
                     sem->Attributes().color = value.Get();
                     return true;
                 },
-                [&](const ast::BuiltinAttribute* attr) {
-                    return BuiltinAttribute(attr) == Success;
-                },
+                [&](const ast::BuiltinAttribute*) { return true; },
                 [&](const ast::InvariantAttribute* attr) -> bool {
                     return InvariantAttribute(attr);
                 },
@@ -1122,9 +1118,7 @@
                     func->SetReturnIndex(value.Get());
                     return kSuccess;
                 },
-                [&](const ast::BuiltinAttribute* attr) {
-                    return BuiltinAttribute(attr) == Success ? kSuccess : kErrored;
-                },
+                [&](const ast::BuiltinAttribute*) { return kSuccess; },
                 [&](const ast::InternalAttribute* attr) {
                     return InternalAttribute(attr) ? kSuccess : kErrored;
                 },
@@ -3973,29 +3967,6 @@
     return ws;
 }
 
-tint::Result<tint::core::BuiltinValue> Resolver::BuiltinAttribute(
-    const ast::BuiltinAttribute* attr) {
-    const ast::BuiltinValueName* builtin_val = attr->builtin;
-    Mark(builtin_val);
-
-    const ast::Identifier* ident = builtin_val->name;
-    if (!TINT_LIKELY(CheckNotTemplated("builtin value", ident))) {
-        return Failure{};
-    }
-    Mark(ident);
-
-    core::BuiltinValue builtin = core::ParseBuiltinValue(ident->symbol.NameView());
-    if (builtin == core::BuiltinValue::kUndefined) {
-        sem_.ErrorUnexpectedIdent(ident, "builtin value", core::kBuiltinValueStrings);
-        return Failure{};
-    }
-
-    auto* sem = b.create<sem::BuiltinAttribute>(attr, builtin);
-    // Apply the resolved tint::sem::BuiltinAttribute to the attribute.
-    b.Sem().Add(attr, sem);
-    return builtin;
-}
-
 bool Resolver::DiagnosticAttribute(const ast::DiagnosticAttribute* attr) {
     return DiagnosticControl(attr->control);
 }
@@ -4461,11 +4432,7 @@
                     return true;
                 },
                 [&](const ast::BuiltinAttribute* attr) {
-                    auto value = BuiltinAttribute(attr);
-                    if (value != Success) {
-                        return false;
-                    }
-                    attributes.builtin = value.Get();
+                    attributes.builtin = attr->builtin;
                     return true;
                 },
                 [&](const ast::InterpolateAttribute* attr) {
diff --git a/src/tint/lang/wgsl/resolver/resolver.h b/src/tint/lang/wgsl/resolver/resolver.h
index b6f33a7..969ecbb 100644
--- a/src/tint/lang/wgsl/resolver/resolver.h
+++ b/src/tint/lang/wgsl/resolver/resolver.h
@@ -402,10 +402,6 @@
     /// current_function_
     bool WorkgroupSize(const ast::Function*);
 
-    /// Resolves the `@builtin` attribute @p attr
-    /// @returns the builtin value on success
-    tint::Result<tint::core::BuiltinValue> BuiltinAttribute(const ast::BuiltinAttribute* attr);
-
     /// Resolves the `@location` attribute @p attr
     /// @returns the location value on success.
     tint::Result<uint32_t> LocationAttribute(const ast::LocationAttribute* attr);
diff --git a/src/tint/lang/wgsl/resolver/uniformity.cc b/src/tint/lang/wgsl/resolver/uniformity.cc
index 2faeb21..aecfb1f 100644
--- a/src/tint/lang/wgsl/resolver/uniformity.cc
+++ b/src/tint/lang/wgsl/resolver/uniformity.cc
@@ -1192,7 +1192,7 @@
             // Only the num_workgroups and workgroup_id builtins, and subgroup_size builtin used in
             // compute stage are uniform.
             if (auto* builtin_attr = ast::GetAttribute<ast::BuiltinAttribute>(obj->attributes)) {
-                auto builtin = b.Sem().Get(builtin_attr)->Value();
+                auto builtin = builtin_attr->builtin;
                 if (builtin == core::BuiltinValue::kNumWorkgroups ||
                     builtin == core::BuiltinValue::kWorkgroupId) {
                     return false;
diff --git a/src/tint/lang/wgsl/resolver/unresolved_identifier_test.cc b/src/tint/lang/wgsl/resolver/unresolved_identifier_test.cc
index 85de965..eb50282 100644
--- a/src/tint/lang/wgsl/resolver/unresolved_identifier_test.cc
+++ b/src/tint/lang/wgsl/resolver/unresolved_identifier_test.cc
@@ -54,16 +54,6 @@
 Possible values: 'function', 'pixel_local', 'private', 'push_constant', 'storage', 'uniform', 'workgroup')");
 }
 
-TEST_F(ResolverUnresolvedIdentifierSuggestions, BuiltinValue) {
-    Func("f", Vector{Param("p", ty.i32(), Vector{Builtin(Ident(Source{{12, 34}}, "positon"))})},
-         ty.void_(), tint::Empty, Vector{Stage(ast::PipelineStage::kVertex)});
-
-    EXPECT_FALSE(r()->Resolve());
-    EXPECT_EQ(r()->error(), R"(12:34 error: unresolved builtin value 'positon'
-12:34 note: Did you mean 'position'?
-Possible values: 'frag_depth', 'front_facing', 'global_invocation_id', 'instance_index', 'local_invocation_id', 'local_invocation_index', 'num_workgroups', 'position', 'sample_index', 'sample_mask', 'subgroup_invocation_id', 'subgroup_size', 'vertex_index', 'workgroup_id')");
-}
-
 TEST_F(ResolverUnresolvedIdentifierSuggestions, TexelFormat) {
     GlobalVar("v", ty("texture_storage_1d", Expr(Source{{12, 34}}, "rba8unorm"), "read"));
 
diff --git a/src/tint/lang/wgsl/resolver/validator.cc b/src/tint/lang/wgsl/resolver/validator.cc
index fa0df4d..964ebea 100644
--- a/src/tint/lang/wgsl/resolver/validator.cc
+++ b/src/tint/lang/wgsl/resolver/validator.cc
@@ -987,7 +987,7 @@
     auto* type = storage_ty->UnwrapRef();
     bool is_stage_mismatch = false;
     bool is_output = !is_input;
-    auto builtin = sem_.Get(attr)->Value();
+    auto builtin = attr->builtin;
 
     auto err_builtin_type = [&](std::string_view required) {
         AddError(attr->source) << "store type of " << style::Attribute("@builtin")
@@ -1066,9 +1066,9 @@
             break;
         case core::BuiltinValue::kSampleMask:
             if (mode_ == wgsl::ValidationMode::kCompat) {
-                AddError(attr->builtin->source) << "use of " << style::Attribute("@builtin")
-                                                << style::Code("(", style::Enum(builtin), ")")
-                                                << " is not allowed in compatibility mode";
+                AddError(attr->source) << "use of " << style::Attribute("@builtin")
+                                       << style::Code("(", style::Enum(builtin), ")")
+                                       << " is not allowed in compatibility mode";
                 return false;
             }
             if (stage != ast::PipelineStage::kNone && !(stage == ast::PipelineStage::kFragment)) {
@@ -1081,9 +1081,9 @@
             break;
         case core::BuiltinValue::kSampleIndex:
             if (mode_ == wgsl::ValidationMode::kCompat) {
-                AddError(attr->builtin->source) << "use of " << style::Attribute("@builtin")
-                                                << style::Code("(", style::Enum(builtin), ")")
-                                                << " is not allowed in compatibility mode";
+                AddError(attr->source) << "use of " << style::Attribute("@builtin")
+                                       << style::Code("(", style::Enum(builtin), ")")
+                                       << " is not allowed in compatibility mode";
                 return false;
             }
             if (stage != ast::PipelineStage::kNone &&
@@ -1323,7 +1323,7 @@
             bool ok = Switch(
                 attr,  //
                 [&](const ast::BuiltinAttribute* builtin_attr) {
-                    auto builtin = sem_.Get(builtin_attr)->Value();
+                    auto builtin = builtin_attr->builtin;
 
                     if (pipeline_io_attribute) {
                         AddError(attr->source) << "multiple entry point IO attributes";
@@ -1486,8 +1486,7 @@
                 bool has_position = false;
                 if (pipeline_io_attribute) {
                     if (auto* builtin_attr = pipeline_io_attribute->As<ast::BuiltinAttribute>()) {
-                        auto builtin = sem_.Get(builtin_attr)->Value();
-                        has_position = (builtin == core::BuiltinValue::kPosition);
+                        has_position = (builtin_attr->builtin == core::BuiltinValue::kPosition);
                     }
                 }
                 if (!has_position) {
@@ -1561,8 +1560,7 @@
         for (auto* global : func->TransitivelyReferencedGlobals()) {
             if (auto* builtin_attr =
                     ast::GetAttribute<ast::BuiltinAttribute>(global->Declaration()->attributes)) {
-                auto builtin = sem_.Get(builtin_attr)->Value();
-                if (builtin == core::BuiltinValue::kPosition) {
+                if (builtin_attr->builtin == core::BuiltinValue::kPosition) {
                     found = true;
                     break;
                 }
@@ -2428,8 +2426,7 @@
                                           /* is_input */ false)) {
                         return false;
                     }
-                    auto builtin = sem_.Get(builtin_attr)->Value();
-                    if (builtin == core::BuiltinValue::kPosition) {
+                    if (builtin_attr->builtin == core::BuiltinValue::kPosition) {
                         has_position = true;
                     }
                     return true;
diff --git a/src/tint/lang/wgsl/sem/BUILD.bazel b/src/tint/lang/wgsl/sem/BUILD.bazel
index 2407e96..37b8323 100644
--- a/src/tint/lang/wgsl/sem/BUILD.bazel
+++ b/src/tint/lang/wgsl/sem/BUILD.bazel
@@ -45,7 +45,6 @@
     "behavior.cc",
     "block_statement.cc",
     "break_if_statement.cc",
-    "builtin_attribute.cc",
     "builtin_enum_expression.cc",
     "builtin_fn.cc",
     "call.cc",
@@ -80,7 +79,6 @@
     "behavior.h",
     "block_statement.h",
     "break_if_statement.h",
-    "builtin_attribute.h",
     "builtin_enum_expression.h",
     "builtin_fn.h",
     "call.h",
diff --git a/src/tint/lang/wgsl/sem/BUILD.cmake b/src/tint/lang/wgsl/sem/BUILD.cmake
index c83602b..6120b90 100644
--- a/src/tint/lang/wgsl/sem/BUILD.cmake
+++ b/src/tint/lang/wgsl/sem/BUILD.cmake
@@ -51,8 +51,6 @@
   lang/wgsl/sem/block_statement.h
   lang/wgsl/sem/break_if_statement.cc
   lang/wgsl/sem/break_if_statement.h
-  lang/wgsl/sem/builtin_attribute.cc
-  lang/wgsl/sem/builtin_attribute.h
   lang/wgsl/sem/builtin_enum_expression.cc
   lang/wgsl/sem/builtin_enum_expression.h
   lang/wgsl/sem/builtin_fn.cc
diff --git a/src/tint/lang/wgsl/sem/BUILD.gn b/src/tint/lang/wgsl/sem/BUILD.gn
index 4131c9a..e6b2b06 100644
--- a/src/tint/lang/wgsl/sem/BUILD.gn
+++ b/src/tint/lang/wgsl/sem/BUILD.gn
@@ -56,8 +56,6 @@
     "block_statement.h",
     "break_if_statement.cc",
     "break_if_statement.h",
-    "builtin_attribute.cc",
-    "builtin_attribute.h",
     "builtin_enum_expression.cc",
     "builtin_enum_expression.h",
     "builtin_fn.cc",
diff --git a/src/tint/lang/wgsl/sem/builtin_attribute.cc b/src/tint/lang/wgsl/sem/builtin_attribute.cc
deleted file mode 100644
index d6e3784..0000000
--- a/src/tint/lang/wgsl/sem/builtin_attribute.cc
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright 2024 The Dawn & Tint Authors
-//
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are met:
-//
-// 1. Redistributions of source code must retain the above copyright notice, this
-//    list of conditions and the following disclaimer.
-//
-// 2. Redistributions in binary form must reproduce the above copyright notice,
-//    this list of conditions and the following disclaimer in the documentation
-//    and/or other materials provided with the distribution.
-//
-// 3. Neither the name of the copyright holder nor the names of its
-//    contributors may be used to endorse or promote products derived from
-//    this software without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
-// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-#include "src/tint/lang/wgsl/sem/builtin_attribute.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::sem::BuiltinAttribute);
-
-namespace tint::sem {
-
-BuiltinAttribute::BuiltinAttribute(const ast::BuiltinAttribute* declaration,
-                                   const core::BuiltinValue value)
-    : Base(), declaration_(declaration), value_(value) {}
-
-BuiltinAttribute::~BuiltinAttribute() = default;
-
-const ast::BuiltinAttribute* BuiltinAttribute::Declaration() const {
-    return declaration_;
-}
-
-core::BuiltinValue BuiltinAttribute::Value() const {
-    return value_;
-}
-
-}  // namespace tint::sem
diff --git a/src/tint/lang/wgsl/sem/builtin_attribute.h b/src/tint/lang/wgsl/sem/builtin_attribute.h
deleted file mode 100644
index 4906cf8..0000000
--- a/src/tint/lang/wgsl/sem/builtin_attribute.h
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright 2024 The Dawn & Tint Authors
-//
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are met:
-//
-// 1. Redistributions of source code must retain the above copyright notice, this
-//    list of conditions and the following disclaimer.
-//
-// 2. Redistributions in binary form must reproduce the above copyright notice,
-//    this list of conditions and the following disclaimer in the documentation
-//    and/or other materials provided with the distribution.
-//
-// 3. Neither the name of the copyright holder nor the names of its
-//    contributors may be used to endorse or promote products derived from
-//    this software without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
-// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-#ifndef SRC_TINT_LANG_WGSL_SEM_BUILTIN_ATTRIBUTE_H_
-#define SRC_TINT_LANG_WGSL_SEM_BUILTIN_ATTRIBUTE_H_
-
-#include "src/tint/lang/core/builtin_value.h"
-#include "src/tint/lang/wgsl/ast/builtin_attribute.h"
-#include "src/tint/lang/wgsl/sem/expression.h"
-
-namespace tint::sem {
-
-/// BuiltinAttribute holds the semantic information for ast nodes that resolve to a
-/// builtin value.
-class BuiltinAttribute : public Castable<BuiltinAttribute, Node> {
-  public:
-    /// Constructor
-    /// @param declaration the AST node
-    /// @param value the builtin value
-    BuiltinAttribute(const ast::BuiltinAttribute* declaration, core::BuiltinValue value);
-
-    /// Destructor
-    ~BuiltinAttribute() override;
-
-    /// @returns the case selector declaration
-    const ast::BuiltinAttribute* Declaration() const;
-
-    /// @return the enumerator value
-    core::BuiltinValue Value() const;
-
-  private:
-    const ast::BuiltinAttribute* declaration_;
-    const core::BuiltinValue value_;
-};
-
-}  // namespace tint::sem
-
-#endif  // SRC_TINT_LANG_WGSL_SEM_BUILTIN_ATTRIBUTE_H_
diff --git a/src/tint/lang/wgsl/sem/type_mappings.h b/src/tint/lang/wgsl/sem/type_mappings.h
index 9bb8f59..8353530 100644
--- a/src/tint/lang/wgsl/sem/type_mappings.h
+++ b/src/tint/lang/wgsl/sem/type_mappings.h
@@ -30,8 +30,6 @@
 
 #include <type_traits>
 
-#include "src/tint/lang/wgsl/sem/builtin_attribute.h"
-
 // Forward declarations
 namespace tint {
 class CastableBase;
@@ -95,7 +93,6 @@
 struct TypeMappings {
     //! @cond Doxygen_Suppress
     BlockStatement* operator()(ast::BlockStatement*);
-    BuiltinAttribute* operator()(ast::BuiltinAttribute*);
     CastableBase* operator()(ast::Node*);
     Expression* operator()(ast::Expression*);
     ForLoopStatement* operator()(ast::ForLoopStatement*);
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 f6cb13c..9240566 100644
--- a/src/tint/lang/wgsl/writer/ast_printer/ast_printer.cc
+++ b/src/tint/lang/wgsl/writer/ast_printer/ast_printer.cc
@@ -38,7 +38,6 @@
 #include "src/tint/lang/wgsl/ast/bool_literal_expression.h"
 #include "src/tint/lang/wgsl/ast/break_if_statement.h"
 #include "src/tint/lang/wgsl/ast/break_statement.h"
-#include "src/tint/lang/wgsl/ast/builtin_value_name.h"
 #include "src/tint/lang/wgsl/ast/call_expression.h"
 #include "src/tint/lang/wgsl/ast/call_statement.h"
 #include "src/tint/lang/wgsl/ast/color_attribute.h"
@@ -509,7 +508,7 @@
             },
             [&](const ast::BuiltinAttribute* builtin) {
                 out << "builtin(";
-                out << builtin->builtin->String();
+                out << core::ToString(builtin->builtin);
                 out << ")";
             },
             [&](const ast::DiagnosticAttribute* diagnostic) {
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 73832f7..63559ae 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
@@ -35,7 +35,6 @@
 #include "src/tint/lang/wgsl/ast/bool_literal_expression.h"
 #include "src/tint/lang/wgsl/ast/break_if_statement.h"
 #include "src/tint/lang/wgsl/ast/break_statement.h"
-#include "src/tint/lang/wgsl/ast/builtin_value_name.h"
 #include "src/tint/lang/wgsl/ast/call_expression.h"
 #include "src/tint/lang/wgsl/ast/call_statement.h"
 #include "src/tint/lang/wgsl/ast/compound_assignment_statement.h"
@@ -553,7 +552,7 @@
                 Line() << "]";
             },
             [&](const ast::BuiltinAttribute* builtin) {
-                Line() << "BuiltinAttribute [" << builtin->builtin->String() << "]";
+                Line() << "BuiltinAttribute [" << core::ToString(builtin->builtin) << "]";
             },
             [&](const ast::DiagnosticAttribute* diagnostic) {
                 EmitDiagnosticControl(diagnostic->control);