Require vertex shaders to return builtin(position)

Fixup many tests that were just returning void.

Change-Id: Ic93db5b187c679dc1c24a356b48a64e41ba9a823
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/48560
Commit-Queue: James Price <jrprice@google.com>
Auto-Submit: James Price <jrprice@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
diff --git a/src/inspector/inspector_test.cc b/src/inspector/inspector_test.cc
index 7f287d6..a14baa1 100644
--- a/src/inspector/inspector_test.cc
+++ b/src/inspector/inspector_test.cc
@@ -771,7 +771,7 @@
 TEST_F(InspectorGetEntryPointTest, OneEntryPoint) {
   MakeEmptyBodyFunction(
       "foo", ast::DecorationList{
-                 create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+                 create<ast::StageDecoration>(ast::PipelineStage::kFragment),
              });
 
   // TODO(dsinclair): Update to run the namer transform when available.
@@ -784,13 +784,13 @@
   ASSERT_EQ(1u, result.size());
   EXPECT_EQ("foo", result[0].name);
   EXPECT_EQ("foo", result[0].remapped_name);
-  EXPECT_EQ(ast::PipelineStage::kVertex, result[0].stage);
+  EXPECT_EQ(ast::PipelineStage::kFragment, result[0].stage);
 }
 
 TEST_F(InspectorGetEntryPointTest, MultipleEntryPoints) {
   MakeEmptyBodyFunction(
       "foo", ast::DecorationList{
-                 create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+                 create<ast::StageDecoration>(ast::PipelineStage::kFragment),
              });
 
   MakeEmptyBodyFunction(
@@ -808,7 +808,7 @@
   ASSERT_EQ(2u, result.size());
   EXPECT_EQ("foo", result[0].name);
   EXPECT_EQ("foo", result[0].remapped_name);
-  EXPECT_EQ(ast::PipelineStage::kVertex, result[0].stage);
+  EXPECT_EQ(ast::PipelineStage::kFragment, result[0].stage);
   EXPECT_EQ("bar", result[1].name);
   EXPECT_EQ("bar", result[1].remapped_name);
   EXPECT_EQ(ast::PipelineStage::kCompute, result[1].stage);
@@ -820,7 +820,7 @@
   MakeCallerBodyFunction(
       "foo", {"func"},
       ast::DecorationList{
-          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kCompute),
       });
 
   MakeCallerBodyFunction(
@@ -839,7 +839,7 @@
   ASSERT_EQ(2u, result.size());
   EXPECT_EQ("foo", result[0].name);
   EXPECT_EQ("foo", result[0].remapped_name);
-  EXPECT_EQ(ast::PipelineStage::kVertex, result[0].stage);
+  EXPECT_EQ(ast::PipelineStage::kCompute, result[0].stage);
   EXPECT_EQ("bar", result[1].name);
   EXPECT_EQ("bar", result[1].remapped_name);
   EXPECT_EQ(ast::PipelineStage::kFragment, result[1].stage);
@@ -848,7 +848,7 @@
 TEST_F(InspectorGetEntryPointTest, DefaultWorkgroupSize) {
   MakeEmptyBodyFunction(
       "foo", ast::DecorationList{
-                 create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+                 create<ast::StageDecoration>(ast::PipelineStage::kCompute),
              });
 
   Inspector& inspector = Build();
@@ -890,7 +890,7 @@
   MakeCallerBodyFunction(
       "foo", {"func"},
       ast::DecorationList{
-          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kFragment),
       });
 
   Inspector& inspector = Build();
@@ -1172,7 +1172,7 @@
   MakeInOutVariableBodyFunction(
       "foo", {{"in_var", "out_var"}},
       ast::DecorationList{
-          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kFragment),
       });
 
   Inspector& inspector = Build();
@@ -1204,7 +1204,7 @@
   MakeCallerBodyFunction(
       "foo", {"func"},
       ast::DecorationList{
-          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kFragment),
       });
 
   Inspector& inspector = Build();
@@ -1236,7 +1236,7 @@
   MakeInOutVariableCallerBodyFunction(
       "foo", "func", {{"in_var", "out_var"}},
       ast::DecorationList{
-          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kFragment),
       });
 
   Inspector& inspector = Build();
@@ -1266,7 +1266,7 @@
   MakeInOutVariableBodyFunction(
       "foo", {{"in_var", "out_var"}, {"in2_var", "out2_var"}},
       ast::DecorationList{
-          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kFragment),
       });
 
   Inspector& inspector = Build();
@@ -1307,7 +1307,7 @@
   MakeCallerBodyFunction(
       "foo", {"func"},
       ast::DecorationList{
-          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kFragment),
       });
 
   Inspector& inspector = Build();
@@ -1345,7 +1345,7 @@
   MakeInOutVariableBodyFunction(
       "foo", {{"in_var", "out2_var"}},
       ast::DecorationList{
-          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kFragment),
       });
 
   MakeInOutVariableBodyFunction(
@@ -1405,7 +1405,7 @@
   MakeInOutVariableCallerBodyFunction(
       "foo", "func", {{"in_var", "out_var"}},
       ast::DecorationList{
-          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kFragment),
       });
 
   MakeCallerBodyFunction(
@@ -1476,7 +1476,7 @@
   MakeCallerBodyFunction(
       "foo", {"func"},
       ast::DecorationList{
-          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kFragment),
       });
 
   // TODO(dsinclair): Update to run the namer transform when available.
@@ -1670,7 +1670,7 @@
   MakeCallerBodyFunction(
       "ep_func", {},
       ast::DecorationList{
-          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kFragment),
       });
 
   Inspector& inspector = Build();
@@ -1739,7 +1739,7 @@
       {"ub_func", "sb_func", "rosb_func", "s_func", "cs_func", "st_func",
        "rost_func"},
       ast::DecorationList{
-          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kFragment),
       });
 
   Inspector& inspector = Build();
@@ -1812,7 +1812,7 @@
   MakeCallerBodyFunction(
       "ep_func", {"ub_func"},
       ast::DecorationList{
-          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kFragment),
       });
 
   Inspector& inspector = Build();
@@ -1837,7 +1837,7 @@
   MakeCallerBodyFunction(
       "ep_func", {"ub_func"},
       ast::DecorationList{
-          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kFragment),
       });
 
   Inspector& inspector = Build();
@@ -1857,7 +1857,7 @@
   MakeCallerBodyFunction(
       "ep_func", {"ub_func"},
       ast::DecorationList{
-          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kFragment),
       });
 
   Inspector& inspector = Build();
@@ -1885,7 +1885,7 @@
   MakeCallerBodyFunction(
       "ep_func", {"ub_func"},
       ast::DecorationList{
-          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kFragment),
       });
 
   Inspector& inspector = Build();
@@ -1913,7 +1913,7 @@
   MakeCallerBodyFunction(
       "ep_func", {"ub_func"},
       ast::DecorationList{
-          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kFragment),
       });
 
   Inspector& inspector = Build();
@@ -1955,7 +1955,7 @@
                           FuncCall("ub_baz_func"),
                           create<ast::ReturnStatement>()},
        ast::DecorationList{
-           create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+           create<ast::StageDecoration>(ast::PipelineStage::kFragment),
        });
 
   Inspector& inspector = Build();
@@ -1999,7 +1999,7 @@
   MakeCallerBodyFunction(
       "ep_func", {"ub_func"},
       ast::DecorationList{
-          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kFragment),
       });
 
   Inspector& inspector = Build();
@@ -2028,7 +2028,7 @@
   MakeCallerBodyFunction(
       "ep_func", {"sb_func"},
       ast::DecorationList{
-          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kFragment),
       });
 
   Inspector& inspector = Build();
@@ -2058,7 +2058,7 @@
   MakeCallerBodyFunction(
       "ep_func", {"sb_func"},
       ast::DecorationList{
-          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kFragment),
       });
 
   Inspector& inspector = Build();
@@ -2105,7 +2105,7 @@
            create<ast::ReturnStatement>(),
        },
        ast::DecorationList{
-           create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+           create<ast::StageDecoration>(ast::PipelineStage::kFragment),
        });
 
   Inspector& inspector = Build();
@@ -2148,7 +2148,7 @@
   MakeCallerBodyFunction(
       "ep_func", {"sb_func"},
       ast::DecorationList{
-          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kFragment),
       });
 
   Inspector& inspector = Build();
@@ -2177,7 +2177,7 @@
   MakeCallerBodyFunction(
       "ep_func", {"sb_func"},
       ast::DecorationList{
-          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kFragment),
       });
 
   Inspector& inspector = Build();
@@ -2207,7 +2207,7 @@
   MakeCallerBodyFunction(
       "ep_func", {"sb_func"},
       ast::DecorationList{
-          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kFragment),
       });
 
   Inspector& inspector = Build();
@@ -2236,7 +2236,7 @@
   MakeCallerBodyFunction(
       "ep_func", {"sb_func"},
       ast::DecorationList{
-          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kFragment),
       });
 
   Inspector& inspector = Build();
@@ -2258,7 +2258,7 @@
   MakeCallerBodyFunction(
       "ep_func", {"sb_func"},
       ast::DecorationList{
-          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kFragment),
       });
 
   Inspector& inspector = Build();
@@ -2306,7 +2306,7 @@
            create<ast::ReturnStatement>(),
        },
        ast::DecorationList{
-           create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+           create<ast::StageDecoration>(ast::PipelineStage::kFragment),
        });
 
   Inspector& inspector = Build();
@@ -2349,7 +2349,7 @@
   MakeCallerBodyFunction(
       "ep_func", {"sb_func"},
       ast::DecorationList{
-          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kFragment),
       });
 
   Inspector& inspector = Build();
