tint/ast: Generate ast::BuiltinValue from intrinsics.def

Emit unit tests for parsing and printing.
Emit benchmarks for parsing.
Uses intrinsics.def as a single-source-of-truth.
The generators provide a way to optimize the enum parsers.

Change-Id: Ic95177b8b60a51f0bcd6dab4138984f54f30ed6d
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/97201
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Dan Sinclair <dsinclair@chromium.org>
Commit-Queue: Ben Clayton <bclayton@google.com>
diff --git a/src/tint/BUILD.gn b/src/tint/BUILD.gn
index 1b3342e..83e97c01 100644
--- a/src/tint/BUILD.gn
+++ b/src/tint/BUILD.gn
@@ -1002,6 +1002,7 @@
       "ast/builtin_attribute_test.cc",
       "ast/builtin_texture_helper_test.cc",
       "ast/builtin_texture_helper_test.h",
+      "ast/builtin_value_test.cc",
       "ast/call_expression_test.cc",
       "ast/call_statement_test.cc",
       "ast/case_statement_test.cc",
diff --git a/src/tint/CMakeLists.txt b/src/tint/CMakeLists.txt
index 6308c5f..ddf7e3f 100644
--- a/src/tint/CMakeLists.txt
+++ b/src/tint/CMakeLists.txt
@@ -698,6 +698,7 @@
     ast/builtin_attribute_test.cc
     ast/builtin_texture_helper_test.cc
     ast/builtin_texture_helper_test.h
+    ast/builtin_value_test.cc
     ast/call_expression_test.cc
     ast/call_statement_test.cc
     ast/case_statement_test.cc
