Rename builtin(frag_coord) to builtin(position)

Use the variable storage class to determine the correct builtin to use
in the SPIR-V generator.

Added a deprecation warning for frag_coord.

Bug: tint:714
Change-Id: I5ad4956f9345e2f39f4af16e84668dec345ac82e
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/47742
Auto-Submit: James Price <jrprice@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
diff --git a/src/ast/builtin.h b/src/ast/builtin.h
index 0e8151d..3806e44 100644
--- a/src/ast/builtin.h
+++ b/src/ast/builtin.h
@@ -27,7 +27,7 @@
   kVertexIndex,
   kInstanceIndex,
   kFrontFacing,
-  kFragCoord,
+  kFragCoord,  // TODO(crbug.com/tint/714): Remove this
   kFragDepth,
   kLocalInvocationId,
   kLocalInvocationIndex,
diff --git a/src/reader/spirv/enum_converter.cc b/src/reader/spirv/enum_converter.cc
index bfdb4fb..15491eb 100644
--- a/src/reader/spirv/enum_converter.cc
+++ b/src/reader/spirv/enum_converter.cc
@@ -77,7 +77,7 @@
     case SpvBuiltInFrontFacing:
       return ast::Builtin::kFrontFacing;
     case SpvBuiltInFragCoord:
-      return ast::Builtin::kFragCoord;
+      return ast::Builtin::kPosition;
     case SpvBuiltInFragDepth:
       return ast::Builtin::kFragDepth;
     case SpvBuiltInLocalInvocationId:
diff --git a/src/reader/spirv/enum_converter_test.cc b/src/reader/spirv/enum_converter_test.cc
index 670e63d..7011216 100644
--- a/src/reader/spirv/enum_converter_test.cc
+++ b/src/reader/spirv/enum_converter_test.cc
@@ -214,7 +214,7 @@
         BuiltinCase{SpvBuiltInFrontFacing, ast::StorageClass::kInput, true,
                     ast::Builtin::kFrontFacing},
         BuiltinCase{SpvBuiltInFragCoord, ast::StorageClass::kInput, true,
-                    ast::Builtin::kFragCoord},
+                    ast::Builtin::kPosition},
         BuiltinCase{SpvBuiltInLocalInvocationId, ast::StorageClass::kInput,
                     true, ast::Builtin::kLocalInvocationId},
         BuiltinCase{SpvBuiltInLocalInvocationIndex, ast::StorageClass::kInput,
diff --git a/src/reader/wgsl/parser_impl.cc b/src/reader/wgsl/parser_impl.cc
index c873095..923053c 100644
--- a/src/reader/wgsl/parser_impl.cc
+++ b/src/reader/wgsl/parser_impl.cc
@@ -1379,6 +1379,10 @@
   if (builtin == ast::Builtin::kNone)
     return add_error(ident.source, "invalid value for builtin decoration");
 
+  if (builtin == ast::Builtin::kFragCoord) {
+    deprecated(ident.source, "use 'position' instead of 'frag_coord'");
+  }
+
   return {builtin, ident.source};
 }
 
diff --git a/src/reader/wgsl/parser_impl_param_list_test.cc b/src/reader/wgsl/parser_impl_param_list_test.cc
index c581145..4949c38 100644
--- a/src/reader/wgsl/parser_impl_param_list_test.cc
+++ b/src/reader/wgsl/parser_impl_param_list_test.cc
@@ -97,7 +97,7 @@
 
 TEST_F(ParserImplTest, ParamList_Decorations) {
   auto p = parser(
-      "[[builtin(frag_coord)]] coord : vec4<f32>, "
+      "[[builtin(position)]] coord : vec4<f32>, "
       "[[location(1)]] loc1 : f32");
 
   auto* f32 = p->builder().create<type::F32>();
@@ -115,12 +115,12 @@
   ASSERT_EQ(decos0.size(), 1u);
   EXPECT_TRUE(decos0[0]->Is<ast::BuiltinDecoration>());
   EXPECT_EQ(decos0[0]->As<ast::BuiltinDecoration>()->value(),
-            ast::Builtin::kFragCoord);
+            ast::Builtin::kPosition);
 
   ASSERT_EQ(e.value[0]->source().range.begin.line, 1u);
-  ASSERT_EQ(e.value[0]->source().range.begin.column, 25u);
+  ASSERT_EQ(e.value[0]->source().range.begin.column, 23u);
   ASSERT_EQ(e.value[0]->source().range.end.line, 1u);