@@ -2379,7 +2379,7 @@
   MakeCallerBodyFunction(
       "ep_func", {"sb_func"},
       ast::DecorationList{
-          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kFragment),
       });
 
   Inspector& inspector = Build();
@@ -2408,7 +2408,7 @@
   MakeCallerBodyFunction(
       "ep_func", {"sb_func"},
       ast::DecorationList{
-          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kFragment),
       });
 
   Inspector& inspector = Build();
@@ -2428,7 +2428,7 @@
   MakeSamplerReferenceBodyFunction(
       "ep", "foo_texture", "foo_sampler", "foo_coords", ty.f32(),
       ast::DecorationList{
-          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kFragment),
       });
 
   Inspector& inspector = Build();
@@ -2444,9 +2444,10 @@
 
 TEST_F(InspectorGetSamplerResourceBindingsTest, NoSampler) {
   MakeEmptyBodyFunction(
-      "ep_func", ast::DecorationList{
-                     create<ast::StageDecoration>(ast::PipelineStage::kVertex),
-                 });
+      "ep_func",
+      ast::DecorationList{
+          create<ast::StageDecoration>(ast::PipelineStage::kFragment),
+      });
 
   Inspector& inspector = Build();
 
@@ -2469,7 +2470,7 @@
   MakeCallerBodyFunction(
       "ep_func", {"foo_func"},
       ast::DecorationList{
-          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kFragment),
       });
 
   Inspector& inspector = Build();
@@ -2493,7 +2494,7 @@
   MakeSamplerReferenceBodyFunction(
       "ep", "foo_texture", "foo_sampler", "foo_coords", ty.f32(),
       ast::DecorationList{
-          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kFragment),
       });
 
   Inspector& inspector = Build();
@@ -2512,7 +2513,7 @@
   MakeComparisonSamplerReferenceBodyFunction(
       "ep", "foo_texture", "foo_sampler", "foo_coords", "foo_depth", ty.f32(),
       ast::DecorationList{
-          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kFragment),
       });
 
   Inspector& inspector = Build();
@@ -2533,7 +2534,7 @@
   MakeComparisonSamplerReferenceBodyFunction(
       "ep", "foo_texture", "foo_sampler", "foo_coords", "foo_depth", ty.f32(),
       ast::DecorationList{
-          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kFragment),
       });
 
   Inspector& inspector = Build();
@@ -2550,9 +2551,10 @@
 
 TEST_F(InspectorGetComparisonSamplerResourceBindingsTest, NoSampler) {
   MakeEmptyBodyFunction(
-      "ep_func", ast::DecorationList{
-                     create<ast::StageDecoration>(ast::PipelineStage::kVertex),
-                 });
+      "ep_func",
+      ast::DecorationList{
+          create<ast::StageDecoration>(ast::PipelineStage::kFragment),
+      });
 
   Inspector& inspector = Build();
 
@@ -2576,7 +2578,7 @@
   MakeCallerBodyFunction(
       "ep_func", {"foo_func"},
       ast::DecorationList{
-          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kFragment),
       });
 
   Inspector& inspector = Build();
@@ -2601,7 +2603,7 @@
   MakeComparisonSamplerReferenceBodyFunction(
       "ep", "foo_texture", "foo_sampler", "foo_coords", "foo_depth", ty.f32(),
       ast::DecorationList{
-          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kFragment),
       });
 
   Inspector& inspector = Build();
@@ -2620,7 +2622,7 @@
   MakeSamplerReferenceBodyFunction(
       "ep", "foo_texture", "foo_sampler", "foo_coords", ty.f32(),
       ast::DecorationList{
-          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kFragment),
       });
 
   Inspector& inspector = Build();
@@ -2634,7 +2636,7 @@
 TEST_F(InspectorGetSampledTextureResourceBindingsTest, Empty) {
   MakeEmptyBodyFunction(
       "foo", ast::DecorationList{
-                 create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+                 create<ast::StageDecoration>(ast::PipelineStage::kFragment),
              });
 
   Inspector& inspector = Build();
@@ -2657,7 +2659,7 @@
       "ep", "foo_texture", "foo_sampler", "foo_coords",
       GetBaseType(GetParam().sampled_kind),
       ast::DecorationList{
-          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kFragment),
       });
 
   Inspector& inspector = Build();
@@ -2716,7 +2718,7 @@
       "ep", "foo_texture", "foo_sampler", "foo_coords", "foo_array_index",
       GetBaseType(GetParam().sampled_kind),
       ast::DecorationList{
-          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kFragment),
       });
 
   Inspector& inspector = Build();
@@ -2761,7 +2763,7 @@
                                            "foo_coords", "foo_sample_index")),
        },
        ast::DecorationList{
-           create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+           create<ast::StageDecoration>(ast::PipelineStage::kFragment),
        });
 
   Inspector& inspector = Build();
@@ -2805,7 +2807,7 @@
 TEST_F(InspectorGetMultisampledArrayTextureResourceBindingsTest, Empty) {
   MakeEmptyBodyFunction(
       "foo", ast::DecorationList{
-                 create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+                 create<ast::StageDecoration>(ast::PipelineStage::kFragment),
              });
 
   Inspector& inspector = Build();
@@ -2830,7 +2832,7 @@
       "ep", "foo_texture", "foo_sampler", "foo_coords", "foo_array_index",
       GetBaseType(GetParam().sampled_kind),
       ast::DecorationList{
-          create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+          create<ast::StageDecoration>(ast::PipelineStage::kFragment),
       });
 
   Inspector& inspector = Build();
@@ -2867,7 +2869,7 @@
 TEST_F(InspectorGetStorageTextureResourceBindingsTest, Empty) {
   MakeEmptyBodyFunction(
       "ep", ast::DecorationList{
-                create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+                create<ast::StageDecoration>(ast::PipelineStage::kFragment),
             });
 
   Inspector& inspector = Build();
@@ -2924,7 +2926,7 @@
   MakeStorageTextureBodyFunction(
       "ep", "st_var", dim_type,
       ast::DecorationList{
-          create<ast::StageDecoration>(ast::PipelineStage::kVertex)});
+          create<ast::StageDecoration>(ast::PipelineStage::kFragment)});
 
   Inspector& inspector = Build();
 
@@ -3026,7 +3028,7 @@
                Call("textureDimensions", "dt", "dt_level")),
        },
        ast::DecorationList{
-           create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+           create<ast::StageDecoration>(ast::PipelineStage::kFragment),
        });
 
   Inspector& inspector = Build();
diff --git a/src/reader/wgsl/parser_test.cc b/src/reader/wgsl/parser_test.cc
index cd01174..00ecea4 100644
--- a/src/reader/wgsl/parser_test.cc
+++ b/src/reader/wgsl/parser_test.cc
@@ -36,7 +36,7 @@
   Source::File file("test.wgsl", R"(
 [[location(0)]] var<out> gl_FragColor : vec4<f32>;
 
-[[stage(vertex)]]
+[[stage(fragment)]]
 fn main() {
   gl_FragColor = vec4<f32>(.4, .2, .3, 1.);
 }
diff --git a/src/resolver/decoration_validation_test.cc b/src/resolver/decoration_validation_test.cc
index a58f544..a927c46 100644
--- a/src/resolver/decoration_validation_test.cc
+++ b/src/resolver/decoration_validation_test.cc
@@ -94,7 +94,7 @@
   Func("main", ast::VariableList{}, ty.f32(),
        ast::StatementList{create<ast::ReturnStatement>(Expr(1.f))},
        ast::DecorationList{
-           create<ast::StageDecoration>(ast::PipelineStage::kVertex)},
+           create<ast::StageDecoration>(ast::PipelineStage::kCompute)},
        ast::DecorationList{createDecoration({}, *this, params.kind)});
 
   if (params.should_pass) {
diff --git a/src/resolver/entry_point_validation_test.cc b/src/resolver/entry_point_validation_test.cc
index 4b31d1a..6d9f342 100644
--- a/src/resolver/entry_point_validation_test.cc
+++ b/src/resolver/entry_point_validation_test.cc
@@ -32,7 +32,7 @@
   // [[stage(vertex)]]
   // fn main() -> [[location(0)]] f32 { return 1.0; }
   Func(Source{{12, 34}}, "main", {}, ty.f32(), {Return(Expr(1.0f))},
-       {Stage(ast::PipelineStage::kVertex)}, {Location(0)});
+       {Stage(ast::PipelineStage::kFragment)}, {Location(0)});
 
   EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
@@ -500,5 +500,17 @@
 12:34 note: while analysing entry point main)");
 }
 
+TEST_F(ResolverEntryPointValidationTest, VertexShaderMustReturnPosition) {
+  // [[stage(vertex)]]
+  // fn main() {}
+  Func(Source{{12, 34}}, "main", {}, ty.void_(), {},
+       {Stage(ast::PipelineStage::kVertex)});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: a vertex shader must include the 'position' builtin "
+            "in its return type");
+}
+
 }  // namespace
 }  // namespace tint
diff --git a/src/resolver/function_validation_test.cc b/src/resolver/function_validation_test.cc
index daf14ad..f7ccd40 100644
--- a/src/resolver/function_validation_test.cc
+++ b/src/resolver/function_validation_test.cc
@@ -47,7 +47,6 @@
 
 TEST_F(ResolverFunctionValidationTest,
        VoidFunctionEndWithoutReturnStatement_Pass) {
-  // [[stage(vertex)]]
   // fn func { var a:i32 = 2; }
   auto* var = Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2));
 