diff --git a/src/tint/ast/builtin_value.cc b/src/tint/ast/builtin_value.cc
index c1caafb..e8d6451 100644
--- a/src/tint/ast/builtin_value.cc
+++ b/src/tint/ast/builtin_value.cc
@@ -12,69 +12,93 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+////////////////////////////////////////////////////////////////////////////////
+// File generated by tools/src/cmd/gen
+// using the template:
+//   src/tint/ast/builtin_value.cc.tmpl
+//
+// Do not modify this file directly
+////////////////////////////////////////////////////////////////////////////////
+
 #include "src/tint/ast/builtin_value.h"
 
 namespace tint::ast {
 
-std::ostream& operator<<(std::ostream& out, BuiltinValue builtin) {
-    switch (builtin) {
-        case BuiltinValue::kNone: {
-            out << "none";
-            break;
-        }
-        case BuiltinValue::kPosition: {
-            out << "position";
-            break;
-        }
-        case BuiltinValue::kVertexIndex: {
-            out << "vertex_index";
-            break;
-        }
-        case BuiltinValue::kInstanceIndex: {
-            out << "instance_index";
-            break;
-        }
-        case BuiltinValue::kFrontFacing: {
-            out << "front_facing";
-            break;
-        }
-        case BuiltinValue::kFragDepth: {
-            out << "frag_depth";
-            break;
-        }
-        case BuiltinValue::kLocalInvocationId: {
-            out << "local_invocation_id";
-            break;
-        }
-        case BuiltinValue::kLocalInvocationIndex: {
-            out << "local_invocation_index";
-            break;
-        }
-        case BuiltinValue::kGlobalInvocationId: {
-            out << "global_invocation_id";
-            break;
-        }
-        case BuiltinValue::kWorkgroupId: {
-            out << "workgroup_id";
-            break;
-        }
-        case BuiltinValue::kNumWorkgroups: {
-            out << "num_workgroups";
-            break;
-        }
-        case BuiltinValue::kSampleIndex: {
-            out << "sample_index";
-            break;
-        }
-        case BuiltinValue::kSampleMask: {
-            out << "sample_mask";
-            break;
-        }
-        case BuiltinValue::kPointSize: {
-            out << "pointsize";
-        }
+/// ParseBuiltinValue parses a BuiltinValue from a string.
+/// @param str the string to parse
+/// @returns the parsed enum, or BuiltinValue::kInvalid if the string could not be parsed.
+BuiltinValue ParseBuiltinValue(std::string_view str) {
+    if (str == "position") {
+        return BuiltinValue::kPosition;
     }
-    return out;
+    if (str == "vertex_index") {
+        return BuiltinValue::kVertexIndex;
+    }
+    if (str == "instance_index") {
+        return BuiltinValue::kInstanceIndex;
+    }
+    if (str == "front_facing") {
+        return BuiltinValue::kFrontFacing;
+    }
+    if (str == "frag_depth") {
+        return BuiltinValue::kFragDepth;
+    }
+    if (str == "local_invocation_id") {
+        return BuiltinValue::kLocalInvocationId;
+    }
+    if (str == "local_invocation_index") {
+        return BuiltinValue::kLocalInvocationIndex;
+    }
+    if (str == "global_invocation_id") {
+        return BuiltinValue::kGlobalInvocationId;
+    }
+    if (str == "workgroup_id") {
+        return BuiltinValue::kWorkgroupId;
+    }
+    if (str == "num_workgroups") {
+        return BuiltinValue::kNumWorkgroups;
+    }
+    if (str == "sample_index") {
+        return BuiltinValue::kSampleIndex;
+    }
+    if (str == "sample_mask") {
+        return BuiltinValue::kSampleMask;
+    }
+    return BuiltinValue::kInvalid;
+}
+
+std::ostream& operator<<(std::ostream& out, BuiltinValue value) {
+    switch (value) {
+        case BuiltinValue::kInvalid:
+            return out << "invalid";
+        case BuiltinValue::kPosition:
+            return out << "position";
+        case BuiltinValue::kVertexIndex:
+            return out << "vertex_index";
+        case BuiltinValue::kInstanceIndex:
+            return out << "instance_index";
+        case BuiltinValue::kFrontFacing:
+            return out << "front_facing";
+        case BuiltinValue::kFragDepth:
+            return out << "frag_depth";
+        case BuiltinValue::kLocalInvocationId:
+            return out << "local_invocation_id";
+        case BuiltinValue::kLocalInvocationIndex:
+            return out << "local_invocation_index";
+        case BuiltinValue::kGlobalInvocationId:
+            return out << "global_invocation_id";
+        case BuiltinValue::kWorkgroupId:
+            return out << "workgroup_id";
+        case BuiltinValue::kNumWorkgroups:
+            return out << "num_workgroups";
+        case BuiltinValue::kSampleIndex:
+            return out << "sample_index";
+        case BuiltinValue::kSampleMask:
+            return out << "sample_mask";
+        case BuiltinValue::kPointSize:
+            return out << "point_size";
+    }
+    return out << "<unknown>";
 }
 
 }  // namespace tint::ast
diff --git a/src/tint/ast/builtin_value.cc.tmpl b/src/tint/ast/builtin_value.cc.tmpl
new file mode 100644
index 0000000..7340c88
--- /dev/null
+++ b/src/tint/ast/builtin_value.cc.tmpl
@@ -0,0 +1,22 @@
+{{- /*
+--------------------------------------------------------------------------------
+Template file for use with tools/src/cmd/gen to generate builtin_value.cc
+
+See:
+* tools/src/cmd/gen for structures used by this template
+* https://golang.org/pkg/text/template/ for documentation on the template syntax
+--------------------------------------------------------------------------------
+*/ -}}
+
+{{- Import "src/tint/templates/enums.tmpl.inc" -}}
+{{- $enum := (Sem.Enum "builtin_value") -}}
+
+#include "src/tint/ast/builtin_value.h"
+
+namespace tint::ast {
+
+{{ Eval "ParseEnum" $enum}}
+
+{{ Eval "EnumOStream" $enum}}
+
+}  // namespace tint::ast
diff --git a/src/tint/ast/builtin_value.h b/src/tint/ast/builtin_value.h
index 68c1939..0a2c7f0 100644
--- a/src/tint/ast/builtin_value.h
+++ b/src/tint/ast/builtin_value.h
@@ -12,6 +12,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+////////////////////////////////////////////////////////////////////////////////
+// File generated by tools/src/cmd/gen
+// using the template:
+//   src/tint/ast/builtin_value.h.tmpl
+//
+// Do not modify this file directly
+////////////////////////////////////////////////////////////////////////////////
+
 #ifndef SRC_TINT_AST_BUILTIN_VALUE_H_
 #define SRC_TINT_AST_BUILTIN_VALUE_H_
 
@@ -19,9 +27,9 @@
 
 namespace tint::ast {
 
-/// The builtin identifiers
+/// Storage class of a given pointer.
 enum class BuiltinValue {
-    kNone = -1,
+    kInvalid,
     kPosition,
     kVertexIndex,
     kInstanceIndex,
@@ -34,16 +42,18 @@
     kNumWorkgroups,
     kSampleIndex,
     kSampleMask,
-
-    // Below are not currently WGSL builtins, but are included in this enum as
-    // they are used by certain backends.
-    kPointSize,
+    kPointSize,  // Tint-internal enum entry - not parsed
 };
 
 /// @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, BuiltinValue builtin);
+/// @param value the BuiltinValue
+/// @returns `out` so calls can be chained
+std::ostream& operator<<(std::ostream& out, BuiltinValue value);
+
+/// ParseBuiltinValue parses a BuiltinValue from a string.
+/// @param str the string to parse
+/// @returns the parsed enum, or BuiltinValue::kInvalid if the string could not be parsed.
+BuiltinValue ParseBuiltinValue(std::string_view str);
 
 }  // namespace tint::ast
 
diff --git a/src/tint/ast/builtin_value.h.tmpl b/src/tint/ast/builtin_value.h.tmpl
new file mode 100644
index 0000000..1985305
--- /dev/null
+++ b/src/tint/ast/builtin_value.h.tmpl
@@ -0,0 +1,26 @@
+{{- /*
+--------------------------------------------------------------------------------
+Template file for use with tools/src/cmd/gen to generate builtin_value.h
+
+See:
+* tools/src/cmd/gen for structures used by this template
+* https://golang.org/pkg/text/template/ for documentation on the template syntax
+--------------------------------------------------------------------------------
+*/ -}}
+
+{{- Import "src/tint/templates/enums.tmpl.inc" -}}
+{{- $enum := (Sem.Enum "builtin_value") -}}
+
+#ifndef SRC_TINT_AST_BUILTIN_VALUE_H_
+#define SRC_TINT_AST_BUILTIN_VALUE_H_
+
+#include <ostream>
+
+namespace tint::ast {
+
+/// Storage class of a given pointer.
+{{ Eval "DeclareEnum" $enum}}
+
+}  // namespace tint::ast
+
+#endif  // SRC_TINT_AST_BUILTIN_VALUE_H_
diff --git a/src/tint/ast/builtin_value_bench.cc b/src/tint/ast/builtin_value_bench.cc
new file mode 100644
index 0000000..0a4048c
--- /dev/null
+++ b/src/tint/ast/builtin_value_bench.cc
@@ -0,0 +1,130 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+////////////////////////////////////////////////////////////////////////////////
+// File generated by tools/src/cmd/gen
+// using the template:
+//   src/tint/ast/builtin_value_bench.cc.tmpl
+//
+// Do not modify this file directly
+////////////////////////////////////////////////////////////////////////////////
+
+#include "src/tint/ast/builtin_value.h"
+
+#include <array>
+
+#include "benchmark/benchmark.h"
+
+namespace tint::ast {
+namespace {
+
+void BuiltinValueParser(::benchmark::State& state) {
+    std::array kStrings{
+        "pccsitin",
+        "oiti3",
+        "positVon",
+        "position",
+        "1osition",
+        "osJtqqon",
+        "llos77tion",
+        "vrtHHppx_index",
+        "vertx_icx",
+        "veGtex_bnde",
+        "vertex_index",
+        "vertex_inveii",
+        "veWWtex_ind8x",
+        "vxxrtMx_indx",
+        "isXance_indegg",
+        "insanc_iXVex",
+        "instance_in3ex",
+        "instance_index",
+        "instancE_index",
+        "nsTTance_PPndex",
+        "nstancxx_indddx",
+        "44ront_facing",
+        "fSSont_facinVV",
+        "fronR_Racing",
+        "front_facing",
+        "ron9_faciFg",
+        "front_facin",
+        "fVonRR_HaOing",
+        "fyag_epth",
+        "f77ag_nnellrrh",
+        "fra400depth",
+        "frag_depth",
+        "fa_epooh",
+        "frg_ezzth",
+        "f11a_eppiih",
+        "local_invXXcation_id",
+        "lIIcal_i5599ocation_inn",
+        "HHrrcal_inSSocation_Yaa",
+        "local_invocation_id",
+        "lokkal_invocatini",
+        "jocal_invocRRongid",
+        "local_inocatbon_i",
+        "local_injocation_index",
+        "local_invocatio_index",
+        "locl_invocqtion_ndex",
+        "local_invocation_index",
+        "localNNinvocaton_index",
+        "local_invocatin_ivvdx",
+        "locl_invocatioQQ_index",
+        "globalrnvocaton_iff",
+        "global_invocation_jd",
+        "NNlbal_wwnvocation82d",
+        "global_invocation_id",
+        "global_invocationid",
+        "globalrrinvocation_id",
+        "globaG_invocation_id",
+        "workgroupFFid",
+        "worgrupid",
+        "workgroup_rr",
+        "workgroup_id",
+        "workgrouid",
+        "DokgXoJJp_id",
+        "8orgrup_i",
+        "num_wkkr11up",
+        "numworkgroups",
+        "Ju_workgroups",
+        "num_workgroups",
+        "num_corkgroups",
+        "num_woOkgroups",
+        "num_workKK__vvttps",
+        "smple5inxxe8",
+        "s__mle_qFdex",
+        "saqqple_idex",
+        "sample_index",
+        "saOpe_33nde66",
+        "s6oople_indttQx",
+        "sam66le_inex",
+        "samxe66masOz",
+        "yyample_mask",
+        "amplZZHask",
+        "sample_mask",
+        "WWaple_maq4k",
+        "samplOO_ask",
+        "sYohpe_msk",
+    };
+    for (auto _ : state) {
+        for (auto& str : kStrings) {
+            auto result = ParseBuiltinValue(str);
+            benchmark::DoNotOptimize(result);
+        }
+    }
+}
+
+BENCHMARK(BuiltinValueParser);
+
+}  // namespace
+}  // namespace tint::ast
diff --git a/src/tint/ast/builtin_value_bench.cc.tmpl b/src/tint/ast/builtin_value_bench.cc.tmpl
new file mode 100644
index 0000000..f50bd20
--- /dev/null
+++ b/src/tint/ast/builtin_value_bench.cc.tmpl
@@ -0,0 +1,26 @@
+{{- /*
+--------------------------------------------------------------------------------
+Template file for use with tools/src/cmd/gen to generate builtin_value_bench.cc
+
+See:
+* tools/src/cmd/gen for structures used by this template
+* https://golang.org/pkg/text/template/ for documentation on the template syntax
+--------------------------------------------------------------------------------
+*/ -}}
+
+{{- Import "src/tint/templates/enums.tmpl.inc" -}}
+{{- $enum := (Sem.Enum "builtin_value") -}}
+
+#include "src/tint/ast/builtin_value.h"
+
+#include <array>
+
+#include "benchmark/benchmark.h"
+
+namespace tint::ast {
+namespace {
+
+{{ Eval "BenchmarkParseEnum" $enum }}
+
+}  // namespace
+}  // namespace tint::ast
diff --git a/src/tint/ast/builtin_value_test.cc b/src/tint/ast/builtin_value_test.cc
new file mode 100644
index 0000000..a29810a
--- /dev/null
+++ b/src/tint/ast/builtin_value_test.cc
@@ -0,0 +1,122 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+////////////////////////////////////////////////////////////////////////////////
+// File generated by tools/src/cmd/gen
+// using the template:
+//   src/tint/ast/builtin_value_test.cc.tmpl
+//
+// Do not modify this file directly
+////////////////////////////////////////////////////////////////////////////////
+
+#include "src/tint/ast/builtin_value.h"
+
+#include <string>
+
+#include "src/tint/ast/test_helper.h"
+#include "src/tint/utils/string.h"
+
+namespace tint::ast {
+namespace {
+
+namespace parse_print_tests {
+
+struct Case {
+    const char* string;
+    BuiltinValue value;
+};
+
+inline std::ostream& operator<<(std::ostream& out, Case c) {
+    return out << "'" << std::string(c.string) << "'";
+}
+
+static constexpr Case kValidCases[] = {
+    {"position", BuiltinValue::kPosition},
+    {"vertex_index", BuiltinValue::kVertexIndex},
+    {"instance_index", BuiltinValue::kInstanceIndex},
+    {"front_facing", BuiltinValue::kFrontFacing},
+    {"frag_depth", BuiltinValue::kFragDepth},
+    {"local_invocation_id", BuiltinValue::kLocalInvocationId},
+    {"local_invocation_index", BuiltinValue::kLocalInvocationIndex},
+    {"global_invocation_id", BuiltinValue::kGlobalInvocationId},
+    {"workgroup_id", BuiltinValue::kWorkgroupId},
+    {"num_workgroups", BuiltinValue::kNumWorkgroups},
+    {"sample_index", BuiltinValue::kSampleIndex},
+    {"sample_mask", BuiltinValue::kSampleMask},
+};
+
+static constexpr Case kInvalidCases[] = {
+    {"pccsitin", BuiltinValue::kInvalid},
+    {"oiti3", BuiltinValue::kInvalid},
+    {"positVon", BuiltinValue::kInvalid},
+    {"1ertex_index", BuiltinValue::kInvalid},
+    {"vertex_Jnqex", BuiltinValue::kInvalid},
+    {"velltex_inde77", BuiltinValue::kInvalid},
+    {"inpptanceqHHindx", BuiltinValue::kInvalid},
+    {"cnsanvendex", BuiltinValue::kInvalid},
+    {"istancG_index", BuiltinValue::kInvalid},
+    {"front_facvnii", BuiltinValue::kInvalid},
+    {"frWWnt_faci8g", BuiltinValue::kInvalid},
+    {"fxxonM_facig", BuiltinValue::kInvalid},
+    {"fXag_detgg", BuiltinValue::kInvalid},
+    {"fag_XuVh", BuiltinValue::kInvalid},
+    {"frag_dept3", BuiltinValue::kInvalid},
+    {"local_Envocation_id", BuiltinValue::kInvalid},
+    {"localiPPvocatioTT_id", BuiltinValue::kInvalid},
+    {"localxxnvocationddid", BuiltinValue::kInvalid},
+    {"loca44_invocation_index", BuiltinValue::kInvalid},
+    {"local_invocSStionVVindex", BuiltinValue::kInvalid},
+    {"locRR_invocat22n_index", BuiltinValue::kInvalid},
+    {"globalFinvoction_id", BuiltinValue::kInvalid},
+    {"gloal_invocation_id", BuiltinValue::kInvalid},
+    {"RRlHOOaV_invoction_id", BuiltinValue::kInvalid},
+    {"workgyoup_i", BuiltinValue::kInvalid},
+    {"wnrrrkg77loup_Gd", BuiltinValue::kInvalid},
+    {"00orkgr4up_id", BuiltinValue::kInvalid},
+    {"numwroogrops", BuiltinValue::kInvalid},
+    {"nzm_wokgroups", BuiltinValue::kInvalid},
+    {"uippworkgro11ps", BuiltinValue::kInvalid},
+    {"sample_iXXdex", BuiltinValue::kInvalid},
+    {"5nnample_99IIdex", BuiltinValue::kInvalid},
+    {"samYlaaHHrrndeSS", BuiltinValue::kInvalid},
+    {"aHkk_mask", BuiltinValue::kInvalid},
+    {"jRRmpl_gsk", BuiltinValue::kInvalid},
+    {"smple_mbk", BuiltinValue::kInvalid},
+};
+
+using BuiltinValueParseTest = testing::TestWithParam<Case>;
+
+TEST_P(BuiltinValueParseTest, Parse) {
+    const char* string = GetParam().string;
+    BuiltinValue expect = GetParam().value;
+    EXPECT_EQ(expect, ParseBuiltinValue(string));
+}
+
+INSTANTIATE_TEST_SUITE_P(ValidCases, BuiltinValueParseTest, testing::ValuesIn(kValidCases));
+INSTANTIATE_TEST_SUITE_P(InvalidCases, BuiltinValueParseTest, testing::ValuesIn(kInvalidCases));
+
+using BuiltinValuePrintTest = testing::TestWithParam<Case>;
+
+TEST_P(BuiltinValuePrintTest, Print) {
+    BuiltinValue value = GetParam().value;
+    const char* expect = GetParam().string;
+    EXPECT_EQ(expect, utils::ToString(value));
+}
+
+INSTANTIATE_TEST_SUITE_P(ValidCases, BuiltinValuePrintTest, testing::ValuesIn(kValidCases));
+
+}  // namespace parse_print_tests
+
+}  // namespace
+}  // namespace tint::ast
diff --git a/src/tint/ast/builtin_value_test.cc.tmpl b/src/tint/ast/builtin_value_test.cc.tmpl
new file mode 100644
index 0000000..213a81b
--- /dev/null
+++ b/src/tint/ast/builtin_value_test.cc.tmpl
@@ -0,0 +1,27 @@
+{{- /*
+--------------------------------------------------------------------------------
+Template file for use with tools/src/cmd/gen to generate builtin_value_test.cc
+
+See:
+* tools/src/cmd/gen for structures used by this template
+* https://golang.org/pkg/text/template/ for documentation on the template syntax
+--------------------------------------------------------------------------------
+*/ -}}
+
+{{- Import "src/tint/templates/enums.tmpl.inc" -}}
+{{- $enum := (Sem.Enum "builtin_value") -}}
+
+#include "src/tint/ast/builtin_value.h"
+
+#include <string>
+
+#include "src/tint/ast/test_helper.h"
+#include "src/tint/utils/string.h"
+
+namespace tint::ast {
+namespace {
+
+{{ Eval "TestParsePrintEnum" $enum}}
+
+}  // namespace
+}  // namespace tint::ast
diff --git a/src/tint/intrinsics.def b/src/tint/intrinsics.def
index c4b2313..072d38a 100644
--- a/src/tint/intrinsics.def
+++ b/src/tint/intrinsics.def
@@ -23,6 +23,23 @@
 // Enumerators                                                                //
 ////////////////////////////////////////////////////////////////////////////////
 
+// https://gpuweb.github.io/gpuweb/wgsl/#builtin-values
+enum builtin_value {
+  position
+  vertex_index
+  instance_index
+  front_facing
+  frag_depth
+  local_invocation_id
+  local_invocation_index
+  global_invocation_id
+  workgroup_id
+  num_workgroups
+  sample_index
+  sample_mask
+  @internal point_size
+}
+
 // https://gpuweb.github.io/gpuweb/wgsl/#extension
 enum extension {
   // WGSL Extension "f16"
diff --git a/src/tint/reader/spirv/enum_converter.cc b/src/tint/reader/spirv/enum_converter.cc
index a85ecbd..6e5986a 100644
--- a/src/tint/reader/spirv/enum_converter.cc
+++ b/src/tint/reader/spirv/enum_converter.cc
@@ -93,7 +93,7 @@
     }
 
     Fail() << "unknown SPIR-V builtin: " << uint32_t(b);
-    return ast::BuiltinValue::kNone;
+    return ast::BuiltinValue::kInvalid;
 }
 
 ast::TextureDimension EnumConverter::ToDim(SpvDim dim, bool arrayed) {
diff --git a/src/tint/reader/spirv/enum_converter_test.cc b/src/tint/reader/spirv/enum_converter_test.cc
index 3a6b914..748b72c 100644
--- a/src/tint/reader/spirv/enum_converter_test.cc
+++ b/src/tint/reader/spirv/enum_converter_test.cc
@@ -205,9 +205,9 @@
 INSTANTIATE_TEST_SUITE_P(
     EnumConverterBad,
     SpvBuiltinTest,
-    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}));
+    testing::Values(BuiltinCase{static_cast<SpvBuiltIn>(9999), false, ast::BuiltinValue::kInvalid},
+                    BuiltinCase{static_cast<SpvBuiltIn>(9999), false, ast::BuiltinValue::kInvalid},
+                    BuiltinCase{SpvBuiltInNumWorkgroups, false, ast::BuiltinValue::kInvalid}));
 
 // Dim
 