-  ASSERT_EQ(e.value[0]->source().range.end.column, 30u);
+  ASSERT_EQ(e.value[0]->source().range.end.column, 28u);
 
   EXPECT_EQ(e.value[1]->symbol(), p->builder().Symbols().Get("loc1"));
   EXPECT_EQ(e.value[1]->declared_type(), f32);
@@ -131,9 +131,9 @@
   EXPECT_EQ(decos1[0]->As<ast::LocationDecoration>()->value(), 1u);
 
   ASSERT_EQ(e.value[1]->source().range.begin.line, 1u);
-  ASSERT_EQ(e.value[1]->source().range.begin.column, 60u);
+  ASSERT_EQ(e.value[1]->source().range.begin.column, 58u);
   ASSERT_EQ(e.value[1]->source().range.end.line, 1u);
-  ASSERT_EQ(e.value[1]->source().range.end.column, 64u);
+  ASSERT_EQ(e.value[1]->source().range.end.column, 62u);
 }
 
 }  // namespace
diff --git a/src/reader/wgsl/parser_impl_variable_decoration_test.cc b/src/reader/wgsl/parser_impl_variable_decoration_test.cc
index 4f4f6af..ea44069 100644
--- a/src/reader/wgsl/parser_impl_variable_decoration_test.cc
+++ b/src/reader/wgsl/parser_impl_variable_decoration_test.cc
@@ -305,6 +305,28 @@
             "1:7: expected signed integer literal for group decoration");
 }
 
+TEST_F(ParserImplTest, Decoration_FragCoord_Deprecated) {
+  auto p = parser("builtin(frag_coord)");
+  auto deco = p->decoration();
+  EXPECT_TRUE(deco.matched);
+  EXPECT_FALSE(deco.errored);
+  ASSERT_NE(deco.value, nullptr);
+  auto* var_deco = deco.value->As<ast::Decoration>();
+  ASSERT_NE(var_deco, nullptr);
+  ASSERT_FALSE(p->has_error());
+  ASSERT_TRUE(var_deco->Is<ast::BuiltinDecoration>());
+
+  auto* builtin = var_deco->As<ast::BuiltinDecoration>();
+  EXPECT_EQ(builtin->value(), ast::Builtin::kFragCoord);
+
+  EXPECT_EQ(
+      p->builder().Diagnostics().str(),
+      R"(test.wgsl:1:9 warning: use of deprecated language feature: use 'position' instead of 'frag_coord'
+builtin(frag_coord)
+        ^^^^^^^^^^
+)");
+}
+
 }  // namespace
 }  // namespace wgsl
 }  // namespace reader
diff --git a/src/transform/canonicalize_entry_point_io.h b/src/transform/canonicalize_entry_point_io.h
index 479cd0c..4298459 100644
--- a/src/transform/canonicalize_entry_point_io.h
+++ b/src/transform/canonicalize_entry_point_io.h
@@ -34,7 +34,7 @@
 /// };
 ///
 /// [[stage(fragment)]]
-/// fn frag_main([[builtin(frag_coord)]] coord : vec4<f32>,
+/// fn frag_main([[builtin(position)]] coord : vec4<f32>,
 ///              locations : Locations) -> [[location(0)]] f32 {
 ///   var col : f32 = (coord.x * locations.loc1);
 ///   return col;
@@ -49,7 +49,7 @@
 /// };
 ///
 /// struct frag_main_in {
-///   [[builtin(frag_coord)]] coord : vec4<f32>;
+///   [[builtin(position)]] coord : vec4<f32>;
 ///   [[location(1)]] loc1 : f32;
 ///   [[location(2)]] loc2 : vec4<u32>
 /// };