@@ -55,9 +54,6 @@
        ty.void_(),
        ast::StatementList{
            create<ast::VariableDeclStatement>(var),
-       },
-       ast::DecorationList{
-           create<ast::StageDecoration>(ast::PipelineStage::kVertex),
        });
 
   EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -82,14 +78,10 @@
 
 TEST_F(ResolverFunctionValidationTest,
        VoidFunctionEndWithoutReturnStatementEmptyBody_Pass) {
-  // [[stage(vertex)]]
   // fn func {}
 
   Func(Source{Source::Location{12, 34}}, "func", ast::VariableList{},
-       ty.void_(), ast::StatementList{},
-       ast::DecorationList{
-           create<ast::StageDecoration>(ast::PipelineStage::kVertex),
-       });
+       ty.void_(), ast::StatementList{});
 
   EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
@@ -109,15 +101,11 @@
 
 TEST_F(ResolverFunctionValidationTest,
        FunctionTypeMustMatchReturnStatementType_Pass) {
-  // [[stage(vertex)]]
   // fn func { return; }
 
   Func("func", ast::VariableList{}, ty.void_(),
        ast::StatementList{
            create<ast::ReturnStatement>(),
-       },
-       ast::DecorationList{
-           create<ast::StageDecoration>(ast::PipelineStage::kVertex),
        });
 
   EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -148,10 +136,6 @@
                                         Expr(2.f)),
        },
        ast::DecorationList{});
-  Func("main", ast::VariableList{}, ty.void_(), ast::StatementList{},
-       ast::DecorationList{
-           create<ast::StageDecoration>(ast::PipelineStage::kVertex),
-       });
 
   EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
@@ -183,10 +167,6 @@
                                         Expr(2.f)),
        },
        ast::DecorationList{});
-  Func("main", ast::VariableList{}, ty.void_(), ast::StatementList{},
-       ast::DecorationList{
-           create<ast::StageDecoration>(ast::PipelineStage::kVertex),
-       });
 
   EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
@@ -202,10 +182,6 @@
                                         Expr(2u)),
        },
        ast::DecorationList{});
-  Func("main", ast::VariableList{}, ty.void_(), ast::StatementList{},
-       ast::DecorationList{
-           create<ast::StageDecoration>(ast::PipelineStage::kVertex),
-       });
 
   EXPECT_FALSE(r()->Resolve());
   EXPECT_EQ(r()->error(),
diff --git a/src/resolver/resolver.cc b/src/resolver/resolver.cc
index 4038df9..e5bf012 100644
--- a/src/resolver/resolver.cc
+++ b/src/resolver/resolver.cc
@@ -521,7 +521,8 @@
   return ValidateVariable(param);
 }
 
-bool Resolver::ValidateFunction(const ast::Function* func) {
+bool Resolver::ValidateFunction(const ast::Function* func,
+                                const FunctionInfo* info) {
   if (symbol_to_function_.find(func->symbol()) != symbol_to_function_.end()) {
     diagnostics_.add_error("v-0016",
                            "function names must be unique '" +
@@ -564,7 +565,7 @@
   }
 
   if (func->IsEntryPoint()) {
-    if (!ValidateEntryPoint(func)) {
+    if (!ValidateEntryPoint(func, info)) {
       return false;
     }
   }
@@ -572,7 +573,8 @@
   return true;
 }
 
-bool Resolver::ValidateEntryPoint(const ast::Function* func) {
+bool Resolver::ValidateEntryPoint(const ast::Function* func,
+                                  const FunctionInfo* info) {
   auto stage_deco_count = 0;
   for (auto* deco : func->decorations()) {
     if (deco->Is<ast::StageDecoration>()) {
@@ -750,9 +752,13 @@
     }
   }
 
+  // Clear IO sets after parameter validation. Builtin and location attributes
+  // in return types should be validated independently from those used in
+  // parameters.
+  builtins.clear();
+  locations.clear();
+
   if (!func->return_type()->Is<sem::Void>()) {
-    builtins.clear();
-    locations.clear();
     if (!validate_entry_point_decorations(func->return_type_decorations(),
                                           func->return_type(), func->source(),
                                           ParamOrRetType::kReturnType)) {
@@ -760,6 +766,28 @@
     }
   }
 
+  if (func->pipeline_stage() == ast::PipelineStage::kVertex &&
+      builtins.count(ast::Builtin::kPosition) == 0) {
+    // Check module-scope variables, as the SPIR-V sanitizer generates these.
+    bool found = false;
+    for (auto* var : info->referenced_module_vars) {
+      if (auto* builtin = ast::GetDecoration<ast::BuiltinDecoration>(
+              var->declaration->decorations())) {
+        if (builtin->value() == ast::Builtin::kPosition) {
+          found = true;
+          break;
+        }
+      }
+    }
+    if (!found) {
+      diagnostics_.add_error(
+          "a vertex shader must include the 'position' builtin in its return "
+          "type",
+          func->source());
+      return false;
+    }
+  }
+
   return true;
 }
 
@@ -863,7 +891,7 @@
     Mark(deco);
   }
 
-  if (!ValidateFunction(func)) {
+  if (!ValidateFunction(func, func_info)) {
     return false;
   }
 
diff --git a/src/resolver/resolver.h b/src/resolver/resolver.h
index c352f42..f0aef7d 100644
--- a/src/resolver/resolver.h
+++ b/src/resolver/resolver.h
@@ -238,8 +238,8 @@
   // Each return true on success, false on failure.
   bool ValidateAssignment(const ast::AssignmentStatement* a);
   bool ValidateBinary(ast::BinaryExpression* expr);
-  bool ValidateEntryPoint(const ast::Function* func);
-  bool ValidateFunction(const ast::Function* func);
+  bool ValidateEntryPoint(const ast::Function* func, const FunctionInfo* info);
+  bool ValidateFunction(const ast::Function* func, const FunctionInfo* info);
   bool ValidateGlobalVariable(const VariableInfo* var);
   bool ValidateMatrixConstructor(const sem::Matrix* matrix_type,
                                  const ast::ExpressionList& values);
diff --git a/src/resolver/resolver_test.cc b/src/resolver/resolver_test.cc
index b00a199..423c9f5 100644
--- a/src/resolver/resolver_test.cc
+++ b/src/resolver/resolver_test.cc
@@ -1532,7 +1532,7 @@
                create<ast::AssignmentStatement>(Expr("call_b"), Call("b")),
            },
            ast::DecorationList{
-               create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+               create<ast::StageDecoration>(ast::PipelineStage::kCompute),
            });
 
   auto* ep_2 =
@@ -1541,7 +1541,7 @@
                create<ast::AssignmentStatement>(Expr("call_c"), Call("c")),
            },
            ast::DecorationList{
-               create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+               create<ast::StageDecoration>(ast::PipelineStage::kCompute),
            });
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -1620,7 +1620,7 @@
            create<ast::CallStatement>(Call(fn_b(0))),
        },
        {
-           create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+           create<ast::StageDecoration>(ast::PipelineStage::kCompute),
        });
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
diff --git a/src/resolver/struct_pipeline_stage_use_test.cc b/src/resolver/struct_pipeline_stage_use_test.cc
index 44572df..81f3016 100644
--- a/src/resolver/struct_pipeline_stage_use_test.cc
+++ b/src/resolver/struct_pipeline_stage_use_test.cc
@@ -65,11 +65,12 @@
 }
 
 TEST_F(ResolverPipelineStageUseTest, StructUsedAsVertexShaderParam) {
-  auto* s = Structure(
-      "S", {Member("a", ty.f32(), {create<ast::LocationDecoration>(0)})});
+  auto* s = Structure("S", {Member("a", ty.f32(), {Location(0)})});
 
-  Func("main", {Param("param", s)}, ty.void_(), {},
-       {create<ast::StageDecoration>(ast::PipelineStage::kVertex)});
+  Func("main", {Param("param", s)}, ty.vec4<f32>(),
+       {Return(Construct(ty.vec4<f32>()))},
+       {Stage(ast::PipelineStage::kVertex)},
+       {Builtin(ast::Builtin::kPosition)});
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -81,10 +82,10 @@
 
 TEST_F(ResolverPipelineStageUseTest, StructUsedAsVertexShaderReturnType) {
   auto* s = Structure(
-      "S", {Member("a", ty.f32(), {create<ast::LocationDecoration>(0)})});
+      "S", {Member("a", ty.f32(), {Builtin(ast::Builtin::kPosition)})});
 
   Func("main", {}, s, {Return(Construct(s, Expr(0.f)))},
-       {create<ast::StageDecoration>(ast::PipelineStage::kVertex)});
+       {Stage(ast::PipelineStage::kVertex)});
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
@@ -142,13 +143,13 @@
 
 TEST_F(ResolverPipelineStageUseTest, StructUsedMultipleStages) {
   auto* s = Structure(
-      "S", {Member("a", ty.f32(), {create<ast::LocationDecoration>(0)})});
+      "S", {Member("a", ty.f32(), {Builtin(ast::Builtin::kPosition)})});
 
   Func("vert_main", {Param("param", s)}, s, {Return(Construct(s, Expr(0.f)))},
-       {create<ast::StageDecoration>(ast::PipelineStage::kVertex)});
+       {Stage(ast::PipelineStage::kVertex)});
 
   Func("frag_main", {Param("param", s)}, ty.void_(), {},
-       {create<ast::StageDecoration>(ast::PipelineStage::kFragment)});
+       {Stage(ast::PipelineStage::kFragment)});
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
diff --git a/src/resolver/type_validation_test.cc b/src/resolver/type_validation_test.cc
index b80bfb5..95a3cd7 100644
--- a/src/resolver/type_validation_test.cc
+++ b/src/resolver/type_validation_test.cc
@@ -112,9 +112,7 @@
 
   auto* var = Var("a", ty.f32(), ast::StorageClass::kNone, Expr(2.0f));
 
