tint/ast: Rename Builtin to BuiltinValue

This is what it is called in the spec, and avoids confusion with the builtin functions.

Change-Id: I5cd4a250351c10928e90b640a8c68c7834bcf2a0
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/97200
Commit-Queue: Ben Clayton <bclayton@google.com>
Reviewed-by: Dan Sinclair <dsinclair@chromium.org>
diff --git a/src/tint/BUILD.gn b/src/tint/BUILD.gn
index c426520..86410ed 100644
--- a/src/tint/BUILD.gn
+++ b/src/tint/BUILD.gn
@@ -205,10 +205,10 @@
     "ast/bool_literal_expression.h",
     "ast/break_statement.cc",
     "ast/break_statement.h",
-    "ast/builtin.cc",
-    "ast/builtin.h",
     "ast/builtin_attribute.cc",
     "ast/builtin_attribute.h",
+    "ast/builtin_value.cc",
+    "ast/builtin_value.h",
     "ast/call_expression.cc",
     "ast/call_expression.h",
     "ast/call_statement.cc",
diff --git a/src/tint/CMakeLists.txt b/src/tint/CMakeLists.txt
index ccef6ee..25c2723 100644
--- a/src/tint/CMakeLists.txt
+++ b/src/tint/CMakeLists.txt
@@ -77,8 +77,8 @@
   ast/break_statement.h
   ast/builtin_attribute.cc
   ast/builtin_attribute.h
-  ast/builtin.cc
-  ast/builtin.h
+  ast/builtin_value.cc
+  ast/builtin_value.h
   ast/call_expression.cc
   ast/call_expression.h
   ast/call_statement.cc
diff --git a/src/tint/ast/builtin_attribute.cc b/src/tint/ast/builtin_attribute.cc
index 30e7cc6..d5aace0 100644
--- a/src/tint/ast/builtin_attribute.cc
+++ b/src/tint/ast/builtin_attribute.cc
@@ -22,7 +22,7 @@
 
 namespace tint::ast {
 
-BuiltinAttribute::BuiltinAttribute(ProgramID pid, NodeID nid, const Source& src, Builtin b)
+BuiltinAttribute::BuiltinAttribute(ProgramID pid, NodeID nid, const Source& src, BuiltinValue b)
     : Base(pid, nid, src), builtin(b) {}
 
 BuiltinAttribute::~BuiltinAttribute() = default;
diff --git a/src/tint/ast/builtin_attribute.h b/src/tint/ast/builtin_attribute.h
index d0b3208..0aae24b 100644
--- a/src/tint/ast/builtin_attribute.h
+++ b/src/tint/ast/builtin_attribute.h
@@ -18,7 +18,7 @@
 #include <string>
 
 #include "src/tint/ast/attribute.h"
-#include "src/tint/ast/builtin.h"
+#include "src/tint/ast/builtin_value.h"
 
 namespace tint::ast {
 
@@ -30,7 +30,7 @@
     /// @param nid the unique node identifier
     /// @param src the source of this node
     /// @param builtin the builtin value
-    BuiltinAttribute(ProgramID pid, NodeID nid, const Source& src, Builtin builtin);
+    BuiltinAttribute(ProgramID pid, NodeID nid, const Source& src, BuiltinValue builtin);
     ~BuiltinAttribute() override;
 
     /// @returns the WGSL name for the attribute
@@ -43,7 +43,7 @@
     const BuiltinAttribute* Clone(CloneContext* ctx) const override;
 
     /// The builtin value
-    const Builtin builtin;
+    const BuiltinValue builtin;
 };
 
 }  // namespace tint::ast
diff --git a/src/tint/ast/builtin_attribute_test.cc b/src/tint/ast/builtin_attribute_test.cc
index a57f5b1..dba6997 100644
--- a/src/tint/ast/builtin_attribute_test.cc
+++ b/src/tint/ast/builtin_attribute_test.cc
@@ -20,8 +20,8 @@
 using BuiltinAttributeTest = TestHelper;
 
 TEST_F(BuiltinAttributeTest, Creation) {
-    auto* d = create<BuiltinAttribute>(Builtin::kFragDepth);
-    EXPECT_EQ(Builtin::kFragDepth, d->builtin);
+    auto* d = create<BuiltinAttribute>(BuiltinValue::kFragDepth);
+    EXPECT_EQ(BuiltinValue::kFragDepth, d->builtin);
 }
 
 }  // namespace