diff --git a/src/tint/reader/spirv/parser_impl.cc b/src/tint/reader/spirv/parser_impl.cc
index 4021c52..eab0aff 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::BuiltinValue::kNone) {
+            if (ast_builtin == ast::BuiltinValue::kInvalid) {
                 // 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 d3c170a..13ceb74 100644
--- a/src/tint/reader/wgsl/parser_impl.cc
+++ b/src/tint/reader/wgsl/parser_impl.cc
@@ -70,46 +70,6 @@
 const char kWriteAccess[] = "write";
 const char kReadWriteAccess[] = "read_write";
 
-ast::BuiltinValue ident_to_builtin(std::string_view str) {
-    if (str == "position") {
-        return ast::BuiltinValue::kPosition;
-    }
-    if (str == "vertex_index") {
-        return ast::BuiltinValue::kVertexIndex;
-    }
-    if (str == "instance_index") {
-        return ast::BuiltinValue::kInstanceIndex;
-    }
-    if (str == "front_facing") {
-        return ast::BuiltinValue::kFrontFacing;
-    }
-    if (str == "frag_depth") {
-        return ast::BuiltinValue::kFragDepth;
-    }
-    if (str == "local_invocation_id") {
-        return ast::BuiltinValue::kLocalInvocationId;
-    }
-    if (str == "local_invocation_idx" || str == "local_invocation_index") {
-        return ast::BuiltinValue::kLocalInvocationIndex;
-    }
-    if (str == "global_invocation_id") {
-        return ast::BuiltinValue::kGlobalInvocationId;
-    }
-    if (str == "workgroup_id") {
-        return ast::BuiltinValue::kWorkgroupId;
-    }
-    if (str == "num_workgroups") {
-        return ast::BuiltinValue::kNumWorkgroups;
-    }
-    if (str == "sample_index") {
-        return ast::BuiltinValue::kSampleIndex;
-    }
-    if (str == "sample_mask") {
-        return ast::BuiltinValue::kSampleMask;
-    }
-    return ast::BuiltinValue::kNone;
-}
-
 const char kBindingAttribute[] = "binding";
 const char kBuiltinAttribute[] = "builtin";
 const char kGroupAttribute[] = "group";
@@ -1564,8 +1524,8 @@
         return Failure::kErrored;
     }
 
-    ast::BuiltinValue builtin = ident_to_builtin(ident.value);
-    if (builtin == ast::BuiltinValue::kNone) {
+    ast::BuiltinValue builtin = ast::ParseBuiltinValue(ident.value);
+    if (builtin == ast::BuiltinValue::kInvalid) {
         return add_error(ident.source, "invalid value for builtin attribute");
     }
 
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 05d822b..f338f62 100644
--- a/src/tint/reader/wgsl/parser_impl_variable_attribute_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_variable_attribute_test.cc
@@ -139,7 +139,6 @@
                     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},
diff --git a/src/tint/transform/canonicalize_entry_point_io_test.cc b/src/tint/transform/canonicalize_entry_point_io_test.cc
index c8af902..f2af213 100644
--- a/src/tint/transform/canonicalize_entry_point_io_test.cc
+++ b/src/tint/transform/canonicalize_entry_point_io_test.cc
@@ -3163,7 +3163,7 @@
     auto* expect = R"(
 @builtin(position) @internal(disable_validation__ignore_storage_class) var<out> value : vec4<f32>;
 
-@builtin(pointsize) @internal(disable_validation__ignore_storage_class) var<out> vertex_point_size : f32;
+@builtin(point_size) @internal(disable_validation__ignore_storage_class) var<out> vertex_point_size : f32;
 
 fn vert_main_inner() -> vec4<f32> {
   return vec4<f32>();
@@ -3197,7 +3197,7 @@
 struct tint_symbol {
   @builtin(position)
   value : vec4<f32>,
-  @builtin(pointsize)
+  @builtin(point_size)
   vertex_point_size : f32,
 }
 
@@ -3238,7 +3238,7 @@
     auto* expect = R"(
 @builtin(position) @internal(disable_validation__ignore_storage_class) var<out> pos_1 : vec4<f32>;
 
-@builtin(pointsize) @internal(disable_validation__ignore_storage_class) var<out> vertex_point_size : f32;
+@builtin(point_size) @internal(disable_validation__ignore_storage_class) var<out> vertex_point_size : f32;
 
 struct VertOut {
   pos : vec4<f32>,
@@ -3279,7 +3279,7 @@
     auto* expect = R"(
 @builtin(position) @internal(disable_validation__ignore_storage_class) var<out> pos_1 : vec4<f32>;
 
-@builtin(pointsize) @internal(disable_validation__ignore_storage_class) var<out> vertex_point_size : f32;
+@builtin(point_size) @internal(disable_validation__ignore_storage_class) var<out> vertex_point_size : f32;
 
 fn vert_main_inner() -> VertOut {
   return VertOut();
@@ -3325,7 +3325,7 @@
 struct tint_symbol {
   @builtin(position)
   pos : vec4<f32>,
-  @builtin(pointsize)
+  @builtin(point_size)
   vertex_point_size : f32,
 }
 
@@ -3367,7 +3367,7 @@
 struct tint_symbol {
   @builtin(position)
   pos : vec4<f32>,
-  @builtin(pointsize)
+  @builtin(point_size)
   vertex_point_size : f32,
 }
 
@@ -3432,7 +3432,7 @@
 
 @builtin(position) @internal(disable_validation__ignore_storage_class) var<out> vertex_point_size_1_1 : vec4<f32>;
 
-@builtin(pointsize) @internal(disable_validation__ignore_storage_class) var<out> vertex_point_size_4 : f32;
+@builtin(point_size) @internal(disable_validation__ignore_storage_class) var<out> vertex_point_size_4 : f32;
 
 var<private> vertex_point_size : f32;
 
@@ -3510,7 +3510,7 @@
 
 @builtin(position) @internal(disable_validation__ignore_storage_class) var<out> vertex_point_size_1_1 : vec4<f32>;
 
-@builtin(pointsize) @internal(disable_validation__ignore_storage_class) var<out> vertex_point_size_4 : f32;
+@builtin(point_size) @internal(disable_validation__ignore_storage_class) var<out> vertex_point_size_4 : f32;
 
 fn vert_main_inner(collide : VertIn1, collide_1 : VertIn2) -> VertOut {
   let x = (collide.collide + collide_1.collide);
@@ -3601,7 +3601,7 @@
   vertex_point_size : vec4<f32>,
   @builtin(position)
   vertex_point_size_1 : vec4<f32>,
-  @builtin(pointsize)
+  @builtin(point_size)
   vertex_point_size_2 : f32,
 }
 
@@ -3664,7 +3664,7 @@
   vertex_point_size : vec4<f32>,
   @builtin(position)
   vertex_point_size_1 : vec4<f32>,
-  @builtin(pointsize)
+  @builtin(point_size)
   vertex_point_size_2 : f32,
 }
 
@@ -3753,7 +3753,7 @@
   vertex_point_size : vec4<f32>,
   @builtin(position)
   vertex_point_size_1 : vec4<f32>,
-  @builtin(pointsize)
+  @builtin(point_size)
   vertex_point_size_2 : f32,
 }
 
@@ -3816,7 +3816,7 @@
   vertex_point_size : vec4<f32>,
   @builtin(position)
   vertex_point_size_1 : vec4<f32>,
-  @builtin(pointsize)
+  @builtin(point_size)
   vertex_point_size_2 : f32,
 }
 
diff --git a/src/tint/writer/spirv/builder.cc b/src/tint/writer/spirv/builder.cc
index 0cd3f7c..651f834 100644
--- a/src/tint/writer/spirv/builder.cc
+++ b/src/tint/writer/spirv/builder.cc
@@ -4171,7 +4171,7 @@
             return SpvBuiltInSampleId;
         case ast::BuiltinValue::kSampleMask:
             return SpvBuiltInSampleMask;
-        case ast::BuiltinValue::kNone:
+        case ast::BuiltinValue::kInvalid:
             break;
     }
     return SpvBuiltInMax;
diff --git a/src/tint/writer/spirv/builder_global_variable_test.cc b/src/tint/writer/spirv/builder_global_variable_test.cc
index 22d8860..5d1a5b0 100644
--- a/src/tint/writer/spirv/builder_global_variable_test.cc
+++ b/src/tint/writer/spirv/builder_global_variable_test.cc
@@ -442,7 +442,7 @@
     BuilderTest_Type,
     BuiltinDataTest,
     testing::Values(
-        BuiltinData{ast::BuiltinValue::kNone, ast::StorageClass::kNone, SpvBuiltInMax},
+        BuiltinData{ast::BuiltinValue::kInvalid, ast::StorageClass::kNone, SpvBuiltInMax},
         BuiltinData{ast::BuiltinValue::kPosition, ast::StorageClass::kIn, SpvBuiltInFragCoord},
         BuiltinData{ast::BuiltinValue::kPosition, ast::StorageClass::kOut, SpvBuiltInPosition},
         BuiltinData{