-  Func("my_func", ast::VariableList{}, ty.void_(), {Decl(var)},
-       ast::DecorationList{
-           create<ast::StageDecoration>(ast::PipelineStage::kVertex)});
+  Func("my_func", ast::VariableList{}, ty.void_(), {Decl(var)});
 
   Global("a", ty.f32(), ast::StorageClass::kPrivate, Expr(2.1f));
 
@@ -277,9 +275,6 @@
        ast::StatementList{
            create<ast::VariableDeclStatement>(Source{{13, 34}}, var1),
            create<ast::ReturnStatement>(),
-       },
-       ast::DecorationList{
-           create<ast::StageDecoration>(ast::PipelineStage::kVertex),
        });
 
   EXPECT_TRUE(r()->Resolve()) << r()->error();
diff --git a/src/resolver/validation_test.cc b/src/resolver/validation_test.cc
index b67eea4..f460bff 100644
--- a/src/resolver/validation_test.cc
+++ b/src/resolver/validation_test.cc
@@ -280,9 +280,6 @@
            create<ast::AssignmentStatement>(Source{Source::Location{12, 34}},
                                             Expr("global_var"), Expr(3.14f)),
            create<ast::ReturnStatement>(),
-       },
-       ast::DecorationList{
-           create<ast::StageDecoration>(ast::PipelineStage::kVertex),
        });
 
   EXPECT_TRUE(r()->Resolve()) << r()->error();
diff --git a/src/transform/calculate_array_length_test.cc b/src/transform/calculate_array_length_test.cc
index 7aefa51..36bbfba 100644
--- a/src/transform/calculate_array_length_test.cc
+++ b/src/transform/calculate_array_length_test.cc
@@ -32,7 +32,7 @@
 
 var<storage> sb : [[access(read)]] SB;
 
-[[stage(vertex)]]
+[[stage(compute)]]
 fn main() {
   var len : u32 = arrayLength(sb.arr);
 }
@@ -50,7 +50,7 @@
 
 var<storage> sb : [[access(read)]] SB;
 