diff --git a/src/transform/canonicalize_entry_point_io_test.cc b/src/transform/canonicalize_entry_point_io_test.cc
index ba7b827..923a06f 100644
--- a/src/transform/canonicalize_entry_point_io_test.cc
+++ b/src/transform/canonicalize_entry_point_io_test.cc
@@ -25,7 +25,7 @@
 TEST_F(CanonicalizeEntryPointIOTest, Parameters) {
   auto* src = R"(
 [[stage(fragment)]]
-fn frag_main([[builtin(frag_coord)]] coord : vec4<f32>,
+fn frag_main([[builtin(position)]] coord : vec4<f32>,
              [[location(1)]] loc1 : f32,
              [[location(2)]] loc2 : vec4<u32>) {
   var col : f32 = (coord.x * loc1);
@@ -34,7 +34,7 @@
 
   auto* expect = R"(
 struct tint_symbol_1 {
-  [[builtin(frag_coord)]]
+  [[builtin(position)]]
   coord : vec4<f32>;
   [[location(1)]]
   loc1 : f32;
@@ -89,7 +89,7 @@
 TEST_F(CanonicalizeEntryPointIOTest, Parameters_EmptyBody) {
   auto* src = R"(
 [[stage(fragment)]]
-fn frag_main([[builtin(frag_coord)]] coord : vec4<f32>,
+fn frag_main([[builtin(position)]] coord : vec4<f32>,
              [[location(1)]] loc1 : f32,
              [[location(2)]] loc2 : vec4<u32>) {
 }
@@ -97,7 +97,7 @@
 
   auto* expect = R"(
 struct tint_symbol_1 {
-  [[builtin(frag_coord)]]
+  [[builtin(position)]]
   coord : vec4<f32>;
   [[location(1)]]
   loc1 : f32;
@@ -118,7 +118,7 @@
 TEST_F(CanonicalizeEntryPointIOTest, StructParameters) {
   auto* src = R"(
 struct FragBuiltins {
-  [[builtin(frag_coord)]] coord : vec4<f32>;
+  [[builtin(position)]] coord : vec4<f32>;
 };
 struct FragLocations {
   [[location(1)]] loc1 : f32;
@@ -144,7 +144,7 @@
 };
 
 struct tint_symbol_1 {
-  [[builtin(frag_coord)]]
+  [[builtin(position)]]
   coord : vec4<f32>;
   [[location(1)]]
   loc1 : f32;
@@ -451,7 +451,7 @@
 [[block]]
 struct FragmentInput {
   [[size(16), location(1)]] value : f32;
-  [[builtin(frag_coord)]] [[align(32)]] coord : vec4<f32>;
+  [[builtin(position)]] [[align(32)]] coord : vec4<f32>;
 };
 
 struct FragmentOutput {
@@ -481,7 +481,7 @@
 struct tint_symbol_1 {
   [[location(1)]]
   value : f32;
-  [[builtin(frag_coord)]]
+  [[builtin(position)]]
   coord : vec4<f32>;
 };
 
diff --git a/src/transform/spirv.cc b/src/transform/spirv.cc
index 6adc931..4e0bed9 100644
--- a/src/transform/spirv.cc
+++ b/src/transform/spirv.cc
@@ -68,7 +68,7 @@
   //
   // [[stage(fragment)]]
   // fn frag_main(
-  //   [[builtin(frag_coord)]] coord : vec4<f32>,
+  //   [[builtin(position)]] coord : vec4<f32>,
   //   samples : FragmentInput
   // ) -> FragmentOutput {
   //   var output : FragmentOutput = FragmentOutput(1.0,
@@ -88,7 +88,7 @@
   //   mask_out : u32;
   // };
   //
-  // [[builtin(frag_coord)]] var<in> coord : vec4<f32>,
+  // [[builtin(position)]] var<in> coord : vec4<f32>,
   // [[builtin(sample_index)]] var<in> sample_index : u32,
   // [[builtin(sample_mask_in)]] var<in> sample_mask_in : u32,
   // [[builtin(frag_depth)]] var<out> depth: f32;
diff --git a/src/transform/spirv_test.cc b/src/transform/spirv_test.cc
index f784435..45e0128 100644
--- a/src/transform/spirv_test.cc
+++ b/src/transform/spirv_test.cc
@@ -25,7 +25,7 @@
 TEST_F(SpirvTest, HandleEntryPointIOTypes_Parameters) {
   auto* src = R"(
 [[stage(fragment)]]
-fn frag_main([[builtin(frag_coord)]] coord : vec4<f32>,
+fn frag_main([[builtin(position)]] coord : vec4<f32>,
              [[location(1)]] loc1 : f32) {
   var col : f32 = (coord.x * loc1);
 }
@@ -38,7 +38,7 @@
 )";
 
   auto* expect = R"(
-[[builtin(frag_coord)]] var<in> tint_symbol : vec4<f32>;
+[[builtin(position)]] var<in> tint_symbol : vec4<f32>;
 
 [[location(1)]] var<in> tint_symbol_1 : f32;
 
@@ -192,7 +192,7 @@
 TEST_F(SpirvTest, HandleEntryPointIOTypes_StructParameters) {
   auto* src = R"(
 struct FragmentInput {
-  [[builtin(frag_coord)]] coord : vec4<f32>;
+  [[builtin(position)]] coord : vec4<f32>;
   [[location(1)]] value : f32;
 };
 
@@ -208,7 +208,7 @@
   value : f32;
 };
 
-[[builtin(frag_coord)]] var<in> tint_symbol : vec4<f32>;
+[[builtin(position)]] var<in> tint_symbol : vec4<f32>;
 
 [[location(1)]] var<in> tint_symbol_1 : f32;
 
@@ -392,7 +392,7 @@
 [[block]]
 struct FragmentInput {
   [[size(16), location(1)]] value : f32;
-  [[builtin(frag_coord)]] [[align(32)]] coord : vec4<f32>;
+  [[builtin(position)]] [[align(32)]] coord : vec4<f32>;
 };
 
 struct FragmentOutput {
@@ -421,7 +421,7 @@
 
 [[location(1)]] var<in> tint_symbol : f32;
 
-[[builtin(frag_coord)]] var<in> tint_symbol_1 : vec4<f32>;
+[[builtin(position)]] var<in> tint_symbol_1 : vec4<f32>;
 
 [[location(1)]] var<out> tint_symbol_4 : f32;
 
diff --git a/src/writer/hlsl/generator_impl_function_entry_point_data_test.cc b/src/writer/hlsl/generator_impl_function_entry_point_data_test.cc
index 2c65dc4..acaef38 100644
--- a/src/writer/hlsl/generator_impl_function_entry_point_data_test.cc
+++ b/src/writer/hlsl/generator_impl_function_entry_point_data_test.cc
@@ -268,7 +268,7 @@
 
 TEST_F(HlslGeneratorImplTest_EntryPoint,
        Emit_Function_EntryPointData_Builtins) {
-  // [[builtin frag_coord]] var<in> coord : vec4<f32>;
+  // [[builtin position]] var<in> coord : vec4<f32>;
   // [[builtin frag_depth]] var<out> depth : f32;
   //
   // struct main_in {
@@ -281,7 +281,7 @@
 
   Global("coord", ty.vec4<f32>(), ast::StorageClass::kInput, nullptr,
          ast::DecorationList{
-             create<ast::BuiltinDecoration>(ast::Builtin::kFragCoord),
+             create<ast::BuiltinDecoration>(ast::Builtin::kPosition),
          });
 
   Global("depth", ty.f32(), ast::StorageClass::kOutput, nullptr,
diff --git a/src/writer/hlsl/generator_impl_function_test.cc b/src/writer/hlsl/generator_impl_function_test.cc
index 0282d72..37af244 100644
--- a/src/writer/hlsl/generator_impl_function_test.cc
+++ b/src/writer/hlsl/generator_impl_function_test.cc
@@ -144,7 +144,7 @@
   // }
   auto* coord_in =
       Param("coord", ty.vec4<f32>(),
-            {create<ast::BuiltinDecoration>(ast::Builtin::kFragCoord)});
+            {create<ast::BuiltinDecoration>(ast::Builtin::kPosition)});
   Func("frag_main", ast::VariableList{coord_in}, ty.f32(),
        {create<ast::ReturnStatement>(MemberAccessor("coord", "x"))},
        {create<ast::StageDecoration>(ast::PipelineStage::kFragment)},
@@ -675,7 +675,7 @@
     Emit_Decoration_Called_By_EntryPoints_WithBuiltinGlobals_And_Params) {  // NOLINT
   Global("coord", ty.vec4<f32>(), ast::StorageClass::kInput, nullptr,
          ast::DecorationList{
-             create<ast::BuiltinDecoration>(ast::Builtin::kFragCoord),
+             create<ast::BuiltinDecoration>(ast::Builtin::kPosition),
          });
 
   Global("depth", ty.f32(), ast::StorageClass::kOutput, nullptr,
diff --git a/src/writer/msl/generator_impl_function_entry_point_data_test.cc b/src/writer/msl/generator_impl_function_entry_point_data_test.cc
index fa22462..46bf201 100644
--- a/src/writer/msl/generator_impl_function_entry_point_data_test.cc
+++ b/src/writer/msl/generator_impl_function_entry_point_data_test.cc
@@ -234,7 +234,7 @@
   // Output builtins go in the output struct, input builtins will be passed
   // as input parameters to the entry point function.
 
-  // [[builtin frag_coord]] var<in> coord : vec4<f32>;
+  // [[builtin position]] var<in> coord : vec4<f32>;
   // [[builtin frag_depth]] var<out> depth : f32;
   //
   // struct main_out {
@@ -243,7 +243,7 @@
 
   Global("coord", ty.vec4<f32>(), ast::StorageClass::kInput, nullptr,
          ast::DecorationList{
-             create<ast::BuiltinDecoration>(ast::Builtin::kFragCoord)});
+             create<ast::BuiltinDecoration>(ast::Builtin::kPosition)});
 
   Global("depth", ty.f32(), ast::StorageClass::kOutput, nullptr,
          ast::DecorationList{
diff --git a/src/writer/msl/generator_impl_function_test.cc b/src/writer/msl/generator_impl_function_test.cc
index 406df03..87001b3 100644
--- a/src/writer/msl/generator_impl_function_test.cc
+++ b/src/writer/msl/generator_impl_function_test.cc
@@ -129,7 +129,7 @@
   // }
   auto* coord_in =
       Param("coord", ty.vec4<f32>(),
-            {create<ast::BuiltinDecoration>(ast::Builtin::kFragCoord)});
+            {create<ast::BuiltinDecoration>(ast::Builtin::kPosition)});
   Func("frag_main", ast::VariableList{coord_in}, ty.f32(),
        {create<ast::ReturnStatement>(MemberAccessor("coord", "x"))},
        {create<ast::StageDecoration>(ast::PipelineStage::kFragment)},
@@ -496,7 +496,7 @@
     Emit_Decoration_Called_By_EntryPoints_WithBuiltinGlobals_And_Params) {  // NOLINT
   Global("coord", ty.vec4<f32>(), ast::StorageClass::kInput, nullptr,
          ast::DecorationList{
-             create<ast::BuiltinDecoration>(ast::Builtin::kFragCoord)});
+             create<ast::BuiltinDecoration>(ast::Builtin::kPosition)});
 
   Global("depth", ty.f32(), ast::StorageClass::kOutput, nullptr,
          ast::DecorationList{
diff --git a/src/writer/spirv/builder.cc b/src/writer/spirv/builder.cc
index 69a0361..50278ca 100644
--- a/src/writer/spirv/builder.cc
+++ b/src/writer/spirv/builder.cc
@@ -788,7 +788,8 @@
     if (auto* builtin = deco->As<ast::BuiltinDecoration>()) {
       push_annot(spv::Op::OpDecorate,
                  {Operand::Int(var_id), Operand::Int(SpvDecorationBuiltIn),
-                  Operand::Int(ConvertBuiltin(builtin->value()))});
+                  Operand::Int(ConvertBuiltin(builtin->value(),
+                                              var->declared_storage_class()))});
     } else if (auto* location = deco->As<ast::LocationDecoration>()) {
       push_annot(spv::Op::OpDecorate,
                  {Operand::Int(var_id), Operand::Int(SpvDecorationLocation),
@@ -3274,10 +3275,18 @@
   return SpvStorageClassMax;
 }
 
-SpvBuiltIn Builder::ConvertBuiltin(ast::Builtin builtin) {
+SpvBuiltIn Builder::ConvertBuiltin(ast::Builtin builtin,
+                                   ast::StorageClass storage) {
   switch (builtin) {
     case ast::Builtin::kPosition:
-      return SpvBuiltInPosition;
+      if (storage == ast::StorageClass::kInput) {
+        return SpvBuiltInFragCoord;
+      } else if (storage == ast::StorageClass::kOutput) {
+        return SpvBuiltInPosition;
+      } else {
+        TINT_ICE(builder_.Diagnostics()) << "invalid storage class for builtin";
+        break;
+      }
     case ast::Builtin::kVertexIndex:
       return SpvBuiltInVertexIndex;
     case ast::Builtin::kInstanceIndex:
diff --git a/src/writer/spirv/builder.h b/src/writer/spirv/builder.h
index f254dc6..16b25e9 100644
--- a/src/writer/spirv/builder.h
+++ b/src/writer/spirv/builder.h
@@ -204,8 +204,9 @@
   SpvStorageClass ConvertStorageClass(ast::StorageClass klass) const;
   /// Converts a builtin to a SPIR-V builtin and pushes a capability if needed.
   /// @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);
+  SpvBuiltIn ConvertBuiltin(ast::Builtin builtin, ast::StorageClass storage);
 
   /// Generates a label for the given id. Emits an error and returns false if
   /// we're currently outside a function.
diff --git a/src/writer/spirv/builder_entry_point_test.cc b/src/writer/spirv/builder_entry_point_test.cc
index 41f52c2..3e02d1a 100644
--- a/src/writer/spirv/builder_entry_point_test.cc
+++ b/src/writer/spirv/builder_entry_point_test.cc
@@ -38,15 +38,14 @@
 
 TEST_F(BuilderTest, EntryPoint_Parameters) {
   // [[stage(fragment)]]
-  // fn frag_main([[builtin(frag_coord)]] coord : vec4<f32>,
+  // fn frag_main([[builtin(position)]] coord : vec4<f32>,
   //              [[location(1)]] loc1 : f32) {
   //   var col : f32 = (coord.x * loc1);
   // }
   auto* f32 = ty.f32();
   auto* vec4 = ty.vec4<float>();
-  auto* coord =
-      Param("coord", vec4,
-            {create<ast::BuiltinDecoration>(ast::Builtin::kFragCoord)});
+  auto* coord = Param(
+      "coord", vec4, {create<ast::BuiltinDecoration>(ast::Builtin::kPosition)});
   auto* loc1 = Param("loc1", f32, {create<ast::LocationDecoration>(1u)});
   auto* mul = Mul(Expr(MemberAccessor("coord", "x")), Expr("loc1"));
   auto* col = Var("col", f32, ast::StorageClass::kFunction, mul, {});
@@ -180,11 +179,12 @@
 TEST_F(BuilderTest, EntryPoint_SharedStruct) {
   // struct Interface {
   //   [[location(1)]] value : f32;
+  //   [[builtin(position)]] pos : vec4<f32>;
   // };
   //
   // [[stage(vertex)]]
   // fn vert_main() -> Interface {
-  //   return Interface(42.0);
+  //   return Interface(42.0, vec4<f32>());
   // }
   //
   // [[stage(fragment)]]
@@ -194,10 +194,13 @@
 
   auto* interface = Structure(
       "Interface",
-      {Member("value", ty.f32(),
-              ast::DecorationList{create<ast::LocationDecoration>(1u)})});
+      {
+          Member("value", ty.f32(), ast::DecorationList{Location(1u)}),
+          Member("pos", ty.vec4<f32>(),
+                 ast::DecorationList{Builtin(ast::Builtin::kPosition)}),
+      });
 
-  auto* vert_retval = Construct(interface, 42.f);
+  auto* vert_retval = Construct(interface, 42.f, Construct(ty.vec4<f32>()));
   Func("vert_main", ast::VariableList{}, interface,
        {create<ast::ReturnStatement>(vert_retval)},
        {create<ast::StageDecoration>(ast::PipelineStage::kVertex)});
@@ -214,63 +217,78 @@
 
   EXPECT_EQ(DumpBuilder(b), R"(OpCapability Shader
 OpMemoryModel Logical GLSL450
-OpEntryPoint Vertex %16 "vert_main" %1
-OpEntryPoint Fragment %25 "frag_main" %5 %7
-OpExecutionMode %25 OriginUpperLeft
-OpExecutionMode %25 DepthReplacing
+OpEntryPoint Vertex %23 "vert_main" %1 %5
+OpEntryPoint Fragment %32 "frag_main" %9 %11 %13
+OpExecutionMode %32 OriginUpperLeft
+OpExecutionMode %32 DepthReplacing
 OpName %1 "tint_symbol_1"
-OpName %5 "tint_symbol_3"
-OpName %7 "tint_symbol_6"
-OpName %10 "Interface"
-OpMemberName %10 0 "value"
-OpName %11 "tint_symbol_2"
-OpName %12 "tint_symbol"
-OpName %16 "vert_main"
-OpName %22 "tint_symbol_7"
-OpName %23 "tint_symbol_5"
-OpName %25 "frag_main"
+OpName %5 "tint_symbol_2"
+OpName %9 "tint_symbol_4"
+OpName %11 "tint_symbol_5"
+OpName %13 "tint_symbol_8"
+OpName %16 "Interface"
+OpMemberName %16 0 "value"
+OpMemberName %16 1 "pos"
+OpName %17 "tint_symbol_3"
+OpName %18 "tint_symbol"
+OpName %23 "vert_main"
+OpName %29 "tint_symbol_9"
+OpName %30 "tint_symbol_7"
+OpName %32 "frag_main"
 OpDecorate %1 Location 1
-OpDecorate %5 Location 1
-OpDecorate %7 BuiltIn FragDepth
-OpMemberDecorate %10 0 Offset 0
+OpDecorate %5 BuiltIn Position
+OpDecorate %9 Location 1
+OpDecorate %11 BuiltIn FragCoord
+OpDecorate %13 BuiltIn FragDepth
+OpMemberDecorate %16 0 Offset 0
+OpMemberDecorate %16 1 Offset 16
 %3 = OpTypeFloat 32
 %2 = OpTypePointer Output %3
 %4 = OpConstantNull %3
 %1 = OpVariable %2 Output %4
-%6 = OpTypePointer Input %3
-%5 = OpVariable %6 Input
-%7 = OpVariable %2 Output %4
-%9 = OpTypeVoid
-%10 = OpTypeStruct %3
-%8 = OpTypeFunction %9 %10
-%15 = OpTypeFunction %9
-%19 = OpConstant %3 42
-%20 = OpConstantComposite %10 %19
-%21 = OpTypeFunction %9 %3
-%11 = OpFunction %9 None %8
-%12 = OpFunctionParameter %10
-%13 = OpLabel
-%14 = OpCompositeExtract %3 %12 0
-OpStore %1 %14
+%7 = OpTypeVector %3 4
+%6 = OpTypePointer Output %7
+%8 = OpConstantNull %7
+%5 = OpVariable %6 Output %8
+%10 = OpTypePointer Input %3
+%9 = OpVariable %10 Input
+%12 = OpTypePointer Input %7
+%11 = OpVariable %12 Input
+%13 = OpVariable %2 Output %4
+%15 = OpTypeVoid
+%16 = OpTypeStruct %3 %7
+%14 = OpTypeFunction %15 %16
+%22 = OpTypeFunction %15
+%26 = OpConstant %3 42
+%27 = OpConstantComposite %16 %26 %8
+%28 = OpTypeFunction %15 %3
+%17 = OpFunction %15 None %14
+%18 = OpFunctionParameter %16
+%19 = OpLabel
+%20 = OpCompositeExtract %3 %18 0
+OpStore %1 %20
+%21 = OpCompositeExtract %7 %18 1
+OpStore %5 %21
 OpReturn
 OpFunctionEnd
-%16 = OpFunction %9 None %15
-%17 = OpLabel
-%18 = OpFunctionCall %9 %11 %20
-OpReturn
-OpFunctionEnd
-%22 = OpFunction %9 None %21
-%23 = OpFunctionParameter %3
+%23 = OpFunction %15 None %22
 %24 = OpLabel
-OpStore %7 %23
+%25 = OpFunctionCall %15 %17 %27
 OpReturn
 OpFunctionEnd
-%25 = OpFunction %9 None %15
-%26 = OpLabel
-%27 = OpLoad %3 %5
-%28 = OpCompositeConstruct %10 %27
-%30 = OpCompositeExtract %3 %28 0
-%29 = OpFunctionCall %9 %22 %30
+%29 = OpFunction %15 None %28
+%30 = OpFunctionParameter %3
+%31 = OpLabel
+OpStore %13 %30
+OpReturn
+OpFunctionEnd
+%32 = OpFunction %15 None %22
+%33 = OpLabel
+%34 = OpLoad %3 %9
+%35 = OpLoad %7 %11
+%36 = OpCompositeConstruct %16 %34 %35
+%38 = OpCompositeExtract %3 %36 0
+%37 = OpFunctionCall %15 %29 %38
 OpReturn
 OpFunctionEnd
 )");
diff --git a/src/writer/spirv/builder_global_variable_test.cc b/src/writer/spirv/builder_global_variable_test.cc
index 464b69c..3b63687 100644
--- a/src/writer/spirv/builder_global_variable_test.cc
+++ b/src/writer/spirv/builder_global_variable_test.cc
@@ -324,6 +324,7 @@
 
 struct BuiltinData {
   ast::Builtin builtin;
+  ast::StorageClass storage;
   SpvBuiltIn result;
 };
 inline std::ostream& operator<<(std::ostream& out, BuiltinData data) {
@@ -336,31 +337,43 @@
 
   spirv::Builder& b = Build();
 
-  EXPECT_EQ(b.ConvertBuiltin(params.builtin), params.result);
+  EXPECT_EQ(b.ConvertBuiltin(params.builtin, params.storage), params.result);
 }
 INSTANTIATE_TEST_SUITE_P(
     BuilderTest_Type,
     BuiltinDataTest,
     testing::Values(
-        BuiltinData{ast::Builtin::kNone, SpvBuiltInMax},
-        BuiltinData{ast::Builtin::kPosition, SpvBuiltInPosition},
+        BuiltinData{ast::Builtin::kNone, ast::StorageClass::kNone,
+                    SpvBuiltInMax},
+        BuiltinData{ast::Builtin::kPosition, ast::StorageClass::kInput,
+                    SpvBuiltInFragCoord},
+        BuiltinData{ast::Builtin::kPosition, ast::StorageClass::kOutput,
+                    SpvBuiltInPosition},
         BuiltinData{
             ast::Builtin::kVertexIndex,
+            ast::StorageClass::kInput,
             SpvBuiltInVertexIndex,
         },
-        BuiltinData{ast::Builtin::kInstanceIndex, SpvBuiltInInstanceIndex},
-        BuiltinData{ast::Builtin::kFrontFacing, SpvBuiltInFrontFacing},
-        BuiltinData{ast::Builtin::kFragCoord, SpvBuiltInFragCoord},
-        BuiltinData{ast::Builtin::kFragDepth, SpvBuiltInFragDepth},
-        BuiltinData{ast::Builtin::kLocalInvocationId,
+        BuiltinData{ast::Builtin::kInstanceIndex, ast::StorageClass::kInput,
+                    SpvBuiltInInstanceIndex},
+        BuiltinData{ast::Builtin::kFrontFacing, ast::StorageClass::kInput,
+                    SpvBuiltInFrontFacing},
+        BuiltinData{ast::Builtin::kFragCoord, ast::StorageClass::kInput,
+                    SpvBuiltInFragCoord},
+        BuiltinData{ast::Builtin::kFragDepth, ast::StorageClass::kOutput,
+                    SpvBuiltInFragDepth},
+        BuiltinData{ast::Builtin::kLocalInvocationId, ast::StorageClass::kInput,
                     SpvBuiltInLocalInvocationId},
         BuiltinData{ast::Builtin::kLocalInvocationIndex,
-                    SpvBuiltInLocalInvocationIndex},
+                    ast::StorageClass::kInput, SpvBuiltInLocalInvocationIndex},
         BuiltinData{ast::Builtin::kGlobalInvocationId,
-                    SpvBuiltInGlobalInvocationId},
-        BuiltinData{ast::Builtin::kSampleIndex, SpvBuiltInSampleId},
-        BuiltinData{ast::Builtin::kSampleMaskIn, SpvBuiltInSampleMask},
-        BuiltinData{ast::Builtin::kSampleMaskOut, SpvBuiltInSampleMask}));
+                    ast::StorageClass::kInput, SpvBuiltInGlobalInvocationId},
+        BuiltinData{ast::Builtin::kSampleIndex, ast::StorageClass::kInput,
+                    SpvBuiltInSampleId},
+        BuiltinData{ast::Builtin::kSampleMaskIn, ast::StorageClass::kInput,
+                    SpvBuiltInSampleMask},
+        BuiltinData{ast::Builtin::kSampleMaskOut, ast::StorageClass::kOutput,
+                    SpvBuiltInSampleMask}));
 
 TEST_F(BuilderTest, GlobalVar_DeclReadOnly) {
   // struct A {
diff --git a/src/writer/wgsl/generator_impl_function_test.cc b/src/writer/wgsl/generator_impl_function_test.cc
index 98f3a8d..05bc689 100644
--- a/src/writer/wgsl/generator_impl_function_test.cc
+++ b/src/writer/wgsl/generator_impl_function_test.cc
@@ -143,9 +143,8 @@
 
 TEST_F(WgslGeneratorImplTest, Emit_Function_EntryPoint_Parameters) {
   auto* vec4 = ty.vec4<f32>();
-  auto* coord =
-      Param("coord", vec4,
-            {create<ast::BuiltinDecoration>(ast::Builtin::kFragCoord)});
+  auto* coord = Param(
+      "coord", vec4, {create<ast::BuiltinDecoration>(ast::Builtin::kPosition)});
   auto* loc1 = Param("loc1", ty.f32(), {create<ast::LocationDecoration>(1u)});
   auto* func =
       Func("frag_main", ast::VariableList{coord, loc1}, ty.void_(),
@@ -160,7 +159,7 @@
 
   ASSERT_TRUE(gen.EmitFunction(func));
   EXPECT_EQ(gen.result(), R"(  [[stage(fragment)]]
-  fn frag_main([[builtin(frag_coord)]] coord : vec4<f32>, [[location(1)]] loc1 : f32) {
+  fn frag_main([[builtin(position)]] coord : vec4<f32>, [[location(1)]] loc1 : f32) {
   }
 )");
 }