diff --git a/src/tint/ast/builtin.cc b/src/tint/ast/builtin_value.cc
similarity index 68%
rename from src/tint/ast/builtin.cc
rename to src/tint/ast/builtin_value.cc
index d215a5c..c1caafb 100644
--- a/src/tint/ast/builtin.cc
+++ b/src/tint/ast/builtin_value.cc
@@ -12,65 +12,65 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "src/tint/ast/builtin.h"
+#include "src/tint/ast/builtin_value.h"
 
 namespace tint::ast {
 
-std::ostream& operator<<(std::ostream& out, Builtin builtin) {
+std::ostream& operator<<(std::ostream& out, BuiltinValue builtin) {
     switch (builtin) {
-        case Builtin::kNone: {
+        case BuiltinValue::kNone: {
             out << "none";
             break;
         }
-        case Builtin::kPosition: {
+        case BuiltinValue::kPosition: {
             out << "position";
             break;
         }
-        case Builtin::kVertexIndex: {
+        case BuiltinValue::kVertexIndex: {
             out << "vertex_index";
             break;
         }
-        case Builtin::kInstanceIndex: {
+        case BuiltinValue::kInstanceIndex: {
             out << "instance_index";
             break;
         }
-        case Builtin::kFrontFacing: {
+        case BuiltinValue::kFrontFacing: {
             out << "front_facing";
             break;
         }
-        case Builtin::kFragDepth: {
+        case BuiltinValue::kFragDepth: {
             out << "frag_depth";
             break;
         }
-        case Builtin::kLocalInvocationId: {
+        case BuiltinValue::kLocalInvocationId: {
             out << "local_invocation_id";
             break;
         }
-        case Builtin::kLocalInvocationIndex: {
+        case BuiltinValue::kLocalInvocationIndex: {
             out << "local_invocation_index";
             break;
         }
-        case Builtin::kGlobalInvocationId: {
+        case BuiltinValue::kGlobalInvocationId: {
             out << "global_invocation_id";
             break;
         }
-        case Builtin::kWorkgroupId: {
+        case BuiltinValue::kWorkgroupId: {
             out << "workgroup_id";
             break;
         }
-        case Builtin::kNumWorkgroups: {
+        case BuiltinValue::kNumWorkgroups: {
             out << "num_workgroups";
             break;
         }
-        case Builtin::kSampleIndex: {
+        case BuiltinValue::kSampleIndex: {
             out << "sample_index";
             break;
         }
-        case Builtin::kSampleMask: {
+        case BuiltinValue::kSampleMask: {
             out << "sample_mask";
             break;
         }
-        case Builtin::kPointSize: {
+        case BuiltinValue::kPointSize: {
             out << "pointsize";
         }
     }
diff --git a/src/tint/ast/builtin.h b/src/tint/ast/builtin_value.h
similarity index 84%
rename from src/tint/ast/builtin.h
rename to src/tint/ast/builtin_value.h
index 699632a..68c1939 100644
--- a/src/tint/ast/builtin.h
+++ b/src/tint/ast/builtin_value.h
@@ -12,15 +12,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef SRC_TINT_AST_BUILTIN_H_
-#define SRC_TINT_AST_BUILTIN_H_
+#ifndef SRC_TINT_AST_BUILTIN_VALUE_H_
+#define SRC_TINT_AST_BUILTIN_VALUE_H_
 
 #include <ostream>
 
 namespace tint::ast {
 
 /// The builtin identifiers
-enum class Builtin {
+enum class BuiltinValue {
     kNone = -1,
     kPosition,
     kVertexIndex,
@@ -43,8 +43,8 @@
 /// @param out the std::ostream to write to
 /// @param builtin the Builtin
 /// @return the std::ostream so calls can be chained
-std::ostream& operator<<(std::ostream& out, Builtin builtin);
+std::ostream& operator<<(std::ostream& out, BuiltinValue builtin);
 
 }  // namespace tint::ast
 
-#endif  // SRC_TINT_AST_BUILTIN_H_
+#endif  // SRC_TINT_AST_BUILTIN_VALUE_H_
diff --git a/src/tint/ast/variable_test.cc b/src/tint/ast/variable_test.cc
index 025fa6b..a54e869 100644
--- a/src/tint/ast/variable_test.cc
+++ b/src/tint/ast/variable_test.cc
@@ -95,7 +95,7 @@
     auto* var = Var("my_var", ty.i32(), StorageClass::kFunction, nullptr,
                     AttributeList{
                         create<LocationAttribute>(1u),
-                        create<BuiltinAttribute>(Builtin::kPosition),
+                        create<BuiltinAttribute>(BuiltinValue::kPosition),
                         create<IdAttribute>(1200u),
                     });
 
diff --git a/src/tint/inspector/inspector.cc b/src/tint/inspector/inspector.cc
index 175afd3..931cd80 100644
--- a/src/tint/inspector/inspector.cc
+++ b/src/tint/inspector/inspector.cc
@@ -180,15 +180,15 @@
                                         entry_point.input_variables);
 
             entry_point.input_position_used |= ContainsBuiltin(
-                ast::Builtin::kPosition, param->Type(), param->Declaration()->attributes);
+                ast::BuiltinValue::kPosition, param->Type(), param->Declaration()->attributes);
             entry_point.front_facing_used |= ContainsBuiltin(
-                ast::Builtin::kFrontFacing, param->Type(), param->Declaration()->attributes);
+                ast::BuiltinValue::kFrontFacing, param->Type(), param->Declaration()->attributes);
             entry_point.sample_index_used |= ContainsBuiltin(
-                ast::Builtin::kSampleIndex, param->Type(), param->Declaration()->attributes);
+                ast::BuiltinValue::kSampleIndex, param->Type(), param->Declaration()->attributes);
             entry_point.input_sample_mask_used |= ContainsBuiltin(
-                ast::Builtin::kSampleMask, param->Type(), param->Declaration()->attributes);
+                ast::BuiltinValue::kSampleMask, param->Type(), param->Declaration()->attributes);
             entry_point.num_workgroups_used |= ContainsBuiltin(
-                ast::Builtin::kNumWorkgroups, param->Type(), param->Declaration()->attributes);
+                ast::BuiltinValue::kNumWorkgroups, param->Type(), param->Declaration()->attributes);
         }
 
         if (!sem->ReturnType()->Is<sem::Void>()) {
@@ -196,7 +196,7 @@
                                         entry_point.output_variables);
 
             entry_point.output_sample_mask_used = ContainsBuiltin(
-                ast::Builtin::kSampleMask, sem->ReturnType(), func->return_type_attributes);
+                ast::BuiltinValue::kSampleMask, sem->ReturnType(), func->return_type_attributes);
         }
 
         for (auto* var : sem->TransitivelyReferencedGlobals()) {
@@ -645,7 +645,7 @@
     variables.push_back(stage_variable);
 }
 
-bool Inspector::ContainsBuiltin(ast::Builtin builtin,
+bool Inspector::ContainsBuiltin(ast::BuiltinValue builtin,
                                 const sem::Type* type,
                                 const ast::AttributeList& attributes) const {
     auto* unwrapped_type = type->UnwrapRef();
diff --git a/src/tint/inspector/inspector.h b/src/tint/inspector/inspector.h
index a5aee17..bc31fda 100644
--- a/src/tint/inspector/inspector.h
+++ b/src/tint/inspector/inspector.h
@@ -174,7 +174,7 @@
     /// Recursively determine if the type contains builtin.
     /// If `type` is a struct, recurse into members to check for the attribute.
     /// Otherwise, check `attributes` for the attribute.
-    bool ContainsBuiltin(ast::Builtin builtin,
+    bool ContainsBuiltin(ast::BuiltinValue builtin,
                          const sem::Type* type,
                          const ast::AttributeList& attributes) const;
 
diff --git a/src/tint/inspector/inspector_test.cc b/src/tint/inspector/inspector_test.cc
index 5af4ae6..31cd803 100644
--- a/src/tint/inspector/inspector_test.cc
+++ b/src/tint/inspector/inspector_test.cc
@@ -434,7 +434,7 @@
 }
 
 TEST_F(InspectorGetEntryPointTest, BuiltInsNotStageVariables) {
-    auto* in_var0 = Param("in_var0", ty.u32(), {Builtin(ast::Builtin::kSampleIndex)});
+    auto* in_var0 = Param("in_var0", ty.u32(), {Builtin(ast::BuiltinValue::kSampleIndex)});
     auto* in_var1 = Param("in_var1", ty.f32(), {Location(0u)});
     Func("foo", {in_var0, in_var1}, ty.f32(),
          {
@@ -444,7 +444,7 @@
              Stage(ast::PipelineStage::kFragment),
          },
          {
-             Builtin(ast::Builtin::kFragDepth),
+             Builtin(ast::BuiltinValue::kFragDepth),
          });
     Inspector& inspector = Build();
 
@@ -811,7 +811,7 @@
 }
 
 TEST_F(InspectorGetEntryPointTest, InputSampleMaskSimpleReferenced) {
-    auto* in_var = Param("in_var", ty.u32(), {Builtin(ast::Builtin::kSampleMask)});
+    auto* in_var = Param("in_var", ty.u32(), {Builtin(ast::BuiltinValue::kSampleMask)});
     Func("ep_func", {in_var}, ty.void_(),
          {
              Return(),
@@ -830,7 +830,8 @@
 
 TEST_F(InspectorGetEntryPointTest, InputSampleMaskStructReferenced) {
     ast::StructMemberList members;
-    members.push_back(Member("inner_position", ty.u32(), {Builtin(ast::Builtin::kSampleMask)}));
+    members.push_back(
+        Member("inner_position", ty.u32(), {Builtin(ast::BuiltinValue::kSampleMask)}));
     Structure("in_struct", members);
 
     Func("ep_func",
@@ -856,7 +857,7 @@
 TEST_F(InspectorGetEntryPointTest, OutputSampleMaskSimpleReferenced) {
     Func("ep_func",
          {
-             Param("in_var", ty.u32(), {Builtin(ast::Builtin::kSampleMask)}),
+             Param("in_var", ty.u32(), {Builtin(ast::BuiltinValue::kSampleMask)}),
          },
          ty.u32(),
          {
@@ -865,7 +866,7 @@
          {
              Stage(ast::PipelineStage::kFragment),
          },
-         {Builtin(ast::Builtin::kSampleMask)});
+         {Builtin(ast::BuiltinValue::kSampleMask)});
 
     Inspector& inspector = Build();
 
@@ -878,7 +879,7 @@
 TEST_F(InspectorGetEntryPointTest, OutputSampleMaskStructReferenced) {
     Structure("out_struct",
               {
-                  Member("inner_sample_mask", ty.u32(), {Builtin(ast::Builtin::kSampleMask)}),
+                  Member("inner_sample_mask", ty.u32(), {Builtin(ast::BuiltinValue::kSampleMask)}),
               });
 
     Func("ep_func", {}, ty.type_name("out_struct"),
@@ -901,7 +902,7 @@
 TEST_F(InspectorGetEntryPointTest, InputPositionSimpleReferenced) {
     Func("ep_func",
          {
-             Param("in_var", ty.vec4<f32>(), {Builtin(ast::Builtin::kPosition)}),
+             Param("in_var", ty.vec4<f32>(), {Builtin(ast::BuiltinValue::kPosition)}),
          },
          ty.void_(),
          {
@@ -922,7 +923,7 @@
 TEST_F(InspectorGetEntryPointTest, InputPositionStructReferenced) {
     Structure("in_struct",
               {
-                  Member("inner_position", ty.vec4<f32>(), {Builtin(ast::Builtin::kPosition)}),
+                  Member("inner_position", ty.vec4<f32>(), {Builtin(ast::BuiltinValue::kPosition)}),
               });
 
     Func("ep_func",
@@ -948,7 +949,7 @@
 TEST_F(InspectorGetEntryPointTest, FrontFacingSimpleReferenced) {
     Func("ep_func",
          {
-             Param("in_var", ty.bool_(), {Builtin(ast::Builtin::kFrontFacing)}),
+             Param("in_var", ty.bool_(), {Builtin(ast::BuiltinValue::kFrontFacing)}),
          },
          ty.void_(),
          {
@@ -969,7 +970,7 @@
 TEST_F(InspectorGetEntryPointTest, FrontFacingStructReferenced) {
     Structure("in_struct",
               {
-                  Member("inner_position", ty.bool_(), {Builtin(ast::Builtin::kFrontFacing)}),
+                  Member("inner_position", ty.bool_(), {Builtin(ast::BuiltinValue::kFrontFacing)}),
               });
 
     Func("ep_func",
@@ -995,7 +996,7 @@
 TEST_F(InspectorGetEntryPointTest, SampleIndexSimpleReferenced) {
     Func("ep_func",
          {
-             Param("in_var", ty.u32(), {Builtin(ast::Builtin::kSampleIndex)}),
+             Param("in_var", ty.u32(), {Builtin(ast::BuiltinValue::kSampleIndex)}),
          },
          ty.void_(),
          {
@@ -1016,7 +1017,7 @@
 TEST_F(InspectorGetEntryPointTest, SampleIndexStructReferenced) {
     Structure("in_struct",
               {
-                  Member("inner_position", ty.u32(), {Builtin(ast::Builtin::kSampleIndex)}),
+                  Member("inner_position", ty.u32(), {Builtin(ast::BuiltinValue::kSampleIndex)}),
               });
 
     Func("ep_func",
@@ -1042,7 +1043,7 @@
 TEST_F(InspectorGetEntryPointTest, NumWorkgroupsSimpleReferenced) {
     Func("ep_func",
          {
-             Param("in_var", ty.vec3<u32>(), {Builtin(ast::Builtin::kNumWorkgroups)}),
+             Param("in_var", ty.vec3<u32>(), {Builtin(ast::BuiltinValue::kNumWorkgroups)}),
          },
          ty.void_(),
          {
@@ -1059,10 +1060,10 @@
 }
 
 TEST_F(InspectorGetEntryPointTest, NumWorkgroupsStructReferenced) {
-    Structure("in_struct",
-              {
-                  Member("inner_position", ty.vec3<u32>(), {Builtin(ast::Builtin::kNumWorkgroups)}),
-              });
+    Structure("in_struct", {
+                               Member("inner_position", ty.vec3<u32>(),
+                                      {Builtin(ast::BuiltinValue::kNumWorkgroups)}),
+                           });
 
     Func("ep_func",
          {
diff --git a/src/tint/program_builder.h b/src/tint/program_builder.h
index c6b163f..f7d91162 100644
--- a/src/tint/program_builder.h
+++ b/src/tint/program_builder.h
@@ -2518,14 +2518,14 @@
     /// @param source the source information
     /// @param builtin the builtin value
     /// @returns the builtin attribute pointer
-    const ast::BuiltinAttribute* Builtin(const Source& source, ast::Builtin builtin) {
+    const ast::BuiltinAttribute* Builtin(const Source& source, ast::BuiltinValue builtin) {
         return create<ast::BuiltinAttribute>(source, builtin);
     }
 
     /// Creates an ast::BuiltinAttribute
     /// @param builtin the builtin value
     /// @returns the builtin attribute pointer
-    const ast::BuiltinAttribute* Builtin(ast::Builtin builtin) {
+    const ast::BuiltinAttribute* Builtin(ast::BuiltinValue builtin) {
         return create<ast::BuiltinAttribute>(source_, builtin);
     }
 
diff --git a/src/tint/reader/spirv/enum_converter.cc b/src/tint/reader/spirv/enum_converter.cc
index 6f61ee6..a85ecbd 100644
--- a/src/tint/reader/spirv/enum_converter.cc
+++ b/src/tint/reader/spirv/enum_converter.cc
@@ -62,38 +62,38 @@
     return ast::StorageClass::kInvalid;
 }
 
-ast::Builtin EnumConverter::ToBuiltin(SpvBuiltIn b) {
+ast::BuiltinValue EnumConverter::ToBuiltin(SpvBuiltIn b) {
     switch (b) {
         case SpvBuiltInPosition:
-            return ast::Builtin::kPosition;
+            return ast::BuiltinValue::kPosition;
         case SpvBuiltInVertexIndex:
-            return ast::Builtin::kVertexIndex;
+            return ast::BuiltinValue::kVertexIndex;
         case SpvBuiltInInstanceIndex:
-            return ast::Builtin::kInstanceIndex;
+            return ast::BuiltinValue::kInstanceIndex;
         case SpvBuiltInFrontFacing:
-            return ast::Builtin::kFrontFacing;
+            return ast::BuiltinValue::kFrontFacing;
         case SpvBuiltInFragCoord:
-            return ast::Builtin::kPosition;
+            return ast::BuiltinValue::kPosition;
         case SpvBuiltInFragDepth:
-            return ast::Builtin::kFragDepth;
+            return ast::BuiltinValue::kFragDepth;
         case SpvBuiltInLocalInvocationId:
-            return ast::Builtin::kLocalInvocationId;
+            return ast::BuiltinValue::kLocalInvocationId;
         case SpvBuiltInLocalInvocationIndex:
-            return ast::Builtin::kLocalInvocationIndex;
+            return ast::BuiltinValue::kLocalInvocationIndex;
         case SpvBuiltInGlobalInvocationId:
-            return ast::Builtin::kGlobalInvocationId;
+            return ast::BuiltinValue::kGlobalInvocationId;
         case SpvBuiltInWorkgroupId:
-            return ast::Builtin::kWorkgroupId;
+            return ast::BuiltinValue::kWorkgroupId;
         case SpvBuiltInSampleId:
-            return ast::Builtin::kSampleIndex;
+            return ast::BuiltinValue::kSampleIndex;
         case SpvBuiltInSampleMask:
-            return ast::Builtin::kSampleMask;
+            return ast::BuiltinValue::kSampleMask;
         default:
             break;
     }
 
     Fail() << "unknown SPIR-V builtin: " << uint32_t(b);
-    return ast::Builtin::kNone;
+    return ast::BuiltinValue::kNone;
 }
 
 ast::TextureDimension EnumConverter::ToDim(SpvDim dim, bool arrayed) {
diff --git a/src/tint/reader/spirv/enum_converter.h b/src/tint/reader/spirv/enum_converter.h
index ac86f71..fc3ad83 100644
--- a/src/tint/reader/spirv/enum_converter.h
+++ b/src/tint/reader/spirv/enum_converter.h
@@ -16,7 +16,7 @@
 #define SRC_TINT_READER_SPIRV_ENUM_CONVERTER_H_
 
 #include "spirv/unified1/spirv.h"
-#include "src/tint/ast/builtin.h"
+#include "src/tint/ast/builtin_value.h"
 #include "src/tint/ast/pipeline_stage.h"
 #include "src/tint/ast/storage_class.h"
 #include "src/tint/reader/spirv/fail_stream.h"
@@ -49,7 +49,7 @@
     /// On failure, logs an error and returns kNone
     /// @param b the SPIR-V builtin
     /// @returns a Tint AST builtin
-    ast::Builtin ToBuiltin(SpvBuiltIn b);
+    ast::BuiltinValue ToBuiltin(SpvBuiltIn b);
 
     /// Converts a possibly arrayed SPIR-V Dim to a Tint texture dimension.
     /// On failure, logs an error and returns kNone
diff --git a/src/tint/reader/spirv/enum_converter_test.cc b/src/tint/reader/spirv/enum_converter_test.cc
index a7d8de1..3a6b914 100644
--- a/src/tint/reader/spirv/enum_converter_test.cc
+++ b/src/tint/reader/spirv/enum_converter_test.cc
@@ -144,7 +144,7 @@
 struct BuiltinCase {
     SpvBuiltIn builtin;
     bool expect_success;
-    ast::Builtin expected;
+    ast::BuiltinValue expected;
 };
 inline std::ostream& operator<<(std::ostream& out, BuiltinCase bc) {
     out << "BuiltinCase{ SpvBuiltIn:" << int(bc.builtin)
@@ -184,30 +184,30 @@
     EnumConverterGood_Input,
     SpvBuiltinTest,
     testing::Values(
-        BuiltinCase{SpvBuiltInPosition, true, ast::Builtin::kPosition},
-        BuiltinCase{SpvBuiltInInstanceIndex, true, ast::Builtin::kInstanceIndex},
-        BuiltinCase{SpvBuiltInFrontFacing, true, ast::Builtin::kFrontFacing},
-        BuiltinCase{SpvBuiltInFragCoord, true, ast::Builtin::kPosition},
-        BuiltinCase{SpvBuiltInLocalInvocationId, true, ast::Builtin::kLocalInvocationId},
-        BuiltinCase{SpvBuiltInLocalInvocationIndex, true, ast::Builtin::kLocalInvocationIndex},
-        BuiltinCase{SpvBuiltInGlobalInvocationId, true, ast::Builtin::kGlobalInvocationId},
-        BuiltinCase{SpvBuiltInWorkgroupId, true, ast::Builtin::kWorkgroupId},
-        BuiltinCase{SpvBuiltInSampleId, true, ast::Builtin::kSampleIndex},
-        BuiltinCase{SpvBuiltInSampleMask, true, ast::Builtin::kSampleMask}));
+        BuiltinCase{SpvBuiltInPosition, true, ast::BuiltinValue::kPosition},
+        BuiltinCase{SpvBuiltInInstanceIndex, true, ast::BuiltinValue::kInstanceIndex},
+        BuiltinCase{SpvBuiltInFrontFacing, true, ast::BuiltinValue::kFrontFacing},
+        BuiltinCase{SpvBuiltInFragCoord, true, ast::BuiltinValue::kPosition},
+        BuiltinCase{SpvBuiltInLocalInvocationId, true, ast::BuiltinValue::kLocalInvocationId},
+        BuiltinCase{SpvBuiltInLocalInvocationIndex, true, ast::BuiltinValue::kLocalInvocationIndex},
+        BuiltinCase{SpvBuiltInGlobalInvocationId, true, ast::BuiltinValue::kGlobalInvocationId},
+        BuiltinCase{SpvBuiltInWorkgroupId, true, ast::BuiltinValue::kWorkgroupId},
+        BuiltinCase{SpvBuiltInSampleId, true, ast::BuiltinValue::kSampleIndex},
+        BuiltinCase{SpvBuiltInSampleMask, true, ast::BuiltinValue::kSampleMask}));
 
 INSTANTIATE_TEST_SUITE_P(
     EnumConverterGood_Output,
     SpvBuiltinTest,
-    testing::Values(BuiltinCase{SpvBuiltInPosition, true, ast::Builtin::kPosition},
-                    BuiltinCase{SpvBuiltInFragDepth, true, ast::Builtin::kFragDepth},
-                    BuiltinCase{SpvBuiltInSampleMask, true, ast::Builtin::kSampleMask}));
+    testing::Values(BuiltinCase{SpvBuiltInPosition, true, ast::BuiltinValue::kPosition},
+                    BuiltinCase{SpvBuiltInFragDepth, true, ast::BuiltinValue::kFragDepth},
+                    BuiltinCase{SpvBuiltInSampleMask, true, ast::BuiltinValue::kSampleMask}));
 
 INSTANTIATE_TEST_SUITE_P(
     EnumConverterBad,
     SpvBuiltinTest,
-    testing::Values(BuiltinCase{static_cast<SpvBuiltIn>(9999), false, ast::Builtin::kNone},
-                    BuiltinCase{static_cast<SpvBuiltIn>(9999), false, ast::Builtin::kNone},
-                    BuiltinCase{SpvBuiltInNumWorkgroups, false, ast::Builtin::kNone}));
+    testing::Values(BuiltinCase{static_cast<SpvBuiltIn>(9999), false, ast::BuiltinValue::kNone},
+                    BuiltinCase{static_cast<SpvBuiltIn>(9999), false, ast::BuiltinValue::kNone},
+                    BuiltinCase{SpvBuiltInNumWorkgroups, false, ast::BuiltinValue::kNone}));
 
 // Dim
 
diff --git a/src/tint/reader/spirv/function.cc b/src/tint/reader/spirv/function.cc
index 7c10f89..06f9ecb 100644
--- a/src/tint/reader/spirv/function.cc
+++ b/src/tint/reader/spirv/function.cc
@@ -20,8 +20,8 @@
 #include "src/tint/ast/assignment_statement.h"
 #include "src/tint/ast/bitcast_expression.h"
 #include "src/tint/ast/break_statement.h"
-#include "src/tint/ast/builtin.h"
 #include "src/tint/ast/builtin_attribute.h"
+#include "src/tint/ast/builtin_value.h"
 #include "src/tint/ast/call_statement.h"
 #include "src/tint/ast/continue_statement.h"
 #include "src/tint/ast/discard_statement.h"
@@ -746,7 +746,7 @@
 /// @returns true if the decorations include a SampleMask builtin
 bool HasBuiltinSampleMask(const ast::AttributeList& decos) {
     if (auto* builtin = ast::GetAttribute<ast::BuiltinAttribute>(decos)) {
-        return builtin->builtin == ast::Builtin::kSampleMask;
+        return builtin->builtin == ast::BuiltinValue::kSampleMask;
     }
     return false;
 }
@@ -1332,7 +1332,7 @@
                 // a gl_Position variable.  Substitute the type.
                 const Type* param_type = ty_.Vector(ty_.F32(), 4);
                 ast::AttributeList out_decos{
-                    create<ast::BuiltinAttribute>(source, ast::Builtin::kPosition)};
+                    create<ast::BuiltinAttribute>(source, ast::BuiltinValue::kPosition)};
 
                 const auto var_name = namer_.GetName(var_id);
                 return_members.push_back(
diff --git a/src/tint/reader/spirv/parser_impl.cc b/src/tint/reader/spirv/parser_impl.cc
index 76ade39..4021c52 100644
--- a/src/tint/reader/spirv/parser_impl.cc
+++ b/src/tint/reader/spirv/parser_impl.cc
@@ -1676,7 +1676,7 @@
                     break;
             }
             auto ast_builtin = enum_converter_.ToBuiltin(spv_builtin);
-            if (ast_builtin == ast::Builtin::kNone) {
+            if (ast_builtin == ast::BuiltinValue::kNone) {
                 // A diagnostic has already been emitted.
                 return false;
             }
diff --git a/src/tint/reader/wgsl/parser_impl.cc b/src/tint/reader/wgsl/parser_impl.cc
index 754b05e..d3c170a 100644
--- a/src/tint/reader/wgsl/parser_impl.cc
+++ b/src/tint/reader/wgsl/parser_impl.cc
@@ -70,44 +70,44 @@
 const char kWriteAccess[] = "write";
 const char kReadWriteAccess[] = "read_write";
 
-ast::Builtin ident_to_builtin(std::string_view str) {
+ast::BuiltinValue ident_to_builtin(std::string_view str) {
     if (str == "position") {
-        return ast::Builtin::kPosition;
+        return ast::BuiltinValue::kPosition;
     }
     if (str == "vertex_index") {
-        return ast::Builtin::kVertexIndex;
+        return ast::BuiltinValue::kVertexIndex;
     }
     if (str == "instance_index") {
-        return ast::Builtin::kInstanceIndex;
+        return ast::BuiltinValue::kInstanceIndex;
     }
     if (str == "front_facing") {
-        return ast::Builtin::kFrontFacing;
+        return ast::BuiltinValue::kFrontFacing;
     }
     if (str == "frag_depth") {
-        return ast::Builtin::kFragDepth;
+        return ast::BuiltinValue::kFragDepth;
     }
     if (str == "local_invocation_id") {
-        return ast::Builtin::kLocalInvocationId;
+        return ast::BuiltinValue::kLocalInvocationId;
     }
     if (str == "local_invocation_idx" || str == "local_invocation_index") {
-        return ast::Builtin::kLocalInvocationIndex;
+        return ast::BuiltinValue::kLocalInvocationIndex;
     }
     if (str == "global_invocation_id") {
-        return ast::Builtin::kGlobalInvocationId;
+        return ast::BuiltinValue::kGlobalInvocationId;
     }
     if (str == "workgroup_id") {
-        return ast::Builtin::kWorkgroupId;
+        return ast::BuiltinValue::kWorkgroupId;
     }
     if (str == "num_workgroups") {
-        return ast::Builtin::kNumWorkgroups;
+        return ast::BuiltinValue::kNumWorkgroups;
     }
     if (str == "sample_index") {
-        return ast::Builtin::kSampleIndex;
+        return ast::BuiltinValue::kSampleIndex;
     }
     if (str == "sample_mask") {
-        return ast::Builtin::kSampleMask;
+        return ast::BuiltinValue::kSampleMask;
     }
-    return ast::Builtin::kNone;
+    return ast::BuiltinValue::kNone;
 }
 
 const char kBindingAttribute[] = "binding";
@@ -1558,14 +1558,14 @@
     return add_error(peek(), "invalid value for stage attribute");
 }
 
-Expect<ast::Builtin> ParserImpl::expect_builtin() {
+Expect<ast::BuiltinValue> ParserImpl::expect_builtin() {
     auto ident = expect_ident("builtin");
     if (ident.errored) {
         return Failure::kErrored;
     }
 
-    ast::Builtin builtin = ident_to_builtin(ident.value);
-    if (builtin == ast::Builtin::kNone) {
+    ast::BuiltinValue builtin = ident_to_builtin(ident.value);
+    if (builtin == ast::BuiltinValue::kNone) {
         return add_error(ident.source, "invalid value for builtin attribute");
     }
 
diff --git a/src/tint/reader/wgsl/parser_impl.h b/src/tint/reader/wgsl/parser_impl.h
index f86d865..3e378c7 100644
--- a/src/tint/reader/wgsl/parser_impl.h
+++ b/src/tint/reader/wgsl/parser_impl.h
@@ -475,7 +475,7 @@
     /// Parses a builtin identifier, erroring if the next token does not match a
     /// valid builtin name.
     /// @returns the parsed builtin.
-    Expect<ast::Builtin> expect_builtin();
+    Expect<ast::BuiltinValue> expect_builtin();
     /// Parses a `body_stmt` grammar element, erroring on parse failure.
     /// @returns the parsed statements
     Expect<ast::BlockStatement*> expect_body_stmt();
diff --git a/src/tint/reader/wgsl/parser_impl_param_list_test.cc b/src/tint/reader/wgsl/parser_impl_param_list_test.cc
index 99cd1f2..86a2f68 100644
--- a/src/tint/reader/wgsl/parser_impl_param_list_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_param_list_test.cc
@@ -105,7 +105,7 @@
     auto attrs_0 = e.value[0]->attributes;
     ASSERT_EQ(attrs_0.size(), 1u);
     EXPECT_TRUE(attrs_0[0]->Is<ast::BuiltinAttribute>());
-    EXPECT_EQ(attrs_0[0]->As<ast::BuiltinAttribute>()->builtin, ast::Builtin::kPosition);
+    EXPECT_EQ(attrs_0[0]->As<ast::BuiltinAttribute>()->builtin, ast::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/reader/wgsl/parser_impl_variable_attribute_list_test.cc b/src/tint/reader/wgsl/parser_impl_variable_attribute_list_test.cc
index 133dd36..e1da7b7 100644
--- a/src/tint/reader/wgsl/parser_impl_variable_attribute_list_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_variable_attribute_list_test.cc
@@ -33,7 +33,7 @@
     ASSERT_TRUE(attr_0->Is<ast::LocationAttribute>());
     EXPECT_EQ(attr_0->As<ast::LocationAttribute>()->value, 4u);
     ASSERT_TRUE(attr_1->Is<ast::BuiltinAttribute>());
-    EXPECT_EQ(attr_1->As<ast::BuiltinAttribute>()->builtin, ast::Builtin::kPosition);
+    EXPECT_EQ(attr_1->As<ast::BuiltinAttribute>()->builtin, ast::BuiltinValue::kPosition);
 }
 
 TEST_F(ParserImplTest, AttributeList_Invalid) {
diff --git a/src/tint/reader/wgsl/parser_impl_variable_attribute_test.cc b/src/tint/reader/wgsl/parser_impl_variable_attribute_test.cc
index 01e4260..05d822b 100644
--- a/src/tint/reader/wgsl/parser_impl_variable_attribute_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_variable_attribute_test.cc
@@ -89,7 +89,7 @@
 
 struct BuiltinData {
     const char* input;
-    ast::Builtin result;
+    ast::BuiltinValue result;
 };
 inline std::ostream& operator<<(std::ostream& out, BuiltinData data) {
     out << std::string(data.input);
@@ -133,19 +133,19 @@
 INSTANTIATE_TEST_SUITE_P(
     ParserImplTest,
     BuiltinTest,
-    testing::Values(BuiltinData{"position", ast::Builtin::kPosition},
-                    BuiltinData{"vertex_index", ast::Builtin::kVertexIndex},
-                    BuiltinData{"instance_index", ast::Builtin::kInstanceIndex},
-                    BuiltinData{"front_facing", ast::Builtin::kFrontFacing},
-                    BuiltinData{"frag_depth", ast::Builtin::kFragDepth},
-                    BuiltinData{"local_invocation_id", ast::Builtin::kLocalInvocationId},
-                    BuiltinData{"local_invocation_idx", ast::Builtin::kLocalInvocationIndex},
-                    BuiltinData{"local_invocation_index", ast::Builtin::kLocalInvocationIndex},
-                    BuiltinData{"global_invocation_id", ast::Builtin::kGlobalInvocationId},
-                    BuiltinData{"workgroup_id", ast::Builtin::kWorkgroupId},
-                    BuiltinData{"num_workgroups", ast::Builtin::kNumWorkgroups},
-                    BuiltinData{"sample_index", ast::Builtin::kSampleIndex},
-                    BuiltinData{"sample_mask", ast::Builtin::kSampleMask}));
+    testing::Values(BuiltinData{"position", ast::BuiltinValue::kPosition},
+                    BuiltinData{"vertex_index", ast::BuiltinValue::kVertexIndex},
+                    BuiltinData{"instance_index", ast::BuiltinValue::kInstanceIndex},
+                    BuiltinData{"front_facing", ast::BuiltinValue::kFrontFacing},
+                    BuiltinData{"frag_depth", ast::BuiltinValue::kFragDepth},
+                    BuiltinData{"local_invocation_id", ast::BuiltinValue::kLocalInvocationId},
+                    BuiltinData{"local_invocation_idx", ast::BuiltinValue::kLocalInvocationIndex},
+                    BuiltinData{"local_invocation_index", ast::BuiltinValue::kLocalInvocationIndex},
+                    BuiltinData{"global_invocation_id", ast::BuiltinValue::kGlobalInvocationId},
+                    BuiltinData{"workgroup_id", ast::BuiltinValue::kWorkgroupId},
+                    BuiltinData{"num_workgroups", ast::BuiltinValue::kNumWorkgroups},
+                    BuiltinData{"sample_index", ast::BuiltinValue::kSampleIndex},
+                    BuiltinData{"sample_mask", ast::BuiltinValue::kSampleMask}));
 
 TEST_F(ParserImplTest, Attribute_Builtin_MissingLeftParen) {
     auto p = parser("builtin position)");
diff --git a/src/tint/resolver/attribute_validation_test.cc b/src/tint/resolver/attribute_validation_test.cc
index 5f69277..2cb8905 100644
--- a/src/tint/resolver/attribute_validation_test.cc
+++ b/src/tint/resolver/attribute_validation_test.cc
@@ -93,7 +93,7 @@
         case AttributeKind::kBinding:
             return {builder.create<ast::BindingAttribute>(source, 1u)};
         case AttributeKind::kBuiltin:
-            return {builder.Builtin(source, ast::Builtin::kPosition)};
+            return {builder.Builtin(source, ast::BuiltinValue::kPosition)};
         case AttributeKind::kGroup:
             return {builder.create<ast::GroupAttribute>(source, 1u)};
         case AttributeKind::kId:
@@ -250,7 +250,7 @@
     auto& params = GetParam();
     auto attrs = createAttributes(Source{{12, 34}}, *this, params.kind);
     if (params.kind != AttributeKind::kBuiltin && params.kind != AttributeKind::kLocation) {
-        attrs.push_back(Builtin(Source{{34, 56}}, ast::Builtin::kPosition));
+        attrs.push_back(Builtin(Source{{34, 56}}, ast::BuiltinValue::kPosition));
     }
     auto* p = Param("a", ty.vec4<f32>(), attrs);
     Func("frag_main", {p}, ty.void_(), {},
@@ -298,7 +298,7 @@
              Stage(ast::PipelineStage::kVertex),
          },
          {
-             Builtin(ast::Builtin::kPosition),
+             Builtin(ast::BuiltinValue::kPosition),
          });
 
     if (params.should_pass) {
@@ -442,7 +442,7 @@
     auto attrs = createAttributes(Source{{12, 34}}, *this, params.kind);
     // a vertex shader must include the 'position' builtin in its return type
     if (params.kind != AttributeKind::kBuiltin) {
-        attrs.push_back(Builtin(Source{{34, 56}}, ast::Builtin::kPosition));
+        attrs.push_back(Builtin(Source{{34, 56}}, ast::BuiltinValue::kPosition));
     }
     Func("vertex_main", {}, ty.vec4<f32>(),
          {
@@ -645,7 +645,7 @@
                               Member("a", ty.vec4<f32>(),
                                      {
                                          Invariant(),
-                                         Builtin(ast::Builtin::kPosition),
+                                         Builtin(ast::BuiltinValue::kPosition),
                                      }),
                           });
     WrapInFunction();
@@ -1140,9 +1140,9 @@
 namespace {
 using InvariantAttributeTests = ResolverTest;
 TEST_F(InvariantAttributeTests, InvariantWithPosition) {
-    auto* param =
-        Param("p", ty.vec4<f32>(),
-              {Invariant(Source{{12, 34}}), Builtin(Source{{56, 78}}, ast::Builtin::kPosition)});
+    auto* param = Param(
+        "p", ty.vec4<f32>(),
+        {Invariant(Source{{12, 34}}), Builtin(Source{{56, 78}}, ast::BuiltinValue::kPosition)});
     Func("main", {param}, ty.vec4<f32>(),
          {
              Return(Construct(ty.vec4<f32>())),
@@ -1368,10 +1368,11 @@
 }
 
 TEST_F(InterpolateTest, VertexOutput_Integer_MissingFlatInterpolation) {
-    auto* s = Structure("S", {
-                                 Member("pos", ty.vec4<f32>(), {Builtin(ast::Builtin::kPosition)}),
-                                 Member(Source{{12, 34}}, "u", ty.u32(), {Location(0)}),
-                             });
+    auto* s =
+        Structure("S", {
+                           Member("pos", ty.vec4<f32>(), {Builtin(ast::BuiltinValue::kPosition)}),
+                           Member(Source{{12, 34}}, "u", ty.u32(), {Location(0)}),
+                       });
     Func("main", {}, ty.Of(s),
          {
              Return(Construct(ty.Of(s))),
@@ -1392,7 +1393,7 @@
          {
              Param("a", ty.vec4<f32>(),
                    {
-                       Builtin(ast::Builtin::kPosition),
+                       Builtin(ast::BuiltinValue::kPosition),
                        Interpolate(Source{{12, 34}}, ast::InterpolationType::kFlat,
                                    ast::InterpolationSampling::kNone),
                    }),
@@ -1416,7 +1417,7 @@
              Stage(ast::PipelineStage::kVertex),
          },
          {
-             Builtin(ast::Builtin::kPosition),
+             Builtin(ast::BuiltinValue::kPosition),
              Interpolate(Source{{12, 34}}, ast::InterpolationType::kFlat,
                          ast::InterpolationSampling::kNone),
          });
diff --git a/src/tint/resolver/builtins_validation_test.cc b/src/tint/resolver/builtins_validation_test.cc
index 15fd2b6..c3ec4a7 100644
--- a/src/tint/resolver/builtins_validation_test.cc
+++ b/src/tint/resolver/builtins_validation_test.cc
@@ -33,59 +33,67 @@
 namespace StageTest {
 struct Params {
     builder::ast_type_func_ptr type;
-    ast::Builtin builtin;
+    ast::BuiltinValue builtin;
     ast::PipelineStage stage;
     bool is_valid;
 };
 
 template <typename T>
-constexpr Params ParamsFor(ast::Builtin builtin, ast::PipelineStage stage, bool is_valid) {
+constexpr Params ParamsFor(ast::BuiltinValue builtin, ast::PipelineStage stage, bool is_valid) {
     return Params{DataType<T>::AST, builtin, stage, is_valid};
 }
 static constexpr Params cases[] = {
-    ParamsFor<vec4<f32>>(ast::Builtin::kPosition, ast::PipelineStage::kVertex, false),
-    ParamsFor<vec4<f32>>(ast::Builtin::kPosition, ast::PipelineStage::kFragment, true),
-    ParamsFor<vec4<f32>>(ast::Builtin::kPosition, ast::PipelineStage::kCompute, false),
+    ParamsFor<vec4<f32>>(ast::BuiltinValue::kPosition, ast::PipelineStage::kVertex, false),
+    ParamsFor<vec4<f32>>(ast::BuiltinValue::kPosition, ast::PipelineStage::kFragment, true),
+    ParamsFor<vec4<f32>>(ast::BuiltinValue::kPosition, ast::PipelineStage::kCompute, false),
 
-    ParamsFor<u32>(ast::Builtin::kVertexIndex, ast::PipelineStage::kVertex, true),
-    ParamsFor<u32>(ast::Builtin::kVertexIndex, ast::PipelineStage::kFragment, false),
-    ParamsFor<u32>(ast::Builtin::kVertexIndex, ast::PipelineStage::kCompute, false),
+    ParamsFor<u32>(ast::BuiltinValue::kVertexIndex, ast::PipelineStage::kVertex, true),
+    ParamsFor<u32>(ast::BuiltinValue::kVertexIndex, ast::PipelineStage::kFragment, false),
+    ParamsFor<u32>(ast::BuiltinValue::kVertexIndex, ast::PipelineStage::kCompute, false),
 
-    ParamsFor<u32>(ast::Builtin::kInstanceIndex, ast::PipelineStage::kVertex, true),
-    ParamsFor<u32>(ast::Builtin::kInstanceIndex, ast::PipelineStage::kFragment, false),
-    ParamsFor<u32>(ast::Builtin::kInstanceIndex, ast::PipelineStage::kCompute, false),
+    ParamsFor<u32>(ast::BuiltinValue::kInstanceIndex, ast::PipelineStage::kVertex, true),
+    ParamsFor<u32>(ast::BuiltinValue::kInstanceIndex, ast::PipelineStage::kFragment, false),
+    ParamsFor<u32>(ast::BuiltinValue::kInstanceIndex, ast::PipelineStage::kCompute, false),
 
-    ParamsFor<bool>(ast::Builtin::kFrontFacing, ast::PipelineStage::kVertex, false),
-    ParamsFor<bool>(ast::Builtin::kFrontFacing, ast::PipelineStage::kFragment, true),
-    ParamsFor<bool>(ast::Builtin::kFrontFacing, ast::PipelineStage::kCompute, false),
+    ParamsFor<bool>(ast::BuiltinValue::kFrontFacing, ast::PipelineStage::kVertex, false),
+    ParamsFor<bool>(ast::BuiltinValue::kFrontFacing, ast::PipelineStage::kFragment, true),
+    ParamsFor<bool>(ast::BuiltinValue::kFrontFacing, ast::PipelineStage::kCompute, false),
 
-    ParamsFor<vec3<u32>>(ast::Builtin::kLocalInvocationId, ast::PipelineStage::kVertex, false),
-    ParamsFor<vec3<u32>>(ast::Builtin::kLocalInvocationId, ast::PipelineStage::kFragment, false),
-    ParamsFor<vec3<u32>>(ast::Builtin::kLocalInvocationId, ast::PipelineStage::kCompute, true),
+    ParamsFor<vec3<u32>>(ast::BuiltinValue::kLocalInvocationId, ast::PipelineStage::kVertex, false),
+    ParamsFor<vec3<u32>>(ast::BuiltinValue::kLocalInvocationId,
+                         ast::PipelineStage::kFragment,
+                         false),
+    ParamsFor<vec3<u32>>(ast::BuiltinValue::kLocalInvocationId, ast::PipelineStage::kCompute, true),
 
-    ParamsFor<u32>(ast::Builtin::kLocalInvocationIndex, ast::PipelineStage::kVertex, false),
-    ParamsFor<u32>(ast::Builtin::kLocalInvocationIndex, ast::PipelineStage::kFragment, false),
-    ParamsFor<u32>(ast::Builtin::kLocalInvocationIndex, ast::PipelineStage::kCompute, true),
+    ParamsFor<u32>(ast::BuiltinValue::kLocalInvocationIndex, ast::PipelineStage::kVertex, false),
+    ParamsFor<u32>(ast::BuiltinValue::kLocalInvocationIndex, ast::PipelineStage::kFragment, false),
+    ParamsFor<u32>(ast::BuiltinValue::kLocalInvocationIndex, ast::PipelineStage::kCompute, true),
 
-    ParamsFor<vec3<u32>>(ast::Builtin::kGlobalInvocationId, ast::PipelineStage::kVertex, false),
-    ParamsFor<vec3<u32>>(ast::Builtin::kGlobalInvocationId, ast::PipelineStage::kFragment, false),
-    ParamsFor<vec3<u32>>(ast::Builtin::kGlobalInvocationId, ast::PipelineStage::kCompute, true),
+    ParamsFor<vec3<u32>>(ast::BuiltinValue::kGlobalInvocationId,
+                         ast::PipelineStage::kVertex,
+                         false),
+    ParamsFor<vec3<u32>>(ast::BuiltinValue::kGlobalInvocationId,
+                         ast::PipelineStage::kFragment,
+                         false),
+    ParamsFor<vec3<u32>>(ast::BuiltinValue::kGlobalInvocationId,
+                         ast::PipelineStage::kCompute,
+                         true),
 
-    ParamsFor<vec3<u32>>(ast::Builtin::kWorkgroupId, ast::PipelineStage::kVertex, false),
-    ParamsFor<vec3<u32>>(ast::Builtin::kWorkgroupId, ast::PipelineStage::kFragment, false),
-    ParamsFor<vec3<u32>>(ast::Builtin::kWorkgroupId, ast::PipelineStage::kCompute, true),
+    ParamsFor<vec3<u32>>(ast::BuiltinValue::kWorkgroupId, ast::PipelineStage::kVertex, false),
+    ParamsFor<vec3<u32>>(ast::BuiltinValue::kWorkgroupId, ast::PipelineStage::kFragment, false),
+    ParamsFor<vec3<u32>>(ast::BuiltinValue::kWorkgroupId, ast::PipelineStage::kCompute, true),
 
-    ParamsFor<vec3<u32>>(ast::Builtin::kNumWorkgroups, ast::PipelineStage::kVertex, false),
-    ParamsFor<vec3<u32>>(ast::Builtin::kNumWorkgroups, ast::PipelineStage::kFragment, false),
-    ParamsFor<vec3<u32>>(ast::Builtin::kNumWorkgroups, ast::PipelineStage::kCompute, true),
+    ParamsFor<vec3<u32>>(ast::BuiltinValue::kNumWorkgroups, ast::PipelineStage::kVertex, false),
+    ParamsFor<vec3<u32>>(ast::BuiltinValue::kNumWorkgroups, ast::PipelineStage::kFragment, false),
+    ParamsFor<vec3<u32>>(ast::BuiltinValue::kNumWorkgroups, ast::PipelineStage::kCompute, true),
 
-    ParamsFor<u32>(ast::Builtin::kSampleIndex, ast::PipelineStage::kVertex, false),
-    ParamsFor<u32>(ast::Builtin::kSampleIndex, ast::PipelineStage::kFragment, true),
-    ParamsFor<u32>(ast::Builtin::kSampleIndex, ast::PipelineStage::kCompute, false),
+    ParamsFor<u32>(ast::BuiltinValue::kSampleIndex, ast::PipelineStage::kVertex, false),
+    ParamsFor<u32>(ast::BuiltinValue::kSampleIndex, ast::PipelineStage::kFragment, true),
+    ParamsFor<u32>(ast::BuiltinValue::kSampleIndex, ast::PipelineStage::kCompute, false),
 
-    ParamsFor<u32>(ast::Builtin::kSampleMask, ast::PipelineStage::kVertex, false),
-    ParamsFor<u32>(ast::Builtin::kSampleMask, ast::PipelineStage::kFragment, true),
-    ParamsFor<u32>(ast::Builtin::kSampleMask, ast::PipelineStage::kCompute, false),
+    ParamsFor<u32>(ast::BuiltinValue::kSampleMask, ast::PipelineStage::kVertex, false),
+    ParamsFor<u32>(ast::BuiltinValue::kSampleMask, ast::PipelineStage::kFragment, true),
+    ParamsFor<u32>(ast::BuiltinValue::kSampleMask, ast::PipelineStage::kCompute, false),
 };
 
 using ResolverBuiltinsStageTest = ResolverTestWithParam<Params>;
@@ -97,7 +105,7 @@
     switch (params.stage) {
         case ast::PipelineStage::kVertex:
             Func("main", {input}, ty.vec4<f32>(), {Return(p)}, {Stage(ast::PipelineStage::kVertex)},
-                 {Builtin(Source{{12, 34}}, ast::Builtin::kPosition)});
+                 {Builtin(Source{{12, 34}}, ast::BuiltinValue::kPosition)});
             break;
         case ast::PipelineStage::kFragment:
             Func("main", {input}, ty.void_(), {}, {Stage(ast::PipelineStage::kFragment)}, {});
@@ -134,7 +142,7 @@
     // ) -> @location(0) f32 { return 1.0; }
     Func("fs_main",
          {
-             Param("fd", ty.f32(), {Builtin(Source{{12, 34}}, ast::Builtin::kFragDepth)}),
+             Param("fd", ty.f32(), {Builtin(Source{{12, 34}}, ast::BuiltinValue::kFragDepth)}),
          },
          ty.f32(),
          {
@@ -159,11 +167,11 @@
     // @fragment
     // fn fragShader(arg: MyInputs) -> @location(0) f32 { return 1.0; }
 
-    auto* s = Structure(
-        "MyInputs",
-        {
-            Member("frag_depth", ty.f32(), {Builtin(Source{{12, 34}}, ast::Builtin::kFragDepth)}),
-        });
+    auto* s = Structure("MyInputs",
+                        {
+                            Member("frag_depth", ty.f32(),
+                                   {Builtin(Source{{12, 34}}, ast::BuiltinValue::kFragDepth)}),
+                        });
 
     Func("fragShader",
          {
@@ -193,7 +201,7 @@
     // @fragment
     // fn fragShader() { var s : S; }
 
-    Structure("S", {Member("idx", ty.u32(), {Builtin(ast::Builtin::kVertexIndex)})});
+    Structure("S", {Member("idx", ty.u32(), {Builtin(ast::BuiltinValue::kVertexIndex)})});
 
     Func("fragShader", {}, ty.void_(), {Decl(Var("s", ty.type_name("S")))},
          {Stage(ast::PipelineStage::kFragment)});
@@ -212,7 +220,7 @@
     auto* s =
         Structure("MyInputs", {
                                   Member("position", ty.vec4<u32>(),
-                                         {Builtin(Source{{12, 34}}, ast::Builtin::kPosition)}),
+                                         {Builtin(Source{{12, 34}}, ast::BuiltinValue::kPosition)}),
                               });
     Func("fragShader",
          {
@@ -237,7 +245,7 @@
     // @vertex
     // fn main() -> @builtin(position) f32 { return 1.0; }
     Func("main", {}, ty.f32(), {Return(1_f)}, {Stage(ast::PipelineStage::kVertex)},
-         {Builtin(Source{{12, 34}}, ast::Builtin::kPosition)});
+         {Builtin(Source{{12, 34}}, ast::BuiltinValue::kPosition)});
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), "12:34 error: store type of builtin(position) must be 'vec4<f32>'");
@@ -250,11 +258,11 @@
     // @fragment
     // fn fragShader(is_front: MyInputs) -> @location(0) f32 { return 1.0; }
 
-    auto* s = Structure(
-        "MyInputs",
-        {
-            Member("frag_depth", ty.i32(), {Builtin(Source{{12, 34}}, ast::Builtin::kFragDepth)}),
-        });
+    auto* s = Structure("MyInputs",
+                        {
+                            Member("frag_depth", ty.i32(),
+                                   {Builtin(Source{{12, 34}}, ast::BuiltinValue::kFragDepth)}),
+                        });
     Func("fragShader", {Param("arg", ty.Of(s))}, ty.f32(),
          {
              Return(1_f),
@@ -277,11 +285,11 @@
     // @fragment
     // fn fragShader(is_front: MyInputs) -> @location(0) f32 { return 1.0; }
 
-    auto* s =
-        Structure("MyInputs",
-                  {
-                      Member("m", ty.f32(), {Builtin(Source{{12, 34}}, ast::Builtin::kSampleMask)}),
-                  });
+    auto* s = Structure(
+        "MyInputs",
+        {
+            Member("m", ty.f32(), {Builtin(Source{{12, 34}}, ast::BuiltinValue::kSampleMask)}),
+        });
     Func("fragShader", {Param("arg", ty.Of(s))}, ty.f32(), {Return(1_f)},
          {Stage(ast::PipelineStage::kFragment)}, {Location(0)});
 
@@ -293,7 +301,7 @@
     // @fragment
     // fn main() -> @builtin(sample_mask) i32 { return 1; }
     Func("main", {}, ty.i32(), {Return(1_i)}, {Stage(ast::PipelineStage::kFragment)},
-         {Builtin(Source{{12, 34}}, ast::Builtin::kSampleMask)});
+         {Builtin(Source{{12, 34}}, ast::BuiltinValue::kSampleMask)});
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), "12:34 error: store type of builtin(sample_mask) must be 'u32'");
@@ -306,7 +314,7 @@
     // ) -> @location(0) f32 { return 1.0; }
     Func("fs_main",
          {
-             Param("arg", ty.bool_(), {Builtin(Source{{12, 34}}, ast::Builtin::kSampleMask)}),
+             Param("arg", ty.bool_(), {Builtin(Source{{12, 34}}, ast::BuiltinValue::kSampleMask)}),
          },
          ty.f32(),
          {
@@ -332,7 +340,7 @@
     auto* s = Structure(
         "MyInputs",
         {
-            Member("m", ty.f32(), {Builtin(Source{{12, 34}}, ast::Builtin::kSampleIndex)}),
+            Member("m", ty.f32(), {Builtin(Source{{12, 34}}, ast::BuiltinValue::kSampleIndex)}),
         });
     Func("fragShader", {Param("arg", ty.Of(s))}, ty.f32(), {Return(1_f)},
          {Stage(ast::PipelineStage::kFragment)}, {Location(0)});
@@ -348,7 +356,7 @@
     // ) -> @location(0) f32 { return 1.0; }
     Func("fs_main",
          {
-             Param("arg", ty.bool_(), {Builtin(Source{{12, 34}}, ast::Builtin::kSampleIndex)}),
+             Param("arg", ty.bool_(), {Builtin(Source{{12, 34}}, ast::BuiltinValue::kSampleIndex)}),
          },
          ty.f32(), {Return(1_f)},
          {
@@ -368,7 +376,7 @@
     // ) -> @location(0) f32 { return 1.0; }
     Func("fs_main",
          {
-             Param("p", ty.vec3<f32>(), {Builtin(Source{{12, 34}}, ast::Builtin::kPosition)}),
+             Param("p", ty.vec3<f32>(), {Builtin(Source{{12, 34}}, ast::BuiltinValue::kPosition)}),
          },
          ty.f32(), {Return(1_f)},
          {
@@ -386,7 +394,7 @@
     // fn fs_main() -> @builtin(kFragDepth) f32 { var fd: i32; return fd; }
     auto* fd = Var("fd", ty.i32());
     Func("fs_main", {}, ty.i32(), {Decl(fd), Return(fd)}, {Stage(ast::PipelineStage::kFragment)},
-         {Builtin(Source{{12, 34}}, ast::Builtin::kFragDepth)});
+         {Builtin(Source{{12, 34}}, ast::BuiltinValue::kFragDepth)});
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), "12:34 error: store type of builtin(frag_depth) must be 'f32'");
 }
@@ -397,10 +405,10 @@
     //   @builtin(kVertexIndex) vi : f32,
     //   @builtin(kPosition) p :vec4<f32>
     // ) -> @builtin(kPosition) vec4<f32> { return vec4<f32>(); }
-    auto* p = Param("p", ty.vec4<f32>(), {Builtin(ast::Builtin::kPosition)});
-    auto* vi = Param("vi", ty.f32(), {Builtin(Source{{12, 34}}, ast::Builtin::kVertexIndex)});
+    auto* p = Param("p", ty.vec4<f32>(), {Builtin(ast::BuiltinValue::kPosition)});
+    auto* vi = Param("vi", ty.f32(), {Builtin(Source{{12, 34}}, ast::BuiltinValue::kVertexIndex)});
     Func("main", {vi, p}, ty.vec4<f32>(), {Return(Expr("p"))}, {Stage(ast::PipelineStage::kVertex)},
-         {Builtin(ast::Builtin::kPosition)});
+         {Builtin(ast::BuiltinValue::kPosition)});
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), "12:34 error: store type of builtin(vertex_index) must be 'u32'");
 }
@@ -411,10 +419,11 @@
     //   @builtin(kInstanceIndex) ii : f32,
     //   @builtin(kPosition) p :vec4<f32>
     // ) -> @builtin(kPosition) vec4<f32> { return vec4<f32>(); }
-    auto* p = Param("p", ty.vec4<f32>(), {Builtin(ast::Builtin::kPosition)});
-    auto* ii = Param("ii", ty.f32(), {Builtin(Source{{12, 34}}, ast::Builtin::kInstanceIndex)});
+    auto* p = Param("p", ty.vec4<f32>(), {Builtin(ast::BuiltinValue::kPosition)});
+    auto* ii =
+        Param("ii", ty.f32(), {Builtin(Source{{12, 34}}, ast::BuiltinValue::kInstanceIndex)});
     Func("main", {ii, p}, ty.vec4<f32>(), {Return(Expr("p"))}, {Stage(ast::PipelineStage::kVertex)},
-         {Builtin(ast::Builtin::kPosition)});
+         {Builtin(ast::BuiltinValue::kPosition)});
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), "12:34 error: store type of builtin(instance_index) must be 'u32'");
 }
@@ -427,13 +436,13 @@
     //   @builtin(sample_index) si: u32,
     //   @builtin(sample_mask) sm : u32
     // ) -> @builtin(frag_depth) f32 { var fd: f32; return fd; }
-    auto* p = Param("p", ty.vec4<f32>(), {Builtin(ast::Builtin::kPosition)});
-    auto* ff = Param("ff", ty.bool_(), {Builtin(ast::Builtin::kFrontFacing)});
-    auto* si = Param("si", ty.u32(), {Builtin(ast::Builtin::kSampleIndex)});
-    auto* sm = Param("sm", ty.u32(), {Builtin(ast::Builtin::kSampleMask)});
+    auto* p = Param("p", ty.vec4<f32>(), {Builtin(ast::BuiltinValue::kPosition)});
+    auto* ff = Param("ff", ty.bool_(), {Builtin(ast::BuiltinValue::kFrontFacing)});
+    auto* si = Param("si", ty.u32(), {Builtin(ast::BuiltinValue::kSampleIndex)});
+    auto* sm = Param("sm", ty.u32(), {Builtin(ast::BuiltinValue::kSampleMask)});
     auto* var_fd = Var("fd", ty.f32());
     Func("fs_main", {p, ff, si, sm}, ty.f32(), {Decl(var_fd), Return(var_fd)},
-         {Stage(ast::PipelineStage::kFragment)}, {Builtin(ast::Builtin::kFragDepth)});
+         {Stage(ast::PipelineStage::kFragment)}, {Builtin(ast::BuiltinValue::kFragDepth)});
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
 
@@ -443,16 +452,17 @@
     //   @builtin(vertex_index) vi : u32,
     //   @builtin(instance_index) ii : u32,
     // ) -> @builtin(position) vec4<f32> { var p :vec4<f32>; return p; }
-    auto* vi = Param("vi", ty.u32(), {Builtin(Source{{12, 34}}, ast::Builtin::kVertexIndex)});
+    auto* vi = Param("vi", ty.u32(), {Builtin(Source{{12, 34}}, ast::BuiltinValue::kVertexIndex)});
 
-    auto* ii = Param("ii", ty.u32(), {Builtin(Source{{12, 34}}, ast::Builtin::kInstanceIndex)});
+    auto* ii =
+        Param("ii", ty.u32(), {Builtin(Source{{12, 34}}, ast::BuiltinValue::kInstanceIndex)});
     auto* p = Var("p", ty.vec4<f32>());
     Func("main", {vi, ii}, ty.vec4<f32>(),
          {
              Decl(p),
              Return(p),
          },
-         {Stage(ast::PipelineStage::kVertex)}, {Builtin(ast::Builtin::kPosition)});
+         {Stage(ast::PipelineStage::kVertex)}, {Builtin(ast::BuiltinValue::kPosition)});
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
@@ -467,11 +477,12 @@
     //   @builtin(num_workgroups) nwgs: vec3<u32>,
     // ) {}
 
-    auto* li_id = Param("li_id", ty.vec3<u32>(), {Builtin(ast::Builtin::kLocalInvocationId)});
-    auto* li_index = Param("li_index", ty.u32(), {Builtin(ast::Builtin::kLocalInvocationIndex)});
-    auto* gi = Param("gi", ty.vec3<u32>(), {Builtin(ast::Builtin::kGlobalInvocationId)});
-    auto* wi = Param("wi", ty.vec3<u32>(), {Builtin(ast::Builtin::kWorkgroupId)});
-    auto* nwgs = Param("nwgs", ty.vec3<u32>(), {Builtin(ast::Builtin::kNumWorkgroups)});
+    auto* li_id = Param("li_id", ty.vec3<u32>(), {Builtin(ast::BuiltinValue::kLocalInvocationId)});
+    auto* li_index =
+        Param("li_index", ty.u32(), {Builtin(ast::BuiltinValue::kLocalInvocationIndex)});
+    auto* gi = Param("gi", ty.vec3<u32>(), {Builtin(ast::BuiltinValue::kGlobalInvocationId)});
+    auto* wi = Param("wi", ty.vec3<u32>(), {Builtin(ast::BuiltinValue::kWorkgroupId)});
+    auto* nwgs = Param("nwgs", ty.vec3<u32>(), {Builtin(ast::BuiltinValue::kNumWorkgroups)});
 
     Func("main", {li_id, li_index, gi, wi, nwgs}, ty.void_(), {},
          {Stage(ast::PipelineStage::kCompute),
@@ -481,7 +492,7 @@
 }
 
 TEST_F(ResolverBuiltinsValidationTest, ComputeBuiltin_WorkGroupIdNotVec3U32) {
-    auto* wi = Param("wi", ty.f32(), {Builtin(Source{{12, 34}}, ast::Builtin::kWorkgroupId)});
+    auto* wi = Param("wi", ty.f32(), {Builtin(Source{{12, 34}}, ast::BuiltinValue::kWorkgroupId)});
     Func("main", {wi}, ty.void_(), {},
          {Stage(ast::PipelineStage::kCompute),
           WorkgroupSize(Expr(Source{Source::Location{12, 34}}, 2_i))});
@@ -493,7 +504,8 @@
 }
 
 TEST_F(ResolverBuiltinsValidationTest, ComputeBuiltin_NumWorkgroupsNotVec3U32) {
-    auto* nwgs = Param("nwgs", ty.f32(), {Builtin(Source{{12, 34}}, ast::Builtin::kNumWorkgroups)});
+    auto* nwgs =
+        Param("nwgs", ty.f32(), {Builtin(Source{{12, 34}}, ast::BuiltinValue::kNumWorkgroups)});
     Func("main", {nwgs}, ty.void_(), {},
          {Stage(ast::PipelineStage::kCompute),
           WorkgroupSize(Expr(Source{Source::Location{12, 34}}, 2_i))});
@@ -505,8 +517,8 @@
 }
 
 TEST_F(ResolverBuiltinsValidationTest, ComputeBuiltin_GlobalInvocationNotVec3U32) {
-    auto* gi =
-        Param("gi", ty.vec3<i32>(), {Builtin(Source{{12, 34}}, ast::Builtin::kGlobalInvocationId)});
+    auto* gi = Param("gi", ty.vec3<i32>(),
+                     {Builtin(Source{{12, 34}}, ast::BuiltinValue::kGlobalInvocationId)});
     Func("main", {gi}, ty.void_(), {},
          {Stage(ast::PipelineStage::kCompute),
           WorkgroupSize(Expr(Source{Source::Location{12, 34}}, 2_i))});
@@ -519,7 +531,7 @@
 
 TEST_F(ResolverBuiltinsValidationTest, ComputeBuiltin_LocalInvocationIndexNotU32) {
     auto* li_index = Param("li_index", ty.vec3<u32>(),
-                           {Builtin(Source{{12, 34}}, ast::Builtin::kLocalInvocationIndex)});
+                           {Builtin(Source{{12, 34}}, ast::BuiltinValue::kLocalInvocationIndex)});
     Func("main", {li_index}, ty.void_(), {},
          {Stage(ast::PipelineStage::kCompute),
           WorkgroupSize(Expr(Source{Source::Location{12, 34}}, 2_i))});
@@ -532,7 +544,7 @@
 
 TEST_F(ResolverBuiltinsValidationTest, ComputeBuiltin_LocalInvocationNotVec3U32) {
     auto* li_id = Param("li_id", ty.vec2<u32>(),
-                        {Builtin(Source{{12, 34}}, ast::Builtin::kLocalInvocationId)});
+                        {Builtin(Source{{12, 34}}, ast::BuiltinValue::kLocalInvocationId)});
     Func("main", {li_id}, ty.void_(), {},
          {Stage(ast::PipelineStage::kCompute),
           WorkgroupSize(Expr(Source{Source::Location{12, 34}}, 2_i))});
@@ -553,11 +565,11 @@
     // @fragment
     // fn fragShader(arg: MyInputs) -> @location(0) f32 { return 1.0; }
 
-    auto* s = Structure("MyInputs",
-                        {Member("position", ty.vec4<f32>(), {Builtin(ast::Builtin::kPosition)}),
-                         Member("front_facing", ty.bool_(), {Builtin(ast::Builtin::kFrontFacing)}),
-                         Member("sample_index", ty.u32(), {Builtin(ast::Builtin::kSampleIndex)}),
-                         Member("sample_mask", ty.u32(), {Builtin(ast::Builtin::kSampleMask)})});
+    auto* s = Structure(
+        "MyInputs", {Member("position", ty.vec4<f32>(), {Builtin(ast::BuiltinValue::kPosition)}),
+                     Member("front_facing", ty.bool_(), {Builtin(ast::BuiltinValue::kFrontFacing)}),
+                     Member("sample_index", ty.u32(), {Builtin(ast::BuiltinValue::kSampleIndex)}),
+                     Member("sample_mask", ty.u32(), {Builtin(ast::BuiltinValue::kSampleMask)})});
     Func("fragShader", {Param("arg", ty.Of(s))}, ty.f32(), {Return(1_f)},
          {Stage(ast::PipelineStage::kFragment)}, {Location(0)});
     EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -570,7 +582,7 @@
     // ) -> @location(0) f32 { return 1.0; }
 
     auto* is_front =
-        Param("is_front", ty.i32(), {Builtin(Source{{12, 34}}, ast::Builtin::kFrontFacing)});
+        Param("is_front", ty.i32(), {Builtin(Source{{12, 34}}, ast::BuiltinValue::kFrontFacing)});
     Func("fs_main", {is_front}, ty.f32(), {Return(1_f)}, {Stage(ast::PipelineStage::kFragment)},
          {Location(0)});
 
@@ -587,7 +599,7 @@
 
     auto* s = Structure(
         "MyInputs",
-        {Member("pos", ty.f32(), {Builtin(Source{{12, 34}}, ast::Builtin::kFrontFacing)})});
+        {Member("pos", ty.f32(), {Builtin(Source{{12, 34}}, ast::BuiltinValue::kFrontFacing)})});
     Func("fragShader", {Param("is_front", ty.Of(s))}, ty.f32(), {Return(1_f)},
          {Stage(ast::PipelineStage::kFragment)}, {Location(0)});
 
diff --git a/src/tint/resolver/entry_point_validation_test.cc b/src/tint/resolver/entry_point_validation_test.cc
index b0495c2..8df0425 100644
--- a/src/tint/resolver/entry_point_validation_test.cc
+++ b/src/tint/resolver/entry_point_validation_test.cc
@@ -59,7 +59,7 @@
     // @vertex
     // fn main() -> @builtin(position) vec4<f32> { return vec4<f32>(); }
     Func(Source{{12, 34}}, "main", {}, ty.vec4<f32>(), {Return(Construct(ty.vec4<f32>()))},
-         {Stage(ast::PipelineStage::kVertex)}, {Builtin(ast::Builtin::kPosition)});
+         {Stage(ast::PipelineStage::kVertex)}, {Builtin(ast::BuiltinValue::kPosition)});
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
@@ -83,7 +83,7 @@
     // }
     Func(Source{{12, 34}}, "main", {}, ty.vec4<f32>(), {Return(Construct(ty.vec4<f32>()))},
          {Stage(ast::PipelineStage::kVertex)},
-         {Location(Source{{13, 43}}, 0), Builtin(Source{{14, 52}}, ast::Builtin::kPosition)});
+         {Location(Source{{13, 43}}, 0), Builtin(Source{{14, 52}}, ast::BuiltinValue::kPosition)});
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), R"(14:52 error: multiple entry point IO attributes
@@ -101,7 +101,7 @@
     // }
     auto* output =
         Structure("Output", {Member("a", ty.f32(), {Location(0)}),
-                             Member("b", ty.f32(), {Builtin(ast::Builtin::kFragDepth)})});
+                             Member("b", ty.f32(), {Builtin(ast::BuiltinValue::kFragDepth)})});
     Func(Source{{12, 34}}, "main", {}, ty.Of(output), {Return(Construct(ty.Of(output)))},
          {Stage(ast::PipelineStage::kFragment)});
 
@@ -119,7 +119,7 @@
     auto* output =
         Structure("Output", {Member("a", ty.f32(),
                                     {Location(Source{{13, 43}}, 0),
-                                     Builtin(Source{{14, 52}}, ast::Builtin::kFragDepth)})});
+                                     Builtin(Source{{14, 52}}, ast::BuiltinValue::kFragDepth)})});
     Func(Source{{12, 34}}, "main", {}, ty.Of(output), {Return(Construct(ty.Of(output)))},
          {Stage(ast::PipelineStage::kFragment)});
 
@@ -159,8 +159,8 @@
     //   return Output();
     // }
     auto* output =
-        Structure("Output", {Member("a", ty.f32(), {Builtin(ast::Builtin::kFragDepth)}),
-                             Member("b", ty.f32(), {Builtin(ast::Builtin::kFragDepth)})});
+        Structure("Output", {Member("a", ty.f32(), {Builtin(ast::BuiltinValue::kFragDepth)}),
+                             Member("b", ty.f32(), {Builtin(ast::BuiltinValue::kFragDepth)})});
     Func(Source{{12, 34}}, "main", {}, ty.Of(output), {Return(Construct(ty.Of(output)))},
          {Stage(ast::PipelineStage::kFragment)});
 
@@ -193,9 +193,9 @@
 TEST_F(ResolverEntryPointValidationTest, ParameterAttribute_Multiple) {
     // @fragment
     // fn main(@location(0) @builtin(sample_index) param : u32) {}
-    auto* param = Param(
-        "param", ty.u32(),
-        {Location(Source{{13, 43}}, 0), Builtin(Source{{14, 52}}, ast::Builtin::kSampleIndex)});
+    auto* param = Param("param", ty.u32(),
+                        {Location(Source{{13, 43}}, 0),
+                         Builtin(Source{{14, 52}}, ast::BuiltinValue::kSampleIndex)});
     Func(Source{{12, 34}}, "main", {param}, ty.void_(), {}, {Stage(ast::PipelineStage::kFragment)});
 
     EXPECT_FALSE(r()->Resolve());
@@ -212,7 +212,7 @@
     // fn main(param : Input) {}
     auto* input =
         Structure("Input", {Member("a", ty.f32(), {Location(0)}),
-                            Member("b", ty.u32(), {Builtin(ast::Builtin::kSampleIndex)})});
+                            Member("b", ty.u32(), {Builtin(ast::BuiltinValue::kSampleIndex)})});
     auto* param = Param("param", ty.Of(input));
     Func(Source{{12, 34}}, "main", {param}, ty.void_(), {}, {Stage(ast::PipelineStage::kFragment)});
 
@@ -228,7 +228,7 @@
     auto* input =
         Structure("Input", {Member("a", ty.u32(),
                                    {Location(Source{{13, 43}}, 0),
-                                    Builtin(Source{{14, 52}}, ast::Builtin::kSampleIndex)})});
+                                    Builtin(Source{{14, 52}}, ast::BuiltinValue::kSampleIndex)})});
     auto* param = Param("param", ty.Of(input));
     Func(Source{{12, 34}}, "main", {param}, ty.void_(), {}, {Stage(ast::PipelineStage::kFragment)});
 
@@ -259,8 +259,8 @@
     // @fragment
     // fn main(@builtin(sample_index) param_a : u32,
     //         @builtin(sample_index) param_b : u32) {}
-    auto* param_a = Param("param_a", ty.u32(), {Builtin(ast::Builtin::kSampleIndex)});
-    auto* param_b = Param("param_b", ty.u32(), {Builtin(ast::Builtin::kSampleIndex)});
+    auto* param_a = Param("param_a", ty.u32(), {Builtin(ast::BuiltinValue::kSampleIndex)});
+    auto* param_b = Param("param_b", ty.u32(), {Builtin(ast::BuiltinValue::kSampleIndex)});
     Func(Source{{12, 34}}, "main", {param_a, param_b}, ty.void_(), {},
          {Stage(ast::PipelineStage::kFragment)});
 
@@ -280,9 +280,9 @@
     // @fragment
     // fn main(param_a : InputA, param_b : InputB) {}
     auto* input_a =
-        Structure("InputA", {Member("a", ty.u32(), {Builtin(ast::Builtin::kSampleIndex)})});
+        Structure("InputA", {Member("a", ty.u32(), {Builtin(ast::BuiltinValue::kSampleIndex)})});
     auto* input_b =
-        Structure("InputB", {Member("a", ty.u32(), {Builtin(ast::Builtin::kSampleIndex)})});
+        Structure("InputB", {Member("a", ty.u32(), {Builtin(ast::BuiltinValue::kSampleIndex)})});
     auto* param_a = Param("param_a", ty.Of(input_a));
     auto* param_b = Param("param_b", ty.Of(input_b));
     Func(Source{{12, 34}}, "main", {param_a, param_b}, ty.void_(), {},
@@ -601,7 +601,7 @@
     // }
     auto* output =
         Structure("Output", {Member("a", ty.f32(), {Location(0)}),
-                             Member("b", ty.f32(), {Builtin(ast::Builtin::kFragDepth)})});
+                             Member("b", ty.f32(), {Builtin(ast::BuiltinValue::kFragDepth)})});
     Func(Source{{12, 34}}, "main", {}, ty.Of(output), {Return(Construct(ty.Of(output)))},
          {Stage(ast::PipelineStage::kFragment)});
 
diff --git a/src/tint/resolver/struct_pipeline_stage_use_test.cc b/src/tint/resolver/struct_pipeline_stage_use_test.cc
index 1ca80df..58c7cae 100644
--- a/src/tint/resolver/struct_pipeline_stage_use_test.cc
+++ b/src/tint/resolver/struct_pipeline_stage_use_test.cc
@@ -66,7 +66,7 @@
     auto* s = Structure("S", {Member("a", ty.f32(), {Location(0)})});
 
     Func("main", {Param("param", ty.Of(s))}, ty.vec4<f32>(), {Return(Construct(ty.vec4<f32>()))},
-         {Stage(ast::PipelineStage::kVertex)}, {Builtin(ast::Builtin::kPosition)});
+         {Stage(ast::PipelineStage::kVertex)}, {Builtin(ast::BuiltinValue::kPosition)});
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -77,7 +77,8 @@
 }
 
 TEST_F(ResolverPipelineStageUseTest, StructUsedAsVertexShaderReturnType) {
-    auto* s = Structure("S", {Member("a", ty.vec4<f32>(), {Builtin(ast::Builtin::kPosition)})});
+    auto* s =
+        Structure("S", {Member("a", ty.vec4<f32>(), {Builtin(ast::BuiltinValue::kPosition)})});
 
     Func("main", {}, ty.Of(s), {Return(Construct(ty.Of(s)))}, {Stage(ast::PipelineStage::kVertex)});
 
@@ -118,8 +119,8 @@
 }
 
 TEST_F(ResolverPipelineStageUseTest, StructUsedAsComputeShaderParam) {
-    auto* s =
-        Structure("S", {Member("a", ty.u32(), {Builtin(ast::Builtin::kLocalInvocationIndex)})});
+    auto* s = Structure(
+        "S", {Member("a", ty.u32(), {Builtin(ast::BuiltinValue::kLocalInvocationIndex)})});
 
     Func("main", {Param("param", ty.Of(s))}, ty.void_(), {},
          {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1_i)});
@@ -133,7 +134,8 @@
 }
 
 TEST_F(ResolverPipelineStageUseTest, StructUsedMultipleStages) {
-    auto* s = Structure("S", {Member("a", ty.vec4<f32>(), {Builtin(ast::Builtin::kPosition)})});
+    auto* s =
+        Structure("S", {Member("a", ty.vec4<f32>(), {Builtin(ast::BuiltinValue::kPosition)})});
 
     Func("vert_main", {}, ty.Of(s), {Return(Construct(ty.Of(s)))},
          {Stage(ast::PipelineStage::kVertex)});
diff --git a/src/tint/resolver/uniformity.cc b/src/tint/resolver/uniformity.cc
index 57bf6b0..905fc0e 100644
--- a/src/tint/resolver/uniformity.cc
+++ b/src/tint/resolver/uniformity.cc
@@ -973,8 +973,8 @@
         auto has_nonuniform_entry_point_attribute = [](auto* obj) {
             // Only the num_workgroups and workgroup_id builtins are uniform.
             if (auto* builtin = ast::GetAttribute<ast::BuiltinAttribute>(obj->attributes)) {
-                if (builtin->builtin == ast::Builtin::kNumWorkgroups ||
-                    builtin->builtin == ast::Builtin::kWorkgroupId) {
+                if (builtin->builtin == ast::BuiltinValue::kNumWorkgroups ||
+                    builtin->builtin == ast::BuiltinValue::kWorkgroupId) {
                     return false;
                 }
             }
diff --git a/src/tint/resolver/validation_test.cc b/src/tint/resolver/validation_test.cc
index 24f420b..45c6d87 100644
--- a/src/tint/resolver/validation_test.cc
+++ b/src/tint/resolver/validation_test.cc
@@ -74,7 +74,7 @@
              Stage(ast::PipelineStage::kVertex),
          },
          {
-             Builtin(ast::Builtin::kPosition),
+             Builtin(ast::BuiltinValue::kPosition),
          });
 
     EXPECT_FALSE(r()->Resolve());
diff --git a/src/tint/resolver/validator.cc b/src/tint/resolver/validator.cc
index c30e2b3..b8a6247 100644
--- a/src/tint/resolver/validator.cc
+++ b/src/tint/resolver/validator.cc
@@ -886,7 +886,7 @@
     bool is_stage_mismatch = false;
     bool is_output = !is_input;
     switch (attr->builtin) {
-        case ast::Builtin::kPosition:
+        case ast::BuiltinValue::kPosition:
             if (stage != ast::PipelineStage::kNone &&
                 !((is_input && stage == ast::PipelineStage::kFragment) ||
                   (is_output && stage == ast::PipelineStage::kVertex))) {
@@ -898,10 +898,10 @@
                 return false;
             }
             break;
-        case ast::Builtin::kGlobalInvocationId:
-        case ast::Builtin::kLocalInvocationId:
-        case ast::Builtin::kNumWorkgroups:
-        case ast::Builtin::kWorkgroupId:
+        case ast::BuiltinValue::kGlobalInvocationId:
+        case ast::BuiltinValue::kLocalInvocationId:
+        case ast::BuiltinValue::kNumWorkgroups:
+        case ast::BuiltinValue::kWorkgroupId:
             if (stage != ast::PipelineStage::kNone &&
                 !(stage == ast::PipelineStage::kCompute && is_input)) {
                 is_stage_mismatch = true;
@@ -912,7 +912,7 @@
                 return false;
             }
             break;
-        case ast::Builtin::kFragDepth:
+        case ast::BuiltinValue::kFragDepth:
             if (stage != ast::PipelineStage::kNone &&
                 !(stage == ast::PipelineStage::kFragment && !is_input)) {
                 is_stage_mismatch = true;
@@ -922,7 +922,7 @@
                 return false;
             }
             break;
-        case ast::Builtin::kFrontFacing:
+        case ast::BuiltinValue::kFrontFacing:
             if (stage != ast::PipelineStage::kNone &&
                 !(stage == ast::PipelineStage::kFragment && is_input)) {
                 is_stage_mismatch = true;
@@ -932,7 +932,7 @@
                 return false;
             }
             break;
-        case ast::Builtin::kLocalInvocationIndex:
+        case ast::BuiltinValue::kLocalInvocationIndex:
             if (stage != ast::PipelineStage::kNone &&
                 !(stage == ast::PipelineStage::kCompute && is_input)) {
                 is_stage_mismatch = true;
@@ -942,8 +942,8 @@
                 return false;
             }
             break;
-        case ast::Builtin::kVertexIndex:
-        case ast::Builtin::kInstanceIndex:
+        case ast::BuiltinValue::kVertexIndex:
+        case ast::BuiltinValue::kInstanceIndex:
             if (stage != ast::PipelineStage::kNone &&
                 !(stage == ast::PipelineStage::kVertex && is_input)) {
                 is_stage_mismatch = true;
@@ -953,7 +953,7 @@
                 return false;
             }
             break;
-        case ast::Builtin::kSampleMask:
+        case ast::BuiltinValue::kSampleMask:
             if (stage != ast::PipelineStage::kNone && !(stage == ast::PipelineStage::kFragment)) {
                 is_stage_mismatch = true;
             }
@@ -962,7 +962,7 @@
                 return false;
             }
             break;
-        case ast::Builtin::kSampleIndex:
+        case ast::BuiltinValue::kSampleIndex:
             if (stage != ast::PipelineStage::kNone &&
                 !(stage == ast::PipelineStage::kFragment && is_input)) {
                 is_stage_mismatch = true;
@@ -1102,7 +1102,7 @@
     // already been seen, in order to catch conflicts.
     // TODO(jrprice): This state could be stored in sem::Function instead, and
     // then passed to sem::Function since it would be useful there too.
-    std::unordered_set<ast::Builtin> builtins;
+    std::unordered_set<ast::BuiltinValue> builtins;
     std::unordered_set<uint32_t> locations;
     enum class ParamOrRetType {
         kParameter,
@@ -1237,7 +1237,7 @@
                 bool has_position = false;
                 if (pipeline_io_attribute) {
                     if (auto* builtin = pipeline_io_attribute->As<ast::BuiltinAttribute>()) {
-                        has_position = (builtin->builtin == ast::Builtin::kPosition);
+                        has_position = (builtin->builtin == ast::BuiltinValue::kPosition);
                     }
                 }
                 if (!has_position) {
@@ -1298,13 +1298,13 @@
     }
 
     if (decl->PipelineStage() == ast::PipelineStage::kVertex &&
-        builtins.count(ast::Builtin::kPosition) == 0) {
+        builtins.count(ast::BuiltinValue::kPosition) == 0) {
         // Check module-scope variables, as the SPIR-V sanitizer generates these.
         bool found = false;
         for (auto* global : func->TransitivelyReferencedGlobals()) {
             if (auto* builtin =
                     ast::GetAttribute<ast::BuiltinAttribute>(global->Declaration()->attributes)) {
-                if (builtin->builtin == ast::Builtin::kPosition) {
+                if (builtin->builtin == ast::BuiltinValue::kPosition) {
                     found = true;
                     break;
                 }
@@ -2111,7 +2111,7 @@
                                       /* is_input */ false)) {
                     return false;
                 }
-                if (builtin->builtin == ast::Builtin::kPosition) {
+                if (builtin->builtin == ast::BuiltinValue::kPosition) {
                     has_position = true;
                 }
             } else if (auto* interpolate = attr->As<ast::InterpolateAttribute>()) {
diff --git a/src/tint/transform/canonicalize_entry_point_io.cc b/src/tint/transform/canonicalize_entry_point_io.cc
index 51977d9..acec84f 100644
--- a/src/tint/transform/canonicalize_entry_point_io.cc
+++ b/src/tint/transform/canonicalize_entry_point_io.cc
@@ -71,7 +71,7 @@
 // Returns true if `attrs` contains a `sample_mask` builtin.
 bool HasSampleMask(const ast::AttributeList& attrs) {
     auto* builtin = ast::GetAttribute<ast::BuiltinAttribute>(attrs);
-    return builtin && builtin->builtin == ast::Builtin::kSampleMask;
+    return builtin && builtin->builtin == ast::BuiltinValue::kSampleMask;
 }
 
 }  // namespace
@@ -191,7 +191,7 @@
             if (builtin) {
                 if (cfg.shader_style == ShaderStyle::kGlsl) {
                     value = FromGLSLBuiltin(builtin->builtin, value, ast_type);
-                } else if (builtin->builtin == ast::Builtin::kSampleMask) {
+                } else if (builtin->builtin == ast::BuiltinValue::kSampleMask) {
                     // Vulkan requires the type of a SampleMask builtin to be an array.
                     // Declare it as array<u32, 1> and then load the first element.
                     ast_type = ctx.dst->ty.array(ast_type, 1_u);
@@ -366,7 +366,7 @@
         // No existing sample mask builtin was found, so create a new output value
         // using the fixed sample mask.
         AddOutput("fixed_sample_mask", ctx.dst->create<sem::U32>(),
-                  {ctx.dst->Builtin(ast::Builtin::kSampleMask)},
+                  {ctx.dst->Builtin(ast::BuiltinValue::kSampleMask)},
                   ctx.dst->Expr(u32(cfg.fixed_sample_mask)));
     }
 
@@ -374,7 +374,7 @@
     void AddVertexPointSize() {
         // Create a new output value and assign it a literal 1.0 value.
         AddOutput("vertex_point_size", ctx.dst->create<sem::F32>(),
-                  {ctx.dst->Builtin(ast::Builtin::kPointSize)}, ctx.dst->Expr(1_f));
+                  {ctx.dst->Builtin(ast::BuiltinValue::kPointSize)}, ctx.dst->Expr(1_f));
     }
 
     /// Create an expression for gl_Position.[component]
@@ -606,11 +606,11 @@
     /// @param stage the current pipeline stage
     /// @param storage_class the storage class (input or output)
     /// @returns the gl_ string corresponding to that builtin
-    const char* GLSLBuiltinToString(ast::Builtin builtin,
+    const char* GLSLBuiltinToString(ast::BuiltinValue builtin,
                                     ast::PipelineStage stage,
                                     ast::StorageClass storage_class) {
         switch (builtin) {
-            case ast::Builtin::kPosition:
+            case ast::BuiltinValue::kPosition:
                 switch (stage) {
                     case ast::PipelineStage::kVertex:
                         return "gl_Position";
@@ -619,27 +619,27 @@
                     default:
                         return "";
                 }
-            case ast::Builtin::kVertexIndex:
+            case ast::BuiltinValue::kVertexIndex:
                 return "gl_VertexID";
-            case ast::Builtin::kInstanceIndex:
+            case ast::BuiltinValue::kInstanceIndex:
                 return "gl_InstanceID";
-            case ast::Builtin::kFrontFacing:
+            case ast::BuiltinValue::kFrontFacing:
                 return "gl_FrontFacing";
-            case ast::Builtin::kFragDepth:
+            case ast::BuiltinValue::kFragDepth:
                 return "gl_FragDepth";
-            case ast::Builtin::kLocalInvocationId:
+            case ast::BuiltinValue::kLocalInvocationId:
                 return "gl_LocalInvocationID";
-            case ast::Builtin::kLocalInvocationIndex:
+            case ast::BuiltinValue::kLocalInvocationIndex:
                 return "gl_LocalInvocationIndex";
-            case ast::Builtin::kGlobalInvocationId:
+            case ast::BuiltinValue::kGlobalInvocationId:
                 return "gl_GlobalInvocationID";
-            case ast::Builtin::kNumWorkgroups:
+            case ast::BuiltinValue::kNumWorkgroups:
                 return "gl_NumWorkGroups";
-            case ast::Builtin::kWorkgroupId:
+            case ast::BuiltinValue::kWorkgroupId:
                 return "gl_WorkGroupID";
-            case ast::Builtin::kSampleIndex:
+            case ast::BuiltinValue::kSampleIndex:
                 return "gl_SampleID";
-            case ast::Builtin::kSampleMask:
+            case ast::BuiltinValue::kSampleMask:
                 if (storage_class == ast::StorageClass::kIn) {
                     return "gl_SampleMaskIn";
                 } else {
@@ -656,18 +656,18 @@
     /// @param ast_type (inout) the incoming WGSL and outgoing GLSL types
     /// @returns an expression representing the GLSL builtin converted to what
     /// WGSL expects
-    const ast::Expression* FromGLSLBuiltin(ast::Builtin builtin,
+    const ast::Expression* FromGLSLBuiltin(ast::BuiltinValue builtin,
                                            const ast::Expression* value,
                                            const ast::Type*& ast_type) {
         switch (builtin) {
-            case ast::Builtin::kVertexIndex:
-            case ast::Builtin::kInstanceIndex:
-            case ast::Builtin::kSampleIndex:
+            case ast::BuiltinValue::kVertexIndex:
+            case ast::BuiltinValue::kInstanceIndex:
+            case ast::BuiltinValue::kSampleIndex:
                 // GLSL uses i32 for these, so bitcast to u32.
                 value = ctx.dst->Bitcast(ast_type, value);
                 ast_type = ctx.dst->ty.i32();
                 break;
-            case ast::Builtin::kSampleMask:
+            case ast::BuiltinValue::kSampleMask:
                 // gl_SampleMask is an array of i32. Retrieve the first element and
                 // bitcast it to u32.
                 value = ctx.dst->IndexAccessor(value, 0_i);
@@ -686,14 +686,14 @@
     /// @param value the value to convert
     /// @param type (out) the type to which the value was converted
     /// @returns the converted value which can be assigned to the GLSL builtin
-    const ast::Expression* ToGLSLBuiltin(ast::Builtin builtin,
+    const ast::Expression* ToGLSLBuiltin(ast::BuiltinValue builtin,
                                          const ast::Expression* value,
                                          const sem::Type*& type) {
         switch (builtin) {
-            case ast::Builtin::kVertexIndex:
-            case ast::Builtin::kInstanceIndex:
-            case ast::Builtin::kSampleIndex:
-            case ast::Builtin::kSampleMask:
+            case ast::BuiltinValue::kVertexIndex:
+            case ast::BuiltinValue::kInstanceIndex:
+            case ast::BuiltinValue::kSampleIndex:
+            case ast::BuiltinValue::kSampleMask:
                 type = ctx.dst->create<sem::I32>();
                 value = ctx.dst->Bitcast(CreateASTTypeFor(ctx, type), value);
                 break;
diff --git a/src/tint/transform/first_index_offset.cc b/src/tint/transform/first_index_offset.cc
index 01f9771..6af162d 100644
--- a/src/tint/transform/first_index_offset.cc
+++ b/src/tint/transform/first_index_offset.cc
@@ -79,13 +79,13 @@
         if (auto* var = node->As<ast::Variable>()) {
             for (auto* attr : var->attributes) {
                 if (auto* builtin_attr = attr->As<ast::BuiltinAttribute>()) {
-                    ast::Builtin builtin = builtin_attr->builtin;
-                    if (builtin == ast::Builtin::kVertexIndex) {
+                    ast::BuiltinValue builtin = builtin_attr->builtin;
+                    if (builtin == ast::BuiltinValue::kVertexIndex) {
                         auto* sem_var = ctx.src->Sem().Get(var);
                         builtin_vars.emplace(sem_var, kFirstVertexName);
                         has_vertex_or_instance_index = true;
                     }
-                    if (builtin == ast::Builtin::kInstanceIndex) {
+                    if (builtin == ast::BuiltinValue::kInstanceIndex) {
                         auto* sem_var = ctx.src->Sem().Get(var);
                         builtin_vars.emplace(sem_var, kFirstInstanceName);
                         has_vertex_or_instance_index = true;
@@ -96,13 +96,13 @@
         if (auto* member = node->As<ast::StructMember>()) {
             for (auto* attr : member->attributes) {
                 if (auto* builtin_attr = attr->As<ast::BuiltinAttribute>()) {
-                    ast::Builtin builtin = builtin_attr->builtin;
-                    if (builtin == ast::Builtin::kVertexIndex) {
+                    ast::BuiltinValue builtin = builtin_attr->builtin;
+                    if (builtin == ast::BuiltinValue::kVertexIndex) {
                         auto* sem_mem = ctx.src->Sem().Get(member);
                         builtin_members.emplace(sem_mem, kFirstVertexName);
                         has_vertex_or_instance_index = true;
                     }
-                    if (builtin == ast::Builtin::kInstanceIndex) {
+                    if (builtin == ast::BuiltinValue::kInstanceIndex) {
                         auto* sem_mem = ctx.src->Sem().Get(member);
                         builtin_members.emplace(sem_mem, kFirstInstanceName);
                         has_vertex_or_instance_index = true;
diff --git a/src/tint/transform/num_workgroups_from_uniform.cc b/src/tint/transform/num_workgroups_from_uniform.cc
index 6b93e73..86310a91 100644
--- a/src/tint/transform/num_workgroups_from_uniform.cc
+++ b/src/tint/transform/num_workgroups_from_uniform.cc
@@ -52,7 +52,7 @@
 bool NumWorkgroupsFromUniform::ShouldRun(const Program* program, const DataMap&) const {
     for (auto* node : program->ASTNodes().Objects()) {
         if (auto* attr = node->As<ast::BuiltinAttribute>()) {
-            if (attr->builtin == ast::Builtin::kNumWorkgroups) {
+            if (attr->builtin == ast::BuiltinValue::kNumWorkgroups) {
                 return true;
             }
         }
@@ -89,7 +89,7 @@
             for (auto* member : str->Members()) {
                 auto* builtin =
                     ast::GetAttribute<ast::BuiltinAttribute>(member->Declaration()->attributes);
-                if (!builtin || builtin->builtin != ast::Builtin::kNumWorkgroups) {
+                if (!builtin || builtin->builtin != ast::BuiltinValue::kNumWorkgroups) {
                     continue;
                 }
 
diff --git a/src/tint/transform/vertex_pulling.cc b/src/tint/transform/vertex_pulling.cc
index 50b8f28..43531eb 100644
--- a/src/tint/transform/vertex_pulling.cc
+++ b/src/tint/transform/vertex_pulling.cc
@@ -709,11 +709,11 @@
             location_info[location->value] = info;
         } else if (auto* builtin = ast::GetAttribute<ast::BuiltinAttribute>(param->attributes)) {
             // Check for existing vertex_index and instance_index builtins.
-            if (builtin->builtin == ast::Builtin::kVertexIndex) {
+            if (builtin->builtin == ast::BuiltinValue::kVertexIndex) {
                 vertex_index_expr = [this, param]() {
                     return ctx.dst->Expr(ctx.Clone(param->symbol));
                 };
-            } else if (builtin->builtin == ast::Builtin::kInstanceIndex) {
+            } else if (builtin->builtin == ast::BuiltinValue::kInstanceIndex) {
                 instance_index_expr = [this, param]() {
                     return ctx.dst->Expr(ctx.Clone(param->symbol));
                 };
@@ -756,9 +756,9 @@
             } else if (auto* builtin =
                            ast::GetAttribute<ast::BuiltinAttribute>(member->attributes)) {
                 // Check for existing vertex_index and instance_index builtins.
-                if (builtin->builtin == ast::Builtin::kVertexIndex) {
+                if (builtin->builtin == ast::BuiltinValue::kVertexIndex) {
                     vertex_index_expr = member_expr;
-                } else if (builtin->builtin == ast::Builtin::kInstanceIndex) {
+                } else if (builtin->builtin == ast::BuiltinValue::kInstanceIndex) {
                     instance_index_expr = member_expr;
                 }
                 members_to_clone.push_back(member);
@@ -825,8 +825,9 @@
             for (const VertexBufferLayoutDescriptor& layout : cfg.vertex_state) {
                 if (layout.step_mode == VertexStepMode::kVertex) {
                     auto name = ctx.dst->Symbols().New("tint_pulling_vertex_index");
-                    new_function_parameters.push_back(ctx.dst->Param(
-                        name, ctx.dst->ty.u32(), {ctx.dst->Builtin(ast::Builtin::kVertexIndex)}));
+                    new_function_parameters.push_back(
+                        ctx.dst->Param(name, ctx.dst->ty.u32(),
+                                       {ctx.dst->Builtin(ast::BuiltinValue::kVertexIndex)}));
                     vertex_index_expr = [this, name]() { return ctx.dst->Expr(name); };
                     break;
                 }
@@ -836,8 +837,9 @@
             for (const VertexBufferLayoutDescriptor& layout : cfg.vertex_state) {
                 if (layout.step_mode == VertexStepMode::kInstance) {
                     auto name = ctx.dst->Symbols().New("tint_pulling_instance_index");
-                    new_function_parameters.push_back(ctx.dst->Param(
-                        name, ctx.dst->ty.u32(), {ctx.dst->Builtin(ast::Builtin::kInstanceIndex)}));
+                    new_function_parameters.push_back(
+                        ctx.dst->Param(name, ctx.dst->ty.u32(),
+                                       {ctx.dst->Builtin(ast::BuiltinValue::kInstanceIndex)}));
                     instance_index_expr = [this, name]() { return ctx.dst->Expr(name); };
                     break;
                 }
diff --git a/src/tint/transform/zero_init_workgroup_memory.cc b/src/tint/transform/zero_init_workgroup_memory.cc
index 96395f1..890ecef 100644
--- a/src/tint/transform/zero_init_workgroup_memory.cc
+++ b/src/tint/transform/zero_init_workgroup_memory.cc
@@ -137,7 +137,7 @@
         std::function<const ast::Expression*()> local_index;
         for (auto* param : fn->params) {
             if (auto* builtin = ast::GetAttribute<ast::BuiltinAttribute>(param->attributes)) {
-                if (builtin->builtin == ast::Builtin::kLocalInvocationIndex) {
+                if (builtin->builtin == ast::BuiltinValue::kLocalInvocationIndex) {
                     local_index = [=] { return b.Expr(ctx.Clone(param->symbol)); };
                     break;
                 }
@@ -147,7 +147,7 @@
                 for (auto* member : str->Members()) {
                     if (auto* builtin = ast::GetAttribute<ast::BuiltinAttribute>(
                             member->Declaration()->attributes)) {
-                        if (builtin->builtin == ast::Builtin::kLocalInvocationIndex) {
+                        if (builtin->builtin == ast::BuiltinValue::kLocalInvocationIndex) {
                             local_index = [=] {
                                 auto* param_expr = b.Expr(ctx.Clone(param->symbol));
                                 auto member_name = ctx.Clone(member->Declaration()->symbol);
@@ -162,7 +162,7 @@
         if (!local_index) {
             // No existing local index parameter. Append one to the entry point.
             auto* param = b.Param(b.Symbols().New("local_invocation_index"), b.ty.u32(),
-                                  {b.Builtin(ast::Builtin::kLocalInvocationIndex)});
+                                  {b.Builtin(ast::BuiltinValue::kLocalInvocationIndex)});
             ctx.InsertBack(fn->params, param);
             local_index = [=] { return b.Expr(param->symbol); };
         }
diff --git a/src/tint/writer/glsl/generator_impl.cc b/src/tint/writer/glsl/generator_impl.cc
index 5473eaf..0dd1f92 100644
--- a/src/tint/writer/glsl/generator_impl.cc
+++ b/src/tint/writer/glsl/generator_impl.cc
@@ -85,10 +85,10 @@
            op == tint::ast::BinaryOp::kGreaterThanEqual;
 }
 
-bool RequiresOESSampleVariables(tint::ast::Builtin builtin) {
+bool RequiresOESSampleVariables(tint::ast::BuiltinValue builtin) {
     switch (builtin) {
-        case tint::ast::Builtin::kSampleIndex:
-        case tint::ast::Builtin::kSampleMask:
+        case tint::ast::BuiltinValue::kSampleIndex:
+        case tint::ast::BuiltinValue::kSampleMask:
             return true;
         default:
             return false;
diff --git a/src/tint/writer/glsl/generator_impl.h b/src/tint/writer/glsl/generator_impl.h
index 811dd91..86be974 100644
--- a/src/tint/writer/glsl/generator_impl.h
+++ b/src/tint/writer/glsl/generator_impl.h
@@ -465,11 +465,11 @@
     /// @param builtin the builtin to convert
     /// @param stage pipeline stage in which this builtin is used
     /// @returns the string name of the builtin or blank on error
-    const char* builtin_to_string(ast::Builtin builtin, ast::PipelineStage stage);
+    const char* builtin_to_string(ast::BuiltinValue builtin, ast::PipelineStage stage);
     /// Converts a builtin to a sem::Type appropriate for GLSL.
     /// @param builtin the builtin to convert
     /// @returns the appropriate semantic type or null on error.
-    sem::Type* builtin_type(ast::Builtin builtin);
+    sem::Type* builtin_type(ast::BuiltinValue builtin);
 
   private:
     enum class VarType { kIn, kOut };
diff --git a/src/tint/writer/glsl/generator_impl_function_test.cc b/src/tint/writer/glsl/generator_impl_function_test.cc
index 450041b..d1f7a5e 100644
--- a/src/tint/writer/glsl/generator_impl_function_test.cc
+++ b/src/tint/writer/glsl/generator_impl_function_test.cc
@@ -165,7 +165,7 @@
     // fn frag_main(@position(0) coord : vec4<f32>) -> @frag_depth f32 {
     //   return coord.x;
     // }
-    auto* coord_in = Param("coord", ty.vec4<f32>(), {Builtin(ast::Builtin::kPosition)});
+    auto* coord_in = Param("coord", ty.vec4<f32>(), {Builtin(ast::BuiltinValue::kPosition)});
     Func("frag_main",
          {
              coord_in,
@@ -178,7 +178,7 @@
              Stage(ast::PipelineStage::kFragment),
          },
          {
-             Builtin(ast::Builtin::kFragDepth),
+             Builtin(ast::BuiltinValue::kFragDepth),
          });
 
     GeneratorImpl& gen = SanitizeAndBuild();
@@ -215,7 +215,7 @@
     // }
     auto* interface_struct = Structure(
         "Interface", {
-                         Member("pos", ty.vec4<f32>(), {Builtin(ast::Builtin::kPosition)}),
+                         Member("pos", ty.vec4<f32>(), {Builtin(ast::BuiltinValue::kPosition)}),
                          Member("col1", ty.f32(), {Location(1)}),
                          Member("col2", ty.f32(), {Location(2)}),
                      });
@@ -295,7 +295,7 @@
   // }
   auto* vertex_output_struct = Structure(
       "VertexOutput",
-      {Member("pos", ty.vec4<f32>(), {Builtin(ast::Builtin::kPosition)})});
+      {Member("pos", ty.vec4<f32>(), {Builtin(ast::BuiltinValue::kPosition)})});
 
   Func("foo", {Param("x", ty.f32())}, ty.Of(vertex_output_struct),
        {Return(Construct(ty.Of(vertex_output_struct),
diff --git a/src/tint/writer/glsl/generator_impl_test.cc b/src/tint/writer/glsl/generator_impl_test.cc
index abae4c9..f45d0d0 100644
--- a/src/tint/writer/glsl/generator_impl_test.cc
+++ b/src/tint/writer/glsl/generator_impl_test.cc
@@ -59,7 +59,7 @@
 TEST_F(GlslGeneratorImplTest, GenerateSampleIndexES) {
     GlobalVar("gl_SampleID", ty.i32(),
               ast::AttributeList{
-                  Builtin(ast::Builtin::kSampleIndex),
+                  Builtin(ast::BuiltinValue::kSampleIndex),
                   Disable(ast::DisabledValidation::kIgnoreStorageClass),
               },
               ast::StorageClass::kIn);
@@ -84,7 +84,7 @@
 TEST_F(GlslGeneratorImplTest, GenerateSampleIndexDesktop) {
     GlobalVar("gl_SampleID", ty.i32(),
               ast::AttributeList{
-                  Builtin(ast::Builtin::kSampleIndex),
+                  Builtin(ast::BuiltinValue::kSampleIndex),
                   Disable(ast::DisabledValidation::kIgnoreStorageClass),
               },
               ast::StorageClass::kIn);
diff --git a/src/tint/writer/hlsl/generator_impl.cc b/src/tint/writer/hlsl/generator_impl.cc
index a9fddc3..8aa5d61 100644
--- a/src/tint/writer/hlsl/generator_impl.cc
+++ b/src/tint/writer/hlsl/generator_impl.cc
@@ -2981,29 +2981,29 @@
     return true;
 }
 
-std::string GeneratorImpl::builtin_to_attribute(ast::Builtin builtin) const {
+std::string GeneratorImpl::builtin_to_attribute(ast::BuiltinValue builtin) const {
     switch (builtin) {
-        case ast::Builtin::kPosition:
+        case ast::BuiltinValue::kPosition:
             return "SV_Position";
-        case ast::Builtin::kVertexIndex:
+        case ast::BuiltinValue::kVertexIndex:
             return "SV_VertexID";
-        case ast::Builtin::kInstanceIndex:
+        case ast::BuiltinValue::kInstanceIndex:
             return "SV_InstanceID";
-        case ast::Builtin::kFrontFacing:
+        case ast::BuiltinValue::kFrontFacing:
             return "SV_IsFrontFace";
-        case ast::Builtin::kFragDepth:
+        case ast::BuiltinValue::kFragDepth:
             return "SV_Depth";
-        case ast::Builtin::kLocalInvocationId:
+        case ast::BuiltinValue::kLocalInvocationId:
             return "SV_GroupThreadID";
-        case ast::Builtin::kLocalInvocationIndex:
+        case ast::BuiltinValue::kLocalInvocationIndex:
             return "SV_GroupIndex";
-        case ast::Builtin::kGlobalInvocationId:
+        case ast::BuiltinValue::kGlobalInvocationId:
             return "SV_DispatchThreadID";
-        case ast::Builtin::kWorkgroupId:
+        case ast::BuiltinValue::kWorkgroupId:
             return "SV_GroupID";
-        case ast::Builtin::kSampleIndex:
+        case ast::BuiltinValue::kSampleIndex:
             return "SV_SampleIndex";
-        case ast::Builtin::kSampleMask:
+        case ast::BuiltinValue::kSampleMask:
             return "SV_Coverage";
         default:
             break;
diff --git a/src/tint/writer/hlsl/generator_impl.h b/src/tint/writer/hlsl/generator_impl.h
index 74ce70c..a282451 100644
--- a/src/tint/writer/hlsl/generator_impl.h
+++ b/src/tint/writer/hlsl/generator_impl.h
@@ -489,7 +489,7 @@
     /// Converts a builtin to an attribute name
     /// @param builtin the builtin to convert
     /// @returns the string name of the builtin or blank on error
-    std::string builtin_to_attribute(ast::Builtin builtin) const;
+    std::string builtin_to_attribute(ast::BuiltinValue builtin) const;
 
     /// Converts interpolation attributes to a HLSL modifiers
     /// @param type the interpolation type
diff --git a/src/tint/writer/hlsl/generator_impl_function_test.cc b/src/tint/writer/hlsl/generator_impl_function_test.cc
index 89d74c5..9314fb8 100644
--- a/src/tint/writer/hlsl/generator_impl_function_test.cc
+++ b/src/tint/writer/hlsl/generator_impl_function_test.cc
@@ -156,7 +156,7 @@
     // fn frag_main(@position(0) coord : vec4<f32>) -> @frag_depth f32 {
     //   return coord.x;
     // }
-    auto* coord_in = Param("coord", ty.vec4<f32>(), {Builtin(ast::Builtin::kPosition)});
+    auto* coord_in = Param("coord", ty.vec4<f32>(), {Builtin(ast::BuiltinValue::kPosition)});
     Func("frag_main", {coord_in}, ty.f32(),
          {
              Return(MemberAccessor("coord", "x")),
@@ -165,7 +165,7 @@
              Stage(ast::PipelineStage::kFragment),
          },
          {
-             Builtin(ast::Builtin::kFragDepth),
+             Builtin(ast::BuiltinValue::kFragDepth),
          });
 
     GeneratorImpl& gen = SanitizeAndBuild();
@@ -207,7 +207,7 @@
     // }
     auto* interface_struct = Structure(
         "Interface", {
-                         Member("pos", ty.vec4<f32>(), {Builtin(ast::Builtin::kPosition)}),
+                         Member("pos", ty.vec4<f32>(), {Builtin(ast::BuiltinValue::kPosition)}),
                          Member("col1", ty.f32(), {Location(1)}),
                          Member("col2", ty.f32(), {Location(2)}),
                      });
@@ -287,7 +287,7 @@
     //   return foo(0.25);
     // }
     auto* vertex_output_struct = Structure(
-        "VertexOutput", {Member("pos", ty.vec4<f32>(), {Builtin(ast::Builtin::kPosition)})});
+        "VertexOutput", {Member("pos", ty.vec4<f32>(), {Builtin(ast::BuiltinValue::kPosition)})});
 
     Func("foo", {Param("x", ty.f32())}, ty.Of(vertex_output_struct),
          {Return(Construct(ty.Of(vertex_output_struct),
diff --git a/src/tint/writer/hlsl/generator_impl_test.cc b/src/tint/writer/hlsl/generator_impl_test.cc
index 997f51a..35b9c82 100644
--- a/src/tint/writer/hlsl/generator_impl_test.cc
+++ b/src/tint/writer/hlsl/generator_impl_test.cc
@@ -40,7 +40,7 @@
 }
 
 struct HlslBuiltinData {
-    ast::Builtin builtin;
+    ast::BuiltinValue builtin;
     const char* attribute_name;
 };
 inline std::ostream& operator<<(std::ostream& out, HlslBuiltinData data) {
@@ -57,17 +57,17 @@
 INSTANTIATE_TEST_SUITE_P(
     HlslGeneratorImplTest,
     HlslBuiltinConversionTest,
-    testing::Values(HlslBuiltinData{ast::Builtin::kPosition, "SV_Position"},
-                    HlslBuiltinData{ast::Builtin::kVertexIndex, "SV_VertexID"},
-                    HlslBuiltinData{ast::Builtin::kInstanceIndex, "SV_InstanceID"},
-                    HlslBuiltinData{ast::Builtin::kFrontFacing, "SV_IsFrontFace"},
-                    HlslBuiltinData{ast::Builtin::kFragDepth, "SV_Depth"},
-                    HlslBuiltinData{ast::Builtin::kLocalInvocationId, "SV_GroupThreadID"},
-                    HlslBuiltinData{ast::Builtin::kLocalInvocationIndex, "SV_GroupIndex"},
-                    HlslBuiltinData{ast::Builtin::kGlobalInvocationId, "SV_DispatchThreadID"},
-                    HlslBuiltinData{ast::Builtin::kWorkgroupId, "SV_GroupID"},
-                    HlslBuiltinData{ast::Builtin::kSampleIndex, "SV_SampleIndex"},
-                    HlslBuiltinData{ast::Builtin::kSampleMask, "SV_Coverage"}));
+    testing::Values(HlslBuiltinData{ast::BuiltinValue::kPosition, "SV_Position"},
+                    HlslBuiltinData{ast::BuiltinValue::kVertexIndex, "SV_VertexID"},
+                    HlslBuiltinData{ast::BuiltinValue::kInstanceIndex, "SV_InstanceID"},
+                    HlslBuiltinData{ast::BuiltinValue::kFrontFacing, "SV_IsFrontFace"},
+                    HlslBuiltinData{ast::BuiltinValue::kFragDepth, "SV_Depth"},
+                    HlslBuiltinData{ast::BuiltinValue::kLocalInvocationId, "SV_GroupThreadID"},
+                    HlslBuiltinData{ast::BuiltinValue::kLocalInvocationIndex, "SV_GroupIndex"},
+                    HlslBuiltinData{ast::BuiltinValue::kGlobalInvocationId, "SV_DispatchThreadID"},
+                    HlslBuiltinData{ast::BuiltinValue::kWorkgroupId, "SV_GroupID"},
+                    HlslBuiltinData{ast::BuiltinValue::kSampleIndex, "SV_SampleIndex"},
+                    HlslBuiltinData{ast::BuiltinValue::kSampleMask, "SV_Coverage"}));
 
 }  // namespace
 }  // namespace tint::writer::hlsl
diff --git a/src/tint/writer/msl/generator_impl.cc b/src/tint/writer/msl/generator_impl.cc
index 626a11d..d024686 100644
--- a/src/tint/writer/msl/generator_impl.cc
+++ b/src/tint/writer/msl/generator_impl.cc
@@ -1862,33 +1862,33 @@
     return true;
 }
 
-std::string GeneratorImpl::builtin_to_attribute(ast::Builtin builtin) const {
+std::string GeneratorImpl::builtin_to_attribute(ast::BuiltinValue builtin) const {
     switch (builtin) {
-        case ast::Builtin::kPosition:
+        case ast::BuiltinValue::kPosition:
             return "position";
-        case ast::Builtin::kVertexIndex:
+        case ast::BuiltinValue::kVertexIndex:
             return "vertex_id";
-        case ast::Builtin::kInstanceIndex:
+        case ast::BuiltinValue::kInstanceIndex:
             return "instance_id";
-        case ast::Builtin::kFrontFacing:
+        case ast::BuiltinValue::kFrontFacing:
             return "front_facing";
-        case ast::Builtin::kFragDepth:
+        case ast::BuiltinValue::kFragDepth:
             return "depth(any)";
-        case ast::Builtin::kLocalInvocationId:
+        case ast::BuiltinValue::kLocalInvocationId:
             return "thread_position_in_threadgroup";
-        case ast::Builtin::kLocalInvocationIndex:
+        case ast::BuiltinValue::kLocalInvocationIndex:
             return "thread_index_in_threadgroup";
-        case ast::Builtin::kGlobalInvocationId:
+        case ast::BuiltinValue::kGlobalInvocationId:
             return "thread_position_in_grid";
-        case ast::Builtin::kWorkgroupId:
+        case ast::BuiltinValue::kWorkgroupId:
             return "threadgroup_position_in_grid";
-        case ast::Builtin::kNumWorkgroups:
+        case ast::BuiltinValue::kNumWorkgroups:
             return "threadgroups_per_grid";
-        case ast::Builtin::kSampleIndex:
+        case ast::BuiltinValue::kSampleIndex:
             return "sample_id";
-        case ast::Builtin::kSampleMask:
+        case ast::BuiltinValue::kSampleMask:
             return "sample_mask";
-        case ast::Builtin::kPointSize:
+        case ast::BuiltinValue::kPointSize:
             return "point_size";
         default:
             break;
diff --git a/src/tint/writer/msl/generator_impl.h b/src/tint/writer/msl/generator_impl.h
index f27d258..137b365 100644
--- a/src/tint/writer/msl/generator_impl.h
+++ b/src/tint/writer/msl/generator_impl.h
@@ -374,7 +374,7 @@
     /// Converts a builtin to an attribute name
     /// @param builtin the builtin to convert
     /// @returns the string name of the builtin or blank on error
-    std::string builtin_to_attribute(ast::Builtin builtin) const;
+    std::string builtin_to_attribute(ast::BuiltinValue builtin) const;
 
     /// Converts interpolation attributes to an MSL attribute
     /// @param type the interpolation type
diff --git a/src/tint/writer/msl/generator_impl_function_test.cc b/src/tint/writer/msl/generator_impl_function_test.cc
index e520d7a..c3f926d 100644
--- a/src/tint/writer/msl/generator_impl_function_test.cc
+++ b/src/tint/writer/msl/generator_impl_function_test.cc
@@ -135,7 +135,7 @@
     // fn frag_main(@position(0) coord : vec4<f32>) -> @frag_depth f32 {
     //   return coord.x;
     // }
-    auto* coord_in = Param("coord", ty.vec4<f32>(), {Builtin(ast::Builtin::kPosition)});
+    auto* coord_in = Param("coord", ty.vec4<f32>(), {Builtin(ast::BuiltinValue::kPosition)});
     Func("frag_main", {coord_in}, ty.f32(),
          {
              Return(MemberAccessor("coord", "x")),
@@ -144,7 +144,7 @@
              Stage(ast::PipelineStage::kFragment),
          },
          {
-             Builtin(ast::Builtin::kFragDepth),
+             Builtin(ast::BuiltinValue::kFragDepth),
          });
 
     GeneratorImpl& gen = SanitizeAndBuild();
@@ -188,7 +188,7 @@
         "Interface", {
                          Member("col1", ty.f32(), {Location(1)}),
                          Member("col2", ty.f32(), {Location(2)}),
-                         Member("pos", ty.vec4<f32>(), {Builtin(ast::Builtin::kPosition)}),
+                         Member("pos", ty.vec4<f32>(), {Builtin(ast::BuiltinValue::kPosition)}),
                      });
 
     Func("vert_main", {}, ty.Of(interface_struct),
@@ -268,7 +268,7 @@
     //   return foo(0.25);
     // }
     auto* vertex_output_struct = Structure(
-        "VertexOutput", {Member("pos", ty.vec4<f32>(), {Builtin(ast::Builtin::kPosition)})});
+        "VertexOutput", {Member("pos", ty.vec4<f32>(), {Builtin(ast::BuiltinValue::kPosition)})});
 
     Func("foo", {Param("x", ty.f32())}, ty.Of(vertex_output_struct),
          {
diff --git a/src/tint/writer/msl/generator_impl_test.cc b/src/tint/writer/msl/generator_impl_test.cc
index aec9bfb..45010b2 100644
--- a/src/tint/writer/msl/generator_impl_test.cc
+++ b/src/tint/writer/msl/generator_impl_test.cc
@@ -52,7 +52,7 @@
 }
 
 struct MslBuiltinData {
-    ast::Builtin builtin;
+    ast::BuiltinValue builtin;
     const char* attribute_name;
 };
 inline std::ostream& operator<<(std::ostream& out, MslBuiltinData data) {
@@ -71,23 +71,23 @@
     MslGeneratorImplTest,
     MslBuiltinConversionTest,
     testing::Values(
-        MslBuiltinData{ast::Builtin::kPosition, "position"},
-        MslBuiltinData{ast::Builtin::kVertexIndex, "vertex_id"},
-        MslBuiltinData{ast::Builtin::kInstanceIndex, "instance_id"},
-        MslBuiltinData{ast::Builtin::kFrontFacing, "front_facing"},
-        MslBuiltinData{ast::Builtin::kFragDepth, "depth(any)"},
-        MslBuiltinData{ast::Builtin::kLocalInvocationId, "thread_position_in_threadgroup"},
-        MslBuiltinData{ast::Builtin::kLocalInvocationIndex, "thread_index_in_threadgroup"},
-        MslBuiltinData{ast::Builtin::kGlobalInvocationId, "thread_position_in_grid"},
-        MslBuiltinData{ast::Builtin::kWorkgroupId, "threadgroup_position_in_grid"},
-        MslBuiltinData{ast::Builtin::kNumWorkgroups, "threadgroups_per_grid"},
-        MslBuiltinData{ast::Builtin::kSampleIndex, "sample_id"},
-        MslBuiltinData{ast::Builtin::kSampleMask, "sample_mask"},
-        MslBuiltinData{ast::Builtin::kPointSize, "point_size"}));
+        MslBuiltinData{ast::BuiltinValue::kPosition, "position"},
+        MslBuiltinData{ast::BuiltinValue::kVertexIndex, "vertex_id"},
+        MslBuiltinData{ast::BuiltinValue::kInstanceIndex, "instance_id"},
+        MslBuiltinData{ast::BuiltinValue::kFrontFacing, "front_facing"},
+        MslBuiltinData{ast::BuiltinValue::kFragDepth, "depth(any)"},
+        MslBuiltinData{ast::BuiltinValue::kLocalInvocationId, "thread_position_in_threadgroup"},
+        MslBuiltinData{ast::BuiltinValue::kLocalInvocationIndex, "thread_index_in_threadgroup"},
+        MslBuiltinData{ast::BuiltinValue::kGlobalInvocationId, "thread_position_in_grid"},
+        MslBuiltinData{ast::BuiltinValue::kWorkgroupId, "threadgroup_position_in_grid"},
+        MslBuiltinData{ast::BuiltinValue::kNumWorkgroups, "threadgroups_per_grid"},
+        MslBuiltinData{ast::BuiltinValue::kSampleIndex, "sample_id"},
+        MslBuiltinData{ast::BuiltinValue::kSampleMask, "sample_mask"},
+        MslBuiltinData{ast::BuiltinValue::kPointSize, "point_size"}));
 
 TEST_F(MslGeneratorImplTest, HasInvariantAttribute_True) {
-    auto* out = Structure(
-        "Out", {Member("pos", ty.vec4<f32>(), {Builtin(ast::Builtin::kPosition), Invariant()})});
+    auto* out = Structure("Out", {Member("pos", ty.vec4<f32>(),
+                                         {Builtin(ast::BuiltinValue::kPosition), Invariant()})});
     Func("vert_main", {}, ty.Of(out), {Return(Construct(ty.Of(out)))},
          {Stage(ast::PipelineStage::kVertex)});
 
@@ -118,7 +118,7 @@
 
 TEST_F(MslGeneratorImplTest, HasInvariantAttribute_False) {
     auto* out =
-        Structure("Out", {Member("pos", ty.vec4<f32>(), {Builtin(ast::Builtin::kPosition)})});
+        Structure("Out", {Member("pos", ty.vec4<f32>(), {Builtin(ast::BuiltinValue::kPosition)})});
     Func("vert_main", {}, ty.Of(out), {Return(Construct(ty.Of(out)))},
          {Stage(ast::PipelineStage::kVertex)});
 
diff --git a/src/tint/writer/spirv/builder.cc b/src/tint/writer/spirv/builder.cc
index 5754f6b..e41096c 100644
--- a/src/tint/writer/spirv/builder.cc
+++ b/src/tint/writer/spirv/builder.cc
@@ -563,7 +563,7 @@
     }
 
     for (auto builtin : func_sem->TransitivelyReferencedBuiltinVariables()) {
-        if (builtin.second->builtin == ast::Builtin::kFragDepth) {
+        if (builtin.second->builtin == ast::BuiltinValue::kFragDepth) {
             push_execution_mode(spv::Op::OpExecutionMode,
                                 {Operand(id), U32Operand(SpvExecutionModeDepthReplacing)});
         }
@@ -4135,9 +4135,9 @@
     return SpvStorageClassMax;
 }
 
-SpvBuiltIn Builder::ConvertBuiltin(ast::Builtin builtin, ast::StorageClass storage) {
+SpvBuiltIn Builder::ConvertBuiltin(ast::BuiltinValue builtin, ast::StorageClass storage) {
     switch (builtin) {
-        case ast::Builtin::kPosition:
+        case ast::BuiltinValue::kPosition:
             if (storage == ast::StorageClass::kIn) {
                 return SpvBuiltInFragCoord;
             } else if (storage == ast::StorageClass::kOut) {
@@ -4146,32 +4146,32 @@
                 TINT_ICE(Writer, builder_.Diagnostics()) << "invalid storage class for builtin";
                 break;
             }
-        case ast::Builtin::kVertexIndex:
+        case ast::BuiltinValue::kVertexIndex:
             return SpvBuiltInVertexIndex;
-        case ast::Builtin::kInstanceIndex:
+        case ast::BuiltinValue::kInstanceIndex:
             return SpvBuiltInInstanceIndex;
-        case ast::Builtin::kFrontFacing:
+        case ast::BuiltinValue::kFrontFacing:
             return SpvBuiltInFrontFacing;
-        case ast::Builtin::kFragDepth:
+        case ast::BuiltinValue::kFragDepth:
             return SpvBuiltInFragDepth;
-        case ast::Builtin::kLocalInvocationId:
+        case ast::BuiltinValue::kLocalInvocationId:
             return SpvBuiltInLocalInvocationId;
-        case ast::Builtin::kLocalInvocationIndex:
+        case ast::BuiltinValue::kLocalInvocationIndex:
             return SpvBuiltInLocalInvocationIndex;
-        case ast::Builtin::kGlobalInvocationId:
+        case ast::BuiltinValue::kGlobalInvocationId:
             return SpvBuiltInGlobalInvocationId;
-        case ast::Builtin::kPointSize:
+        case ast::BuiltinValue::kPointSize:
             return SpvBuiltInPointSize;
-        case ast::Builtin::kWorkgroupId:
+        case ast::BuiltinValue::kWorkgroupId:
             return SpvBuiltInWorkgroupId;
-        case ast::Builtin::kNumWorkgroups:
+        case ast::BuiltinValue::kNumWorkgroups:
             return SpvBuiltInNumWorkgroups;
-        case ast::Builtin::kSampleIndex:
+        case ast::BuiltinValue::kSampleIndex:
             push_capability(SpvCapabilitySampleRateShading);
             return SpvBuiltInSampleId;
-        case ast::Builtin::kSampleMask:
+        case ast::BuiltinValue::kSampleMask:
             return SpvBuiltInSampleMask;
-        case ast::Builtin::kNone:
+        case ast::BuiltinValue::kNone:
             break;
     }
     return SpvBuiltInMax;
diff --git a/src/tint/writer/spirv/builder.h b/src/tint/writer/spirv/builder.h
index f6bb93f..a0eff10 100644
--- a/src/tint/writer/spirv/builder.h
+++ b/src/tint/writer/spirv/builder.h
@@ -211,7 +211,7 @@
     /// @param builtin the builtin to convert
     /// @param storage the storage class that this builtin is being used with
     /// @returns the SPIR-V builtin or SpvBuiltInMax on error.
-    SpvBuiltIn ConvertBuiltin(ast::Builtin builtin, ast::StorageClass storage);
+    SpvBuiltIn ConvertBuiltin(ast::BuiltinValue builtin, ast::StorageClass storage);
 
     /// Converts an interpolate attribute to SPIR-V decorations and pushes a
     /// capability if needed.
diff --git a/src/tint/writer/spirv/builder_entry_point_test.cc b/src/tint/writer/spirv/builder_entry_point_test.cc
index 8e9d857..2c10ab3 100644
--- a/src/tint/writer/spirv/builder_entry_point_test.cc
+++ b/src/tint/writer/spirv/builder_entry_point_test.cc
@@ -15,8 +15,8 @@
 #include <memory>
 
 #include "gtest/gtest.h"
-#include "src/tint/ast/builtin.h"
 #include "src/tint/ast/builtin_attribute.h"
+#include "src/tint/ast/builtin_value.h"
 #include "src/tint/ast/location_attribute.h"
 #include "src/tint/ast/return_statement.h"
 #include "src/tint/ast/stage_attribute.h"
@@ -42,7 +42,7 @@
     //              @location(1) loc1 : f32) {
     //   var col : f32 = (coord.x * loc1);
     // }
-    auto* coord = Param("coord", ty.vec4<f32>(), {Builtin(ast::Builtin::kPosition)});
+    auto* coord = Param("coord", ty.vec4<f32>(), {Builtin(ast::BuiltinValue::kPosition)});
     auto* loc1 = Param("loc1", ty.f32(), {Location(1u)});
     auto* mul = Mul(Expr(MemberAccessor("coord", "x")), Expr("loc1"));
     auto* col = Var("col", ty.f32(), ast::StorageClass::kNone, mul);
@@ -199,7 +199,7 @@
     auto* interface = Structure(
         "Interface", {
                          Member("value", ty.f32(), {Location(1u)}),
-                         Member("pos", ty.vec4<f32>(), {Builtin(ast::Builtin::kPosition)}),
+                         Member("pos", ty.vec4<f32>(), {Builtin(ast::BuiltinValue::kPosition)}),
                      });
 
     auto* vert_retval = Construct(ty.Of(interface), 42_f, Construct(ty.vec4<f32>()));
@@ -211,7 +211,7 @@
          {
              Return(MemberAccessor(Expr("inputs"), "value")),
          },
-         {Stage(ast::PipelineStage::kFragment)}, {Builtin(ast::Builtin::kFragDepth)});
+         {Stage(ast::PipelineStage::kFragment)}, {Builtin(ast::BuiltinValue::kFragDepth)});
 
     spirv::Builder& b = SanitizeAndBuild();
 
@@ -302,7 +302,7 @@
 }
 
 TEST_F(BuilderTest, SampleIndex_SampleRateShadingCapability) {
-    Func("main", {Param("sample_index", ty.u32(), {Builtin(ast::Builtin::kSampleIndex)})},
+    Func("main", {Param("sample_index", ty.u32(), {Builtin(ast::BuiltinValue::kSampleIndex)})},
          ty.void_(), {}, {Stage(ast::PipelineStage::kFragment)});
 
     spirv::Builder& b = SanitizeAndBuild();
diff --git a/src/tint/writer/spirv/builder_function_attribute_test.cc b/src/tint/writer/spirv/builder_function_attribute_test.cc
index c309813..ab1fa0b 100644
--- a/src/tint/writer/spirv/builder_function_attribute_test.cc
+++ b/src/tint/writer/spirv/builder_function_attribute_test.cc
@@ -56,7 +56,7 @@
     ast::StatementList body;
     if (params.stage == ast::PipelineStage::kVertex) {
         ret_type = ty.vec4<f32>();
-        ret_type_attrs.push_back(Builtin(ast::Builtin::kPosition));
+        ret_type_attrs.push_back(Builtin(ast::BuiltinValue::kPosition));
         body.push_back(Return(Construct(ty.vec4<f32>())));
     } else {
         ret_type = ty.void_();
@@ -250,7 +250,7 @@
              Stage(ast::PipelineStage::kFragment),
          },
          {
-             Builtin(ast::Builtin::kFragDepth),
+             Builtin(ast::BuiltinValue::kFragDepth),
          });
 
     spirv::Builder& b = SanitizeAndBuild();
diff --git a/src/tint/writer/spirv/builder_global_variable_test.cc b/src/tint/writer/spirv/builder_global_variable_test.cc
index f29b78e..22d8860 100644
--- a/src/tint/writer/spirv/builder_global_variable_test.cc
+++ b/src/tint/writer/spirv/builder_global_variable_test.cc
@@ -422,7 +422,7 @@
 }
 
 struct BuiltinData {
-    ast::Builtin builtin;
+    ast::BuiltinValue builtin;
     ast::StorageClass storage;
     SpvBuiltIn result;
 };
@@ -442,28 +442,31 @@
     BuilderTest_Type,
     BuiltinDataTest,
     testing::Values(
-        BuiltinData{ast::Builtin::kNone, ast::StorageClass::kNone, SpvBuiltInMax},
-        BuiltinData{ast::Builtin::kPosition, ast::StorageClass::kIn, SpvBuiltInFragCoord},
-        BuiltinData{ast::Builtin::kPosition, ast::StorageClass::kOut, SpvBuiltInPosition},
+        BuiltinData{ast::BuiltinValue::kNone, ast::StorageClass::kNone, SpvBuiltInMax},
+        BuiltinData{ast::BuiltinValue::kPosition, ast::StorageClass::kIn, SpvBuiltInFragCoord},
+        BuiltinData{ast::BuiltinValue::kPosition, ast::StorageClass::kOut, SpvBuiltInPosition},
         BuiltinData{
-            ast::Builtin::kVertexIndex,
+            ast::BuiltinValue::kVertexIndex,
             ast::StorageClass::kIn,
             SpvBuiltInVertexIndex,
         },
-        BuiltinData{ast::Builtin::kInstanceIndex, ast::StorageClass::kIn, SpvBuiltInInstanceIndex},
-        BuiltinData{ast::Builtin::kFrontFacing, ast::StorageClass::kIn, SpvBuiltInFrontFacing},
-        BuiltinData{ast::Builtin::kFragDepth, ast::StorageClass::kOut, SpvBuiltInFragDepth},
-        BuiltinData{ast::Builtin::kLocalInvocationId, ast::StorageClass::kIn,
+        BuiltinData{ast::BuiltinValue::kInstanceIndex, ast::StorageClass::kIn,
+                    SpvBuiltInInstanceIndex},
+        BuiltinData{ast::BuiltinValue::kFrontFacing, ast::StorageClass::kIn, SpvBuiltInFrontFacing},
+        BuiltinData{ast::BuiltinValue::kFragDepth, ast::StorageClass::kOut, SpvBuiltInFragDepth},
+        BuiltinData{ast::BuiltinValue::kLocalInvocationId, ast::StorageClass::kIn,
                     SpvBuiltInLocalInvocationId},
-        BuiltinData{ast::Builtin::kLocalInvocationIndex, ast::StorageClass::kIn,
+        BuiltinData{ast::BuiltinValue::kLocalInvocationIndex, ast::StorageClass::kIn,
                     SpvBuiltInLocalInvocationIndex},
-        BuiltinData{ast::Builtin::kGlobalInvocationId, ast::StorageClass::kIn,
+        BuiltinData{ast::BuiltinValue::kGlobalInvocationId, ast::StorageClass::kIn,
                     SpvBuiltInGlobalInvocationId},
-        BuiltinData{ast::Builtin::kWorkgroupId, ast::StorageClass::kIn, SpvBuiltInWorkgroupId},
-        BuiltinData{ast::Builtin::kNumWorkgroups, ast::StorageClass::kIn, SpvBuiltInNumWorkgroups},
-        BuiltinData{ast::Builtin::kSampleIndex, ast::StorageClass::kIn, SpvBuiltInSampleId},
-        BuiltinData{ast::Builtin::kSampleMask, ast::StorageClass::kIn, SpvBuiltInSampleMask},
-        BuiltinData{ast::Builtin::kSampleMask, ast::StorageClass::kOut, SpvBuiltInSampleMask}));
+        BuiltinData{ast::BuiltinValue::kWorkgroupId, ast::StorageClass::kIn, SpvBuiltInWorkgroupId},
+        BuiltinData{ast::BuiltinValue::kNumWorkgroups, ast::StorageClass::kIn,
+                    SpvBuiltInNumWorkgroups},
+        BuiltinData{ast::BuiltinValue::kSampleIndex, ast::StorageClass::kIn, SpvBuiltInSampleId},
+        BuiltinData{ast::BuiltinValue::kSampleMask, ast::StorageClass::kIn, SpvBuiltInSampleMask},
+        BuiltinData{ast::BuiltinValue::kSampleMask, ast::StorageClass::kOut,
+                    SpvBuiltInSampleMask}));
 
 TEST_F(BuilderTest, GlobalVar_DeclReadOnly) {
     // struct A {
diff --git a/src/tint/writer/wgsl/generator_impl_function_test.cc b/src/tint/writer/wgsl/generator_impl_function_test.cc
index 0673858..a7dab7a 100644
--- a/src/tint/writer/wgsl/generator_impl_function_test.cc
+++ b/src/tint/writer/wgsl/generator_impl_function_test.cc
@@ -99,7 +99,7 @@
 
 TEST_F(WgslGeneratorImplTest, Emit_Function_EntryPoint_Parameters) {
     auto* vec4 = ty.vec4<f32>();
-    auto* coord = Param("coord", vec4, {Builtin(ast::Builtin::kPosition)});
+    auto* coord = Param("coord", vec4, {Builtin(ast::BuiltinValue::kPosition)});
     auto* loc1 = Param("loc1", ty.f32(), {Location(1u)});
     auto* func = Func("frag_main", {coord, loc1}, ty.void_(), {},
                       {
diff --git a/src/tint/writer/wgsl/generator_impl_type_test.cc b/src/tint/writer/wgsl/generator_impl_type_test.cc
index 2e54620..9536b5b 100644
--- a/src/tint/writer/wgsl/generator_impl_type_test.cc
+++ b/src/tint/writer/wgsl/generator_impl_type_test.cc
@@ -270,9 +270,9 @@
 }
 
 TEST_F(WgslGeneratorImplTest, EmitType_Struct_WithEntryPointAttributes) {
-    auto* s = Structure(
-        "S", ast::StructMemberList{Member("a", ty.u32(), {Builtin(ast::Builtin::kVertexIndex)}),
-                                   Member("b", ty.f32(), {Location(2u)})});
+    auto* s = Structure("S", ast::StructMemberList{
+                                 Member("a", ty.u32(), {Builtin(ast::BuiltinValue::kVertexIndex)}),
+                                 Member("b", ty.f32(), {Location(2u)})});
 
     GeneratorImpl& gen = Build();