-[[stage(vertex)]]
+[[stage(compute)]]
 fn main() {
   var tint_symbol_1 : u32 = 0u;
   tint_symbol(sb, tint_symbol_1);
@@ -74,7 +74,7 @@
 
 var<storage> sb : [[access(read)]] SB;
 
-[[stage(vertex)]]
+[[stage(compute)]]
 fn main() {
   var a : u32 = arrayLength(sb.arr);
   var b : u32 = arrayLength(sb.arr);
@@ -94,7 +94,7 @@
 
 var<storage> sb : [[access(read)]] SB;
 
-[[stage(vertex)]]
+[[stage(compute)]]
 fn main() {
   var tint_symbol_1 : u32 = 0u;
   tint_symbol(sb, tint_symbol_1);
@@ -121,7 +121,7 @@
 
 var<storage> sb : [[access(read)]] SB;
 
-[[stage(vertex)]]
+[[stage(compute)]]
 fn main() {
   var len : u32 = arrayLength(sb.arr);
 }
@@ -140,7 +140,7 @@
 
 var<storage> sb : [[access(read)]] SB;
 
-[[stage(vertex)]]
+[[stage(compute)]]
 fn main() {
   var tint_symbol_1 : u32 = 0u;
   tint_symbol(sb, tint_symbol_1);
@@ -164,7 +164,7 @@
 
 var<storage> sb : [[access(read)]] SB;
 
-[[stage(vertex)]]
+[[stage(compute)]]
 fn main() {
   if (true) {
     var len : u32 = arrayLength(sb.arr);
@@ -188,7 +188,7 @@
 
 var<storage> sb : [[access(read)]] SB;
 
-[[stage(vertex)]]
+[[stage(compute)]]
 fn main() {
   if (true) {
     var tint_symbol_1 : u32 = 0u;
@@ -229,7 +229,7 @@
 
 var<storage> sb2 : [[access(read)]] SB2;
 
-[[stage(vertex)]]
+[[stage(compute)]]
 fn main() {
   var len1 : u32 = arrayLength(sb1.arr1);
   var len2 : u32 = arrayLength(sb2.arr2);
@@ -260,7 +260,7 @@
 
 var<storage> sb2 : [[access(read)]] SB2;
 
-[[stage(vertex)]]
+[[stage(compute)]]
 fn main() {
   var tint_symbol_1 : u32 = 0u;
   tint_symbol(sb1, tint_symbol_1);
diff --git a/src/transform/emit_vertex_point_size_test.cc b/src/transform/emit_vertex_point_size_test.cc
index 53b4d52..f420115 100644
--- a/src/transform/emit_vertex_point_size_test.cc
+++ b/src/transform/emit_vertex_point_size_test.cc
@@ -28,8 +28,9 @@
 }
 
 [[stage(vertex)]]
-fn entry() {
+fn entry() -> [[builtin(position)]] vec4<f32> {
   var builtin_assignments_should_happen_before_this : f32;
+  return vec4<f32>();
 }
 
 fn non_entry_b() {
@@ -43,42 +44,10 @@
 }
 
 [[stage(vertex)]]
-fn entry() {
+fn entry() -> [[builtin(position)]] vec4<f32> {
   tint_pointsize = 1.0;
   var builtin_assignments_should_happen_before_this : f32;
-}
-
-fn non_entry_b() {
-}
-)";
-
-  auto got = Run<EmitVertexPointSize>(src);
-
-  EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(EmitVertexPointSizeTest, VertexStageEmpty) {
-  auto* src = R"(
-fn non_entry_a() {
-}
-
-[[stage(vertex)]]
-fn entry() {
-}
-
-fn non_entry_b() {
-}
-)";
-
-  auto* expect = R"(
-[[builtin(pointsize)]] var<out> tint_pointsize : f32;
-
-fn non_entry_a() {
-}
-
-[[stage(vertex)]]
-fn entry() {
-  tint_pointsize = 1.0;
+  return vec4<f32>();
 }
 
 fn non_entry_b() {
@@ -119,8 +88,9 @@
 TEST_F(EmitVertexPointSizeTest, AttemptSymbolCollision) {
   auto* src = R"(
 [[stage(vertex)]]
-fn entry() {
+fn entry() -> [[builtin(position)]] vec4<f32> {
   var tint_pointsize : f32;
+  return vec4<f32>();
 }
 )";
 
@@ -128,9 +98,10 @@
 [[builtin(pointsize)]] var<out> tint_pointsize_1 : f32;
 
 [[stage(vertex)]]
-fn entry() {
+fn entry() -> [[builtin(position)]] vec4<f32> {
   tint_pointsize_1 = 1.0;
   var tint_pointsize : f32;
+  return vec4<f32>();
 }
 )";
 
diff --git a/src/transform/first_index_offset_test.cc b/src/transform/first_index_offset_test.cc
index 5d08be5..09221d3 100644
--- a/src/transform/first_index_offset_test.cc
+++ b/src/transform/first_index_offset_test.cc
@@ -52,8 +52,9 @@
 }
 
 [[stage(vertex)]]
-fn entry([[builtin(vertex_index)]] vert_idx : u32) {
+fn entry([[builtin(vertex_index)]] vert_idx : u32) -> [[builtin(position)]] vec4<f32> {
   test(vert_idx);
+  return vec4<f32>();
 }
 )";
 
@@ -70,8 +71,9 @@
 }
 
 [[stage(vertex)]]
-fn entry([[builtin(vertex_index)]] vert_idx : u32) {
+fn entry([[builtin(vertex_index)]] vert_idx : u32) -> [[builtin(position)]] vec4<f32> {
   test((vert_idx + tint_symbol_1.first_vertex_index));
+  return vec4<f32>();
 }
 )";
 
@@ -97,8 +99,9 @@
 }
 
 [[stage(vertex)]]
-fn entry([[builtin(instance_index)]] inst_idx : u32) {
+fn entry([[builtin(instance_index)]] inst_idx : u32) -> [[builtin(position)]] vec4<f32> {
   test(inst_idx);
+  return vec4<f32>();
 }
 )";
 
@@ -115,8 +118,9 @@
 }
 
 [[stage(vertex)]]
-fn entry([[builtin(instance_index)]] inst_idx : u32) {
+fn entry([[builtin(instance_index)]] inst_idx : u32) -> [[builtin(position)]] vec4<f32> {
   test((inst_idx + tint_symbol_1.first_instance_index));
+  return vec4<f32>();
 }
 )";
 
@@ -147,8 +151,9 @@
 };
 
 [[stage(vertex)]]
-fn entry(inputs : Inputs) {
+fn entry(inputs : Inputs) -> [[builtin(position)]] vec4<f32> {
   test(inputs.instance_idx, inputs.vert_idx);
+  return vec4<f32>();
 }
 )";
 
@@ -173,8 +178,9 @@
 };
 
 [[stage(vertex)]]
-fn entry(inputs : Inputs) {
+fn entry(inputs : Inputs) -> [[builtin(position)]] vec4<f32> {
   test((inputs.instance_idx + tint_symbol_1.first_instance_index), (inputs.vert_idx + tint_symbol_1.first_vertex_index));
+  return vec4<f32>();
 }
 )";
 
@@ -204,8 +210,9 @@
 }
 
 [[stage(vertex)]]
-fn entry([[builtin(vertex_index)]] vert_idx : u32) {
+fn entry([[builtin(vertex_index)]] vert_idx : u32) -> [[builtin(position)]] vec4<f32> {
   func2(vert_idx);
+  return vec4<f32>();
 }
 )";
 
@@ -226,8 +233,9 @@
 }
 
 [[stage(vertex)]]
-fn entry([[builtin(vertex_index)]] vert_idx : u32) {
+fn entry([[builtin(vertex_index)]] vert_idx : u32) -> [[builtin(position)]] vec4<f32> {
   func2((vert_idx + tint_symbol_1.first_vertex_index));
+  return vec4<f32>();
 }
 )";
 
@@ -253,18 +261,21 @@
 }
 
 [[stage(vertex)]]
-fn entry_a([[builtin(vertex_index)]] vert_idx : u32) {
+fn entry_a([[builtin(vertex_index)]] vert_idx : u32) -> [[builtin(position)]] vec4<f32> {
   func(vert_idx);
+  return vec4<f32>();
 }
 
 [[stage(vertex)]]
-fn entry_b([[builtin(vertex_index)]] vert_idx : u32, [[builtin(instance_index)]] inst_idx : u32) {
+fn entry_b([[builtin(vertex_index)]] vert_idx : u32, [[builtin(instance_index)]] inst_idx : u32) -> [[builtin(position)]] vec4<f32> {
   func(vert_idx + inst_idx);
+  return vec4<f32>();
 }
 
 [[stage(vertex)]]
-fn entry_c([[builtin(instance_index)]] inst_idx : u32) {
+fn entry_c([[builtin(instance_index)]] inst_idx : u32) -> [[builtin(position)]] vec4<f32> {
   func(inst_idx);
+  return vec4<f32>();
 }
 )";
 
@@ -282,18 +293,21 @@
 }
 
 [[stage(vertex)]]
-fn entry_a([[builtin(vertex_index)]] vert_idx : u32) {
+fn entry_a([[builtin(vertex_index)]] vert_idx : u32) -> [[builtin(position)]] vec4<f32> {
   func((vert_idx + tint_symbol_1.first_vertex_index));
+  return vec4<f32>();
 }
 
 [[stage(vertex)]]
-fn entry_b([[builtin(vertex_index)]] vert_idx : u32, [[builtin(instance_index)]] inst_idx : u32) {
+fn entry_b([[builtin(vertex_index)]] vert_idx : u32, [[builtin(instance_index)]] inst_idx : u32) -> [[builtin(position)]] vec4<f32> {
   func(((vert_idx + tint_symbol_1.first_vertex_index) + (inst_idx + tint_symbol_1.first_instance_index)));
+  return vec4<f32>();
 }
 
 [[stage(vertex)]]
-fn entry_c([[builtin(instance_index)]] inst_idx : u32) {
+fn entry_c([[builtin(instance_index)]] inst_idx : u32) -> [[builtin(position)]] vec4<f32> {
   func((inst_idx + tint_symbol_1.first_instance_index));
+  return vec4<f32>();
 }
 )";
 
@@ -316,6 +330,8 @@
   auto* src = R"(
 [[builtin(vertex_index)]] var<in> vert_idx : u32;
 
+[[builtin(position)]] var<out> pos : vec4<f32>;
+
 fn test() -> u32 {
   return vert_idx;
 }
@@ -323,6 +339,7 @@
 [[stage(vertex)]]
 fn entry() {
   test();
+  pos = vec4<f32>();
 }
 )";
 
@@ -336,6 +353,8 @@
 
 [[builtin(vertex_index)]] var<in> vert_idx : u32;
 
+[[builtin(position)]] var<out> pos : vec4<f32>;
+
 fn test() -> u32 {
   return (vert_idx + tint_symbol_1.first_vertex_index);
 }
@@ -343,6 +362,7 @@
 [[stage(vertex)]]
 fn entry() {
   test();
+  pos = vec4<f32>();
 }
 )";
 
@@ -363,6 +383,8 @@
   auto* src = R"(
 [[builtin(instance_index)]] var<in> inst_idx : u32;
 
+[[builtin(position)]] var<out> pos : vec4<f32>;
+
 fn test() -> u32 {
   return inst_idx;
 }
@@ -370,6 +392,7 @@
 [[stage(vertex)]]
 fn entry() {
   test();
+  pos = vec4<f32>();
 }
 )";
 
@@ -383,6 +406,8 @@
 
 [[builtin(instance_index)]] var<in> inst_idx : u32;
 
+[[builtin(position)]] var<out> pos : vec4<f32>;
+
 fn test() -> u32 {
   return (inst_idx + tint_symbol_1.first_instance_index);
 }
@@ -390,6 +415,7 @@
 [[stage(vertex)]]
 fn entry() {
   test();
+  pos = vec4<f32>();
 }
 )";
 
@@ -410,6 +436,7 @@
   auto* src = R"(
 [[builtin(instance_index)]] var<in> instance_idx : u32;
 [[builtin(vertex_index)]] var<in> vert_idx : u32;
+[[builtin(position)]] var<out> pos : vec4<f32>;
 
 fn test() -> u32 {
   return instance_idx + vert_idx;
@@ -418,6 +445,7 @@
 [[stage(vertex)]]
 fn entry() {
   test();
+  pos = vec4<f32>();
 }
 )";
 
@@ -434,6 +462,8 @@
 
 [[builtin(vertex_index)]] var<in> vert_idx : u32;
 
+[[builtin(position)]] var<out> pos : vec4<f32>;
+
 fn test() -> u32 {
   return ((instance_idx + tint_symbol_1.first_instance_index) + (vert_idx + tint_symbol_1.first_vertex_index));
 }
@@ -441,6 +471,7 @@
 [[stage(vertex)]]
 fn entry() {
   test();
+  pos = vec4<f32>();
 }
 )";
 
@@ -460,6 +491,7 @@
 TEST_F(FirstIndexOffsetTest, OLD_NestedCalls) {
   auto* src = R"(
 [[builtin(vertex_index)]] var<in> vert_idx : u32;
+[[builtin(position)]] var<out> pos : vec4<f32>;
 
 fn func1() -> u32 {
   return vert_idx;
@@ -472,6 +504,7 @@
 [[stage(vertex)]]
 fn entry() {
   func2();
+  pos = vec4<f32>();
 }
 )";
 
@@ -485,6 +518,8 @@
 
 [[builtin(vertex_index)]] var<in> vert_idx : u32;
 
+[[builtin(position)]] var<out> pos : vec4<f32>;
+
 fn func1() -> u32 {
   return (vert_idx + tint_symbol_1.first_vertex_index);
 }
@@ -496,6 +531,7 @@
 [[stage(vertex)]]
 fn entry() {
   func2();
+  pos = vec4<f32>();
 }
 )";
 
diff --git a/src/transform/hlsl.cc b/src/transform/hlsl.cc
index 4d17f6e..6d67a79 100644
--- a/src/transform/hlsl.cc
+++ b/src/transform/hlsl.cc
@@ -130,7 +130,7 @@
   ctx.dst->Func(
       ctx.dst->Symbols().New("tint_unused_entry_point"), {},
       ctx.dst->ty.void_(), {},
-      {ctx.dst->create<ast::StageDecoration>(ast::PipelineStage::kVertex)});
+      {ctx.dst->create<ast::StageDecoration>(ast::PipelineStage::kCompute)});
 }
 
 }  // namespace transform
diff --git a/src/transform/hlsl_test.cc b/src/transform/hlsl_test.cc
index e0273c2..c144225 100644
--- a/src/transform/hlsl_test.cc
+++ b/src/transform/hlsl_test.cc
@@ -24,7 +24,7 @@
 
 TEST_F(HlslTest, PromoteArrayInitializerToConstVar_Basic) {
   auto* src = R"(
-[[stage(vertex)]]
+[[stage(compute)]]
 fn main() {
   var f0 : f32 = 1.0;
   var f1 : f32 = 2.0;
@@ -35,7 +35,7 @@
 )";
 
   auto* expect = R"(
-[[stage(vertex)]]
+[[stage(compute)]]
 fn main() {
   var f0 : f32 = 1.0;
   var f1 : f32 = 2.0;
@@ -59,7 +59,7 @@
   c : vec3<f32>;
 };
 
-[[stage(vertex)]]
+[[stage(compute)]]
 fn main() {
   var x : f32 = S(1, 2.0, vec3<f32>()).b;
 }
@@ -72,7 +72,7 @@
   c : vec3<f32>;
 };
 
-[[stage(vertex)]]
+[[stage(compute)]]
 fn main() {
   let tint_symbol : S = S(1, 2.0, vec3<f32>());
   var x : f32 = tint_symbol.b;
@@ -86,14 +86,14 @@
 
 TEST_F(HlslTest, PromoteArrayInitializerToConstVar_ArrayInArray) {
   auto* src = R"(
-[[stage(vertex)]]
+[[stage(compute)]]
 fn main() {
   var i : f32 = array<array<f32, 2>, 2>(array<f32, 2>(1.0, 2.0), array<f32, 2>(3.0, 4.0))[0][1];
 }
 )";
 
   auto* expect = R"(
-[[stage(vertex)]]
+[[stage(compute)]]
 fn main() {
   let tint_symbol : array<f32, 2> = array<f32, 2>(1.0, 2.0);
   let tint_symbol_1 : array<f32, 2> = array<f32, 2>(3.0, 4.0);
@@ -123,7 +123,7 @@
   a : S2;
 };
 
-[[stage(vertex)]]
+[[stage(compute)]]
 fn main() {
   var x : i32 = S3(S2(1, S1(2), 3)).a.b.a;
 }
@@ -144,7 +144,7 @@
   a : S2;
 };
 
-[[stage(vertex)]]
+[[stage(compute)]]
 fn main() {
   let tint_symbol : S1 = S1(2);
   let tint_symbol_1 : S2 = S2(1, tint_symbol, 3);
@@ -168,7 +168,7 @@
   a : array<S1, 3>;
 };
 
-[[stage(vertex)]]
+[[stage(compute)]]
 fn main() {
   var x : i32 = S2(array<S1, 3>(S1(1), S1(2), S1(3))).a[1].a;
 }
@@ -183,7 +183,7 @@
   a : array<S1, 3>;
 };
 
-[[stage(vertex)]]
+[[stage(compute)]]
 fn main() {
   let tint_symbol : S1 = S1(1);
   let tint_symbol_1 : S1 = S1(2);
@@ -207,7 +207,7 @@
   c : i32;
 };
 
-[[stage(vertex)]]
+[[stage(compute)]]
 fn main() {
   var local_arr : array<f32, 4> = array<f32, 4>(0.0, 1.0, 2.0, 3.0);
   var local_str : S = S(1, 2.0, 3);
@@ -281,7 +281,7 @@
   auto* src = R"()";
 
   auto* expect = R"(
-[[stage(vertex)]]
+[[stage(compute)]]
 fn tint_unused_entry_point() {
 }
 )";
diff --git a/src/transform/renamer_test.cc b/src/transform/renamer_test.cc
index e840e87..5df98f9 100644
--- a/src/transform/renamer_test.cc
+++ b/src/transform/renamer_test.cc
@@ -49,8 +49,9 @@
 }
 
 [[stage(vertex)]]
-fn entry() {
+fn entry() -> [[builtin(position)]] vec4<f32>  {
   test();
+  return vec4<f32>();
 }
 )";
 
@@ -62,8 +63,9 @@
 }
 
 [[stage(vertex)]]
-fn tint_symbol_2() {
+fn tint_symbol_2() -> [[builtin(position)]] vec4<f32> {
   tint_symbol_1();
+  return vec4<f32>();
 }
 )";
 
diff --git a/src/transform/spirv_test.cc b/src/transform/spirv_test.cc
index 240fae3..7cf08d9 100644
--- a/src/transform/spirv_test.cc
+++ b/src/transform/spirv_test.cc
@@ -307,8 +307,8 @@
   [[location(1)]] value : f32;
 };
 
-[[stage(vertex)]]
-fn vert_main(inputs : Interface) -> Interface {
+[[stage(fragment)]]
+fn frag_main(inputs : Interface) -> Interface {
   return inputs;
 }
 )";
@@ -326,8 +326,8 @@
   tint_symbol_3 = tint_symbol_2.value;
 }
 
-[[stage(vertex)]]
-fn vert_main() {
+[[stage(fragment)]]
+fn frag_main() {
   let tint_symbol_1 : Interface = Interface(tint_symbol);
   tint_symbol_4(tint_symbol_1);
   return;
@@ -342,12 +342,13 @@
 TEST_F(SpirvTest, HandleEntryPointIOTypes_SharedStruct_DifferentShaders) {
   auto* src = R"(
 struct Interface {
+  [[builtin(position)]] pos : vec4<f32>;
   [[location(1)]] value : f32;
 };
 
 [[stage(vertex)]]
 fn vert_main() -> Interface {
-  return Interface(42.0);
+  return Interface(vec4<f32>(), 42.0);
 }
 
 [[stage(fragment)]]
@@ -358,27 +359,33 @@
 
   auto* expect = R"(
 struct Interface {
+  pos : vec4<f32>;
   value : f32;
 };
 
-[[location(1)]] var<out> tint_symbol_1 : f32;
+[[builtin(position)]] var<out> tint_symbol_1 : vec4<f32>;
 
-fn tint_symbol_2(tint_symbol : Interface) {
-  tint_symbol_1 = tint_symbol.value;
+[[location(1)]] var<out> tint_symbol_2 : f32;
+
+fn tint_symbol_3(tint_symbol : Interface) {
+  tint_symbol_1 = tint_symbol.pos;
+  tint_symbol_2 = tint_symbol.value;
 }
 
 [[stage(vertex)]]
 fn vert_main() {
-  tint_symbol_2(Interface(42.0));
+  tint_symbol_3(Interface(vec4<f32>(), 42.0));
   return;
 }
 
-[[location(1)]] var<in> tint_symbol_3 : f32;
+[[builtin(position)]] var<in> tint_symbol_4 : vec4<f32>;
+
+[[location(1)]] var<in> tint_symbol_5 : f32;
 
 [[stage(fragment)]]
 fn frag_main() {
-  let tint_symbol_4 : Interface = Interface(tint_symbol_3);
-  var x : f32 = tint_symbol_4.value;
+  let tint_symbol_6 : Interface = Interface(tint_symbol_4, tint_symbol_5);
+  var x : f32 = tint_symbol_6.value;
 }
 )";
 
diff --git a/src/transform/vertex_pulling_test.cc b/src/transform/vertex_pulling_test.cc
index bf319d6..8effc23 100644
--- a/src/transform/vertex_pulling_test.cc
+++ b/src/transform/vertex_pulling_test.cc
@@ -39,7 +39,9 @@
 TEST_F(VertexPullingTest, Error_InvalidEntryPoint) {
   auto* src = R"(
 [[stage(vertex)]]
-fn main() {}
+fn main() -> [[builtin(position)]] vec4<f32> {
+  return vec4<f32>();
+}
 )";
 
   auto* expect = "error: Vertex stage entry point not found";
@@ -75,7 +77,9 @@
 TEST_F(VertexPullingTest, BasicModule) {
   auto* src = R"(
 [[stage(vertex)]]
-fn main() {}
+fn main() -> [[builtin(position)]] vec4<f32> {
+  return vec4<f32>();
+}
 )";
 
   auto* expect = R"(
@@ -85,10 +89,11 @@
 };
 
 [[stage(vertex)]]
-fn main() {
+fn main() -> [[builtin(position)]] vec4<f32> {
   {
     var tint_pulling_pos : u32;
   }
+  return vec4<f32>();
 }
 )";
 
@@ -107,7 +112,9 @@
 [[location(0)]] var<in> var_a : f32;
 
 [[stage(vertex)]]
-fn main() {}
+fn main() -> [[builtin(position)]] vec4<f32> {
+  return vec4<f32>();
+}
 )";
 
   auto* expect = R"(
@@ -123,12 +130,13 @@
 var<private> var_a : f32;
 
 [[stage(vertex)]]
-fn main() {
+fn main() -> [[builtin(position)]] vec4<f32> {
   {
     var tint_pulling_pos : u32;
     tint_pulling_pos = ((tint_pulling_vertex_index * 4u) + 0u);
     var_a = bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(tint_pulling_pos / 4u)]);
   }
+  return vec4<f32>();
 }
 )";
 
@@ -149,7 +157,9 @@
 [[location(0)]] var<in> var_a : f32;
 
 [[stage(vertex)]]
-fn main() {}
+fn main() -> [[builtin(position)]] vec4<f32> {
+  return vec4<f32>();
+}
 )";
 
   auto* expect = R"(
@@ -165,12 +175,13 @@
 var<private> var_a : f32;
 
 [[stage(vertex)]]
-fn main() {
+fn main() -> [[builtin(position)]] vec4<f32> {
   {
     var tint_pulling_pos : u32;
     tint_pulling_pos = ((tint_pulling_instance_index * 4u) + 0u);
     var_a = bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(tint_pulling_pos / 4u)]);
   }
+  return vec4<f32>();
 }
 )";
 
@@ -191,7 +202,9 @@
 [[location(0)]] var<in> var_a : f32;
 
 [[stage(vertex)]]
-fn main() {}
+fn main() -> [[builtin(position)]] vec4<f32> {
+  return vec4<f32>();
+}
 )";
 
   auto* expect = R"(
@@ -207,12 +220,13 @@
 var<private> var_a : f32;
 
 [[stage(vertex)]]
-fn main() {
+fn main() -> [[builtin(position)]] vec4<f32> {
   {
     var tint_pulling_pos : u32;
     tint_pulling_pos = ((tint_pulling_vertex_index * 4u) + 0u);
     var_a = bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(tint_pulling_pos / 4u)]);
   }
+  return vec4<f32>();
 }
 )";
 
@@ -238,7 +252,9 @@
 [[builtin(instance_index)]] var<in> custom_instance_index : u32;
 
 [[stage(vertex)]]
-fn main() {}
+fn main() -> [[builtin(position)]] vec4<f32> {
+  return vec4<f32>();
+}
 )";
 
   auto* expect = R"(
@@ -260,7 +276,7 @@
 [[builtin(instance_index)]] var<in> custom_instance_index : u32;
 
 [[stage(vertex)]]
-fn main() {
+fn main() -> [[builtin(position)]] vec4<f32> {
   {
     var tint_pulling_pos : u32;
     tint_pulling_pos = ((custom_vertex_index * 4u) + 0u);
@@ -268,6 +284,7 @@
     tint_pulling_pos = ((custom_instance_index * 4u) + 0u);
     var_b = bitcast<f32>(tint_pulling_vertex_buffer_1.tint_vertex_data[(tint_pulling_pos / 4u)]);
   }
+  return vec4<f32>();
 }
 )";
 
@@ -299,7 +316,9 @@
 [[location(1)]] var<in> var_b : vec4<f32>;
 
 [[stage(vertex)]]
-fn main() {}
+fn main() -> [[builtin(position)]] vec4<f32> {
+  return vec4<f32>();
+}
 )";
 
   auto* expect = R"(
@@ -317,7 +336,7 @@
 var<private> var_b : vec4<f32>;
 
 [[stage(vertex)]]
-fn main() {
+fn main() -> [[builtin(position)]] vec4<f32> {
   {
     var tint_pulling_pos : u32;
     tint_pulling_pos = ((tint_pulling_vertex_index * 16u) + 0u);
@@ -325,6 +344,7 @@
     tint_pulling_pos = ((tint_pulling_vertex_index * 16u) + 0u);
     var_b = vec4<f32>(bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[((tint_pulling_pos + 0u) / 4u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[((tint_pulling_pos + 4u) / 4u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[((tint_pulling_pos + 8u) / 4u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[((tint_pulling_pos + 12u) / 4u)]));
   }
+  return vec4<f32>();
 }
 )";
 
@@ -349,7 +369,9 @@
 [[location(2)]] var<in> var_c : vec4<f32>;
 
 [[stage(vertex)]]
-fn main() {}
+fn main() -> [[builtin(position)]] vec4<f32> {
+  return vec4<f32>();
+}
 )";
 
   auto* expect = R"(
@@ -373,7 +395,7 @@
 var<private> var_c : vec4<f32>;
 
 [[stage(vertex)]]
-fn main() {
+fn main() -> [[builtin(position)]] vec4<f32> {
   {
     var tint_pulling_pos : u32;
     tint_pulling_pos = ((tint_pulling_vertex_index * 8u) + 0u);
@@ -383,6 +405,7 @@
     tint_pulling_pos = ((tint_pulling_vertex_index * 16u) + 0u);
     var_c = vec4<f32>(bitcast<f32>(tint_pulling_vertex_buffer_2.tint_vertex_data[((tint_pulling_pos + 0u) / 4u)]), bitcast<f32>(tint_pulling_vertex_buffer_2.tint_vertex_data[((tint_pulling_pos + 4u) / 4u)]), bitcast<f32>(tint_pulling_vertex_buffer_2.tint_vertex_data[((tint_pulling_pos + 8u) / 4u)]), bitcast<f32>(tint_pulling_vertex_buffer_2.tint_vertex_data[((tint_pulling_pos + 12u) / 4u)]));
   }
+  return vec4<f32>();
 }
 )";
 
@@ -407,11 +430,12 @@
 [[location(1)]] var<in> var_b : vec4<f32>;
 
 [[stage(vertex)]]
-fn main() {
+fn main() -> [[builtin(position)]] vec4<f32> {
   var tint_pulling_vertex_index : i32;
   var tint_pulling_vertex_buffer_0 : i32;
   var tint_vertex_data : i32;
   var tint_pulling_pos : i32;
+  return vec4<f32>();
 }
 )";
 
@@ -430,7 +454,7 @@
 var<private> var_b : vec4<f32>;
 
 [[stage(vertex)]]
-fn main() {
+fn main() -> [[builtin(position)]] vec4<f32> {
   {
     var tint_pulling_pos_1 : u32;
     tint_pulling_pos_1 = ((tint_pulling_vertex_index_1 * 16u) + 0u);
@@ -442,6 +466,7 @@
   var tint_pulling_vertex_buffer_0 : i32;
   var tint_vertex_data : i32;
   var tint_pulling_pos : i32;
+  return vec4<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 acaef38..21bfc48 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
@@ -42,14 +42,14 @@
              create<ast::LocationDecoration>(1),
          });
 
-  Func("vtx_main", ast::VariableList{}, ty.void_(),
+  Func("vtx_main", ast::VariableList{}, ty.vec4<f32>(),
        ast::StatementList{
-           create<ast::AssignmentStatement>(Expr("foo"), Expr("foo")),
-           create<ast::AssignmentStatement>(Expr("bar"), Expr("bar")),
+           Assign(Expr("foo"), Expr("foo")),
+           Assign(Expr("bar"), Expr("bar")),
+           Return(Construct(ty.vec4<f32>())),
        },
-       ast::DecorationList{
-           create<ast::StageDecoration>(ast::PipelineStage::kVertex),
-       });
+       {Stage(ast::PipelineStage::kVertex)},
+       {Builtin(ast::Builtin::kPosition)});
 
   std::unordered_set<Symbol> globals;
 
@@ -85,14 +85,14 @@
              create<ast::LocationDecoration>(1),
          });
 
-  Func("vtx_main", ast::VariableList{}, ty.void_(),
+  Func("vtx_main", ast::VariableList{}, ty.vec4<f32>(),
        ast::StatementList{
-           create<ast::AssignmentStatement>(Expr("foo"), Expr("foo")),
-           create<ast::AssignmentStatement>(Expr("bar"), Expr("bar")),
+           Assign(Expr("foo"), Expr("foo")),
+           Assign(Expr("bar"), Expr("bar")),
+           Return(Construct(ty.vec4<f32>())),
        },
-       ast::DecorationList{
-           create<ast::StageDecoration>(ast::PipelineStage::kVertex),
-       });
+       {Stage(ast::PipelineStage::kVertex)},
+       {Builtin(ast::Builtin::kPosition)});
 
   std::unordered_set<Symbol> globals;
 
@@ -134,7 +134,7 @@
            create<ast::AssignmentStatement>(Expr("bar"), Expr("bar")),
        },
        ast::DecorationList{
-           create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+           create<ast::StageDecoration>(ast::PipelineStage::kFragment),
        });
 
   std::unordered_set<Symbol> globals;
diff --git a/src/writer/hlsl/generator_impl_function_test.cc b/src/writer/hlsl/generator_impl_function_test.cc
index 3e7daf6..e3f733b 100644
--- a/src/writer/hlsl/generator_impl_function_test.cc
+++ b/src/writer/hlsl/generator_impl_function_test.cc
@@ -174,32 +174,39 @@
 TEST_F(HlslGeneratorImplTest_Function,
        Emit_Decoration_EntryPoint_SharedStruct_DifferentStages) {
   // struct Interface {
+  //   [[builtin(position)]] pos : vec4<f32>;
   //   [[location(1)]] col1 : f32;
   //   [[location(2)]] col2 : f32;
   // };
   // fn vert_main() -> Interface {
-  //   return Interface(0.4, 0.6);
+  //   return Interface(vec4<f32>(), 0.4, 0.6);
   // }
-  // fn frag_main(colors : Interface) {
-  //   const r = colors.col1;
-  //   const g = colors.col2;
+  // fn frag_main(inputs : Interface) {
+  //   const r = inputs.col1;
+  //   const g = inputs.col2;
+  //   const p = inputs.pos;
   // }
   auto* interface_struct = Structure(
       "Interface",
-      {Member("col1", ty.f32(), {create<ast::LocationDecoration>(1)}),
-       Member("col2", ty.f32(), {create<ast::LocationDecoration>(2)})});
+      {
+          Member("pos", ty.vec4<f32>(), {Builtin(ast::Builtin::kPosition)}),
+          Member("col1", ty.f32(), {Location(1)}),
+          Member("col2", ty.f32(), {Location(2)}),
+      });
 
   Func("vert_main", {}, interface_struct,
-       {create<ast::ReturnStatement>(
-           Construct(interface_struct, Expr(0.5f), Expr(0.25f)))},
-       {create<ast::StageDecoration>(ast::PipelineStage::kVertex)});
+       {Return(Construct(interface_struct, Construct(ty.vec4<f32>()),
+                         Expr(0.5f), Expr(0.25f)))},
+       {Stage(ast::PipelineStage::kVertex)});
 
-  Func("frag_main", {Param("colors", interface_struct)}, ty.void_(),
+  Func("frag_main", {Param("inputs", interface_struct)}, ty.void_(),
        {
            WrapInStatement(
-               Const("r", ty.f32(), MemberAccessor(Expr("colors"), "col1"))),
+               Const("r", ty.f32(), MemberAccessor(Expr("inputs"), "col1"))),
            WrapInStatement(
-               Const("g", ty.f32(), MemberAccessor(Expr("colors"), "col2"))),
+               Const("g", ty.f32(), MemberAccessor(Expr("inputs"), "col2"))),
+           WrapInStatement(Const("p", ty.vec4<f32>(),
+                                 MemberAccessor(Expr("inputs"), "pos"))),
        },
        {create<ast::StageDecoration>(ast::PipelineStage::kFragment)});
 
@@ -207,28 +214,32 @@
 
   ASSERT_TRUE(gen.Generate(out)) << gen.error();
   EXPECT_EQ(result(), R"(struct Interface {
+  float4 pos;
   float col1;
   float col2;
 };
 struct tint_symbol {
   float col1 : TEXCOORD1;
   float col2 : TEXCOORD2;
+  float4 pos : SV_Position;
 };
 struct tint_symbol_3 {
   float col1 : TEXCOORD1;
   float col2 : TEXCOORD2;
+  float4 pos : SV_Position;
 };
 
 tint_symbol vert_main() {
-  const Interface tint_symbol_1 = {0.5f, 0.25f};
-  const tint_symbol tint_symbol_4 = {tint_symbol_1.col1, tint_symbol_1.col2};
+  const Interface tint_symbol_1 = {float4(0.0f, 0.0f, 0.0f, 0.0f), 0.5f, 0.25f};
+  const tint_symbol tint_symbol_4 = {tint_symbol_1.col1, tint_symbol_1.col2, tint_symbol_1.pos};
   return tint_symbol_4;
 }
 
 void frag_main(tint_symbol_3 tint_symbol_2) {
-  const Interface colors = {tint_symbol_2.col1, tint_symbol_2.col2};
-  const float r = colors.col1;
-  const float g = colors.col2;
+  const Interface inputs = {tint_symbol_2.pos, tint_symbol_2.col1, tint_symbol_2.col2};
+  const float r = inputs.col1;
+  const float g = inputs.col2;
+  const float4 p = inputs.pos;
   return;
 }
 
diff --git a/src/writer/hlsl/generator_impl_member_accessor_test.cc b/src/writer/hlsl/generator_impl_member_accessor_test.cc
index f25e691..8086be3 100644
--- a/src/writer/hlsl/generator_impl_member_accessor_test.cc
+++ b/src/writer/hlsl/generator_impl_member_accessor_test.cc
@@ -113,7 +113,7 @@
     ProgramBuilder& b = *this;
     b.Func("main", ast::VariableList{}, b.ty.void_(), statements,
            ast::DecorationList{
-               b.create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+               b.create<ast::StageDecoration>(ast::PipelineStage::kFragment),
            });
   }
 };
diff --git a/src/writer/hlsl/generator_impl_sanitizer_test.cc b/src/writer/hlsl/generator_impl_sanitizer_test.cc
index c167aaf..d882f95 100644
--- a/src/writer/hlsl/generator_impl_sanitizer_test.cc
+++ b/src/writer/hlsl/generator_impl_sanitizer_test.cc
@@ -50,7 +50,7 @@
                    Call("arrayLength", MemberAccessor("sb", "arr")))),
        },
        ast::DecorationList{
-           create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+           create<ast::StageDecoration>(ast::PipelineStage::kFragment),
        });
 
   GeneratorImpl& gen = SanitizeAndBuild();
@@ -83,7 +83,7 @@
            create<ast::VariableDeclStatement>(pos),
        },
        ast::DecorationList{
-           create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+           create<ast::StageDecoration>(ast::PipelineStage::kFragment),
        });
 
   GeneratorImpl& gen = SanitizeAndBuild();
@@ -117,7 +117,7 @@
            create<ast::VariableDeclStatement>(pos),
        },
        ast::DecorationList{
-           create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+           create<ast::StageDecoration>(ast::PipelineStage::kFragment),
        });
 
   GeneratorImpl& gen = SanitizeAndBuild();
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 46bf201..1d4210e 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
@@ -38,14 +38,14 @@
          ast::DecorationList{create<ast::LocationDecoration>(1)});
 
   auto body = ast::StatementList{
-      create<ast::AssignmentStatement>(Expr("foo"), Expr("foo")),
-      create<ast::AssignmentStatement>(Expr("bar"), Expr("bar")),
+      Assign(Expr("foo"), Expr("foo")),
+      Assign(Expr("bar"), Expr("bar")),
+      Return(Construct(ty.vec4<f32>())),
   };
 
-  Func("vtx_main", ast::VariableList{}, ty.void_(), body,
-       ast::DecorationList{
-           create<ast::StageDecoration>(ast::PipelineStage::kVertex),
-       });
+  Func("vtx_main", ast::VariableList{}, ty.vec4<f32>(), body,
+       {Stage(ast::PipelineStage::kVertex)},
+       {Builtin(ast::Builtin::kPosition)});
 
   GeneratorImpl& gen = Build();
 
@@ -75,14 +75,14 @@
          ast::DecorationList{create<ast::LocationDecoration>(1)});
 
   auto body = ast::StatementList{
-      create<ast::AssignmentStatement>(Expr("foo"), Expr("foo")),
-      create<ast::AssignmentStatement>(Expr("bar"), Expr("bar")),
+      Assign(Expr("foo"), Expr("foo")),
+      Assign(Expr("bar"), Expr("bar")),
+      Return(Construct(ty.vec4<f32>())),
   };
 
-  Func("vtx_main", ast::VariableList{}, ty.void_(), body,
-       ast::DecorationList{
-           create<ast::StageDecoration>(ast::PipelineStage::kVertex),
-       });
+  Func("vtx_main", ast::VariableList{}, ty.vec4<f32>(), body,
+       {Stage(ast::PipelineStage::kVertex)},
+       {Builtin(ast::Builtin::kPosition)});
 
   GeneratorImpl& gen = Build();
 
diff --git a/src/writer/spirv/builder_function_decoration_test.cc b/src/writer/spirv/builder_function_decoration_test.cc
index bb27fe7..0a9728a 100644
--- a/src/writer/spirv/builder_function_decoration_test.cc
+++ b/src/writer/spirv/builder_function_decoration_test.cc
@@ -28,14 +28,14 @@
   auto* func =
       Func("main", {}, ty.void_(), ast::StatementList{},
            ast::DecorationList{
-               create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+               create<ast::StageDecoration>(ast::PipelineStage::kFragment),
            });
 
   spirv::Builder& b = Build();
 
   ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
   EXPECT_EQ(DumpInstructions(b.entry_points()),
-            R"(OpEntryPoint Vertex %3 "main"
+            R"(OpEntryPoint Fragment %3 "main"
 )");
 }
 
@@ -51,13 +51,24 @@
 TEST_P(Decoration_StageTest, Emit) {
   auto params = GetParam();
 
-  auto* func = Func("main", {}, ty.void_(), ast::StatementList{},
+  ast::Variable* var = nullptr;
+  ast::StatementList body;
+  if (params.stage == ast::PipelineStage::kVertex) {
+    var = Global("pos", ty.vec4<f32>(), ast::StorageClass::kOutput, nullptr,
+                 ast::DecorationList{Builtin(ast::Builtin::kPosition)});
+    body.push_back(Assign("pos", Construct(ty.vec4<f32>())));
+  }
+
+  auto* func = Func("main", {}, ty.void_(), body,
                     ast::DecorationList{
                         create<ast::StageDecoration>(params.stage),
                     });
 
   spirv::Builder& b = Build();
 
+  if (var) {
+    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
+  }
   ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
 
   auto preamble = b.entry_points();
@@ -82,7 +93,7 @@
   auto* func =
       Func("main", {}, ty.void_(), ast::StatementList{},
            ast::DecorationList{
-               create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+               create<ast::StageDecoration>(ast::PipelineStage::kFragment),
            });
 
   auto* v_in = Global("my_in", ty.f32(), ast::StorageClass::kInput);
@@ -113,7 +124,7 @@
 %9 = OpTypeFunction %10
 )");
   EXPECT_EQ(DumpInstructions(b.entry_points()),
-            R"(OpEntryPoint Vertex %11 "main"
+            R"(OpEntryPoint Fragment %11 "main"
 )");
 }
 
@@ -131,7 +142,7 @@
                // output multiple times.
                create<ast::AssignmentStatement>(Expr("my_out"), Expr("my_in"))},
            ast::DecorationList{
-               create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+               create<ast::StageDecoration>(ast::PipelineStage::kFragment),
            });
 
   spirv::Builder& b = Build();
@@ -158,7 +169,7 @@
 %9 = OpTypeFunction %10
 )");
   EXPECT_EQ(DumpInstructions(b.entry_points()),
-            R"(OpEntryPoint Vertex %11 "main" %4 %1
+            R"(OpEntryPoint Fragment %11 "main" %4 %1
 )");
 }
 
diff --git a/src/writer/spirv/builder_intrinsic_test.cc b/src/writer/spirv/builder_intrinsic_test.cc
index 6f539e5..3be0bd7 100644
--- a/src/writer/spirv/builder_intrinsic_test.cc
+++ b/src/writer/spirv/builder_intrinsic_test.cc
@@ -1261,7 +1261,7 @@
            create<ast::CallStatement>(expr),
        },
        ast::DecorationList{
-           create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+           create<ast::StageDecoration>(ast::PipelineStage::kFragment),
        });
 
   spirv::Builder& b = Build();
@@ -1271,7 +1271,8 @@
   auto* expect = R"(OpCapability Shader
 %11 = OpExtInstImport "GLSL.std.450"
 OpMemoryModel Logical GLSL450
-OpEntryPoint Vertex %3 "a_func"
+OpEntryPoint Fragment %3 "a_func"
+OpExecutionMode %3 OriginUpperLeft
 OpName %3 "a_func"
 OpName %5 "out"
 %2 = OpTypeVoid
@@ -1304,7 +1305,7 @@
            create<ast::CallStatement>(expr),
        },
        ast::DecorationList{
-           create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+           create<ast::StageDecoration>(ast::PipelineStage::kFragment),
        });
 
   spirv::Builder& b = Build();
@@ -1314,7 +1315,8 @@
   auto* expect = R"(OpCapability Shader
 %13 = OpExtInstImport "GLSL.std.450"
 OpMemoryModel Logical GLSL450
-OpEntryPoint Vertex %3 "a_func"
+OpEntryPoint Fragment %3 "a_func"
+OpExecutionMode %3 OriginUpperLeft
 OpName %3 "a_func"
 OpName %5 "out"
 %2 = OpTypeVoid
@@ -1393,7 +1395,7 @@
            create<ast::CallStatement>(expr),
        },
        ast::DecorationList{
-           create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+           create<ast::StageDecoration>(ast::PipelineStage::kFragment),
        });
 
   spirv::Builder& b = Build();
@@ -1443,7 +1445,7 @@
            create<ast::CallStatement>(expr),
        },
        ast::DecorationList{
-           create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+           create<ast::StageDecoration>(ast::PipelineStage::kFragment),
        });
 
   spirv::Builder& b = Build();