Revert "Validate that in/out storage classes are not used"

This reverts commit 6330260f7d0e7322d1162fdd84ff2e6a8db76ab9.

Reason for revert: Need by this CL which was also reverted:
https://dawn-review.googlesource.com/c/tint/+/55402

Original change's description:
> Validate that in/out storage classes are not used
>
> Use a DisableValidationDecoration to allow these storage classes only
> for variables generated by the SPIR-V sanitizer.
>
> Fix or delete all of the tests that were wrongly using these storage
> classes.
>
> Bug: tint:697
> Change-Id: Ife1154f687b18529cfcc7a0ed93407fd25c9868e
> Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/55404
> Reviewed-by: Ben Clayton <bclayton@google.com>
> Kokoro: Kokoro <noreply+kokoro@google.com>
> Auto-Submit: James Price <jrprice@google.com>
> Commit-Queue: James Price <jrprice@google.com>

TBR=bclayton@google.com,jrprice@google.com,noreply+kokoro@google.com,tint-scoped@luci-project-accounts.iam.gserviceaccount.com

Change-Id: I54db99d6d0fdf28c27a1f2b9858b84f5cd197409
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Bug: tint:697
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/55660
Reviewed-by: James Price <jrprice@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: James Price <jrprice@google.com>
diff --git a/src/ast/disable_validation_decoration.cc b/src/ast/disable_validation_decoration.cc
index 28adae4..a8786e0 100644
--- a/src/ast/disable_validation_decoration.cc
+++ b/src/ast/disable_validation_decoration.cc
@@ -34,8 +34,8 @@
       return "disable_validation__function_has_no_body";
     case DisabledValidation::kBindingPointCollision:
       return "disable_validation__binding_point_collision";
-    case DisabledValidation::kIgnoreStorageClass:
-      return "disable_validation__ignore_storage_class";
+    case DisabledValidation::kFunctionVarStorageClass:
+      return "disable_validation__function_var_storage_class";
     case DisabledValidation::kEntryPointParameter:
       return "disable_validation__entry_point_parameter";
   }
diff --git a/src/ast/disable_validation_decoration.h b/src/ast/disable_validation_decoration.h
index e2b7bb9..344dc7e 100644
--- a/src/ast/disable_validation_decoration.h
+++ b/src/ast/disable_validation_decoration.h
@@ -31,9 +31,9 @@
   /// When applied to a module-scoped variable, the validator will not complain
   /// if two resource variables have the same binding points.
   kBindingPointCollision,
-  /// When applied to a variable, the validator will not complain about the
-  /// declared storage class.
-  kIgnoreStorageClass,
+  /// When applied to a function-scoped variable, the validator will not
+  /// complain if the storage class is not `function`.
+  kFunctionVarStorageClass,
   /// When applied to an entry-point function parameter, the validator will not
   /// check for entry IO decorations.
   kEntryPointParameter,
diff --git a/src/program_test.cc b/src/program_test.cc
index 991e178..acd6355 100644
--- a/src/program_test.cc
+++ b/src/program_test.cc
@@ -53,7 +53,7 @@
 }
 
 TEST_F(ProgramTest, Assert_GlobalVariable) {
-  Global("var", ty.f32(), ast::StorageClass::kPrivate);
+  Global("var", ty.f32(), ast::StorageClass::kInput);
 
   Program program(std::move(*this));
   EXPECT_TRUE(program.IsValid());
diff --git a/src/resolver/decoration_validation_test.cc b/src/resolver/decoration_validation_test.cc
index 813149c..d43d8f7 100644
--- a/src/resolver/decoration_validation_test.cc
+++ b/src/resolver/decoration_validation_test.cc
@@ -84,16 +84,6 @@
   }
 }
 
-bool IsShaderIODecoration(DecorationKind kind) {
-  switch (kind) {
-    case DecorationKind::kBuiltin:
-    case DecorationKind::kLocation:
-      return true;
-    default:
-      return false;
-  }
-}
-
 struct TestParams {
   DecorationKind kind;
   bool should_pass;
@@ -337,16 +327,8 @@
     Global("a", ty.sampler(ast::SamplerKind::kSampler),
            ast::StorageClass::kNone, nullptr,
            createDecorations(Source{{12, 34}}, *this, params.kind));
-  } else if (IsShaderIODecoration(params.kind)) {
-    // Shader IO decorations are only valid on global variables when they have
-    // input/output storage classes, which are only generated internally by the
-    // SPIR-V sanitizer.
-    auto decos = createDecorations(Source{{12, 34}}, *this, params.kind);
-    decos.push_back(ASTNodes().Create<ast::DisableValidationDecoration>(
-        ID(), ast::DisabledValidation::kIgnoreStorageClass));
-    Global("a", ty.f32(), ast::StorageClass::kInput, nullptr, decos);
   } else {
-    Global("a", ty.f32(), ast::StorageClass::kPrivate, nullptr,
+    Global("a", ty.f32(), ast::StorageClass::kInput, nullptr,
            createDecorations(Source{{12, 34}}, *this, params.kind));
   }
 
@@ -524,7 +506,7 @@
 
   auto* arr = ty.array(Source{{12, 34}}, el_ty, 4, params.stride);
 
-  Global("myarray", arr, ast::StorageClass::kPrivate);
+  Global("myarray", arr, ast::StorageClass::kInput);
 
   if (params.should_pass) {
     EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -608,7 +590,7 @@
                            create<ast::StrideDecoration>(Source{{56, 78}}, 4),
                        });
 
-  Global("myarray", arr, ast::StorageClass::kPrivate);
+  Global("myarray", arr, ast::StorageClass::kInput);
 
   EXPECT_FALSE(r()->Resolve());
   EXPECT_EQ(r()->error(),
diff --git a/src/resolver/intrinsic_test.cc b/src/resolver/intrinsic_test.cc
index 473598e..34ed7f0 100644
--- a/src/resolver/intrinsic_test.cc
+++ b/src/resolver/intrinsic_test.cc
@@ -52,7 +52,7 @@
 TEST_P(ResolverIntrinsicDerivativeTest, Scalar) {
   auto name = GetParam();
 
-  Global("ident", ty.f32(), ast::StorageClass::kPrivate);
+  Global("ident", ty.f32(), ast::StorageClass::kInput);
 
   auto* expr = Call(name, "ident");
   Func("func", {}, ty.void_(), {Ignore(expr)},
@@ -66,7 +66,7 @@
 
 TEST_P(ResolverIntrinsicDerivativeTest, Vector) {
   auto name = GetParam();
-  Global("ident", ty.vec4<f32>(), ast::StorageClass::kPrivate);
+  Global("ident", ty.vec4<f32>(), ast::StorageClass::kInput);
 
   auto* expr = Call(name, "ident");
   Func("func", {}, ty.void_(), {Ignore(expr)},
@@ -111,7 +111,7 @@
 TEST_P(ResolverIntrinsic, Test) {
   auto name = GetParam();
 
-  Global("my_var", ty.vec3<bool>(), ast::StorageClass::kPrivate);
+  Global("my_var", ty.vec3<bool>(), ast::StorageClass::kInput);
 
   auto* expr = Call(name, "my_var");
   WrapInFunction(expr);
@@ -129,7 +129,7 @@
 TEST_P(ResolverIntrinsicTest_FloatMethod, Vector) {
   auto name = GetParam();
 
-  Global("my_var", ty.vec3<f32>(), ast::StorageClass::kPrivate);
+  Global("my_var", ty.vec3<f32>(), ast::StorageClass::kInput);
 
   auto* expr = Call(name, "my_var");
   WrapInFunction(expr);
@@ -145,7 +145,7 @@
 TEST_P(ResolverIntrinsicTest_FloatMethod, Scalar) {
   auto name = GetParam();
 
-  Global("my_var", ty.f32(), ast::StorageClass::kPrivate);
+  Global("my_var", ty.f32(), ast::StorageClass::kInput);
 
   auto* expr = Call(name, "my_var");
   WrapInFunction(expr);
@@ -159,7 +159,7 @@
 TEST_P(ResolverIntrinsicTest_FloatMethod, MissingParam) {
   auto name = GetParam();
 
-  Global("my_var", ty.f32(), ast::StorageClass::kPrivate);
+  Global("my_var", ty.f32(), ast::StorageClass::kInput);
 
   auto* expr = Call(name);
   WrapInFunction(expr);
@@ -176,7 +176,7 @@
 TEST_P(ResolverIntrinsicTest_FloatMethod, TooManyParams) {
   auto name = GetParam();
 
-  Global("my_var", ty.f32(), ast::StorageClass::kPrivate);
+  Global("my_var", ty.f32(), ast::StorageClass::kInput);
 
   auto* expr = Call(name, "my_var", 1.23f);
   WrapInFunction(expr);
@@ -378,7 +378,7 @@
                     TextureTestParams{ast::TextureDimension::k3d}));
 
 TEST_F(ResolverIntrinsicTest, Dot_Vec2) {
-  Global("my_var", ty.vec2<f32>(), ast::StorageClass::kPrivate);
+  Global("my_var", ty.vec2<f32>(), ast::StorageClass::kInput);
 
   auto* expr = Call("dot", "my_var", "my_var");
   WrapInFunction(expr);
@@ -390,7 +390,7 @@
 }
 
 TEST_F(ResolverIntrinsicTest, Dot_Vec3) {
-  Global("my_var", ty.vec3<f32>(), ast::StorageClass::kPrivate);
+  Global("my_var", ty.vec3<f32>(), ast::StorageClass::kInput);
 
   auto* expr = Call("dot", "my_var", "my_var");
   WrapInFunction(expr);
@@ -402,7 +402,7 @@
 }
 
 TEST_F(ResolverIntrinsicTest, Dot_Vec4) {
-  Global("my_var", ty.vec4<f32>(), ast::StorageClass::kPrivate);
+  Global("my_var", ty.vec4<f32>(), ast::StorageClass::kInput);
 
   auto* expr = Call("dot", "my_var", "my_var");
   WrapInFunction(expr);
@@ -428,7 +428,7 @@
 }
 
 TEST_F(ResolverIntrinsicTest, Dot_Error_VectorInt) {
-  Global("my_var", ty.vec4<i32>(), ast::StorageClass::kPrivate);
+  Global("my_var", ty.vec4<i32>(), ast::StorageClass::kInput);
 
   auto* expr = Call("dot", "my_var", "my_var");
   WrapInFunction(expr);
@@ -444,9 +444,9 @@
 }
 
 TEST_F(ResolverIntrinsicTest, Select) {
-  Global("my_var", ty.vec3<f32>(), ast::StorageClass::kPrivate);
+  Global("my_var", ty.vec3<f32>(), ast::StorageClass::kInput);
 
-  Global("bool_var", ty.vec3<bool>(), ast::StorageClass::kPrivate);
+  Global("bool_var", ty.vec3<bool>(), ast::StorageClass::kInput);
 
   auto* expr = Call("select", "my_var", "my_var", "bool_var");
   WrapInFunction(expr);
@@ -784,7 +784,7 @@
 }
 
 TEST_F(ResolverIntrinsicDataTest, ArrayLength_Error_ArraySized) {
-  Global("arr", ty.array<int, 4>(), ast::StorageClass::kPrivate);
+  Global("arr", ty.array<int, 4>(), ast::StorageClass::kInput);
   auto* call = Call("arrayLength", AddressOf("arr"));
   WrapInFunction(call);
 
@@ -792,7 +792,7 @@
 
   EXPECT_EQ(
       r()->error(),
-      R"(error: no matching call to arrayLength(ptr<private, array<i32, 4>, read_write>)
+      R"(error: no matching call to arrayLength(ptr<in, array<i32, 4>, read_write>)
 
 1 candidate function:
   arrayLength(ptr<storage, array<T>, A>) -> u32
diff --git a/src/resolver/resolver.cc b/src/resolver/resolver.cc
index 5d01d64..8f56d3d 100644
--- a/src/resolver/resolver.cc
+++ b/src/resolver/resolver.cc
@@ -909,15 +909,6 @@
     return false;
   }
 
-  if (!IsValidationDisabled(var->decorations(),
-                            ast::DisabledValidation::kIgnoreStorageClass) &&
-      (var->declared_storage_class() == ast::StorageClass::kInput ||
-       var->declared_storage_class() == ast::StorageClass::kOutput)) {
-    diagnostics_.add_error("invalid use of input/output storage class",
-                           var->source());
-    return false;
-  }
-
   // https://gpuweb.github.io/gpuweb/wgsl/#atomic-types
   // Atomic types may only be instantiated by variables in the workgroup storage
   // class or by storage buffer variables with a read_write access mode.
@@ -2791,8 +2782,9 @@
 
   if (!var->is_const()) {
     if (info->storage_class != ast::StorageClass::kFunction &&
-        !IsValidationDisabled(var->decorations(),
-                              ast::DisabledValidation::kIgnoreStorageClass)) {
+        !IsValidationDisabled(
+            var->decorations(),
+            ast::DisabledValidation::kFunctionVarStorageClass)) {
       if (info->storage_class != ast::StorageClass::kNone) {
         diagnostics_.add_error(
             "function variable has a non-function storage class",
diff --git a/src/resolver/resolver_test.cc b/src/resolver/resolver_test.cc
index 90fe982..c5e7176 100644
--- a/src/resolver/resolver_test.cc
+++ b/src/resolver/resolver_test.cc
@@ -320,7 +320,7 @@
 
 TEST_F(ResolverTest, Stmt_VariableDecl_ModuleScope) {
   auto* init = Expr(2);
-  Global("my_var", ty.i32(), ast::StorageClass::kPrivate, init);
+  Global("my_var", ty.i32(), ast::StorageClass::kInput, init);
 
   EXPECT_TRUE(r()->Resolve()) << r()->error();
 
@@ -405,7 +405,7 @@
   Func("func_i32", params, ty.void_(), {fn_i32_decl}, ast::DecorationList{});
 
   // Declare f32 "foo" at module scope
-  auto* mod_f32 = Var("foo", ty.f32(), ast::StorageClass::kPrivate, Expr(2.f));
+  auto* mod_f32 = Var("foo", ty.f32(), ast::StorageClass::kInput, Expr(2.f));
   auto* mod_init = mod_f32->constructor();
   AST().AddGlobalVariable(mod_f32);
 
@@ -477,7 +477,7 @@
 }
 
 TEST_F(ResolverTest, ArrayAccessor_Matrix_Dynamic_F32) {
-  Global("my_var", ty.mat2x3<f32>(), ast::StorageClass::kPrivate);
+  Global("my_var", ty.mat2x3<f32>(), ast::StorageClass::kInput);
   auto* acc = IndexAccessor("my_var", Expr(Source{{12, 34}}, 1.0f));
   WrapInFunction(acc);
 
@@ -487,7 +487,7 @@
 }
 
 TEST_F(ResolverTest, ArrayAccessor_Matrix_Dynamic_Ref) {
-  Global("my_var", ty.mat2x3<f32>(), ast::StorageClass::kPrivate);
+  Global("my_var", ty.mat2x3<f32>(), ast::StorageClass::kInput);
   auto* idx = Var("idx", ty.i32(), Construct(ty.i32()));
   auto* acc = IndexAccessor("my_var", idx);
   WrapInFunction(Decl(idx), acc);
@@ -496,7 +496,7 @@
 }
 
 TEST_F(ResolverTest, ArrayAccessor_Matrix_BothDimensions_Dynamic_Ref) {
-  Global("my_var", ty.mat4x4<f32>(), ast::StorageClass::kPrivate);
+  Global("my_var", ty.mat4x4<f32>(), ast::StorageClass::kOutput);
   auto* idx = Var("idx", ty.u32(), Expr(3u));
   auto* idy = Var("idy", ty.u32(), Expr(2u));
   auto* acc = IndexAccessor(IndexAccessor("my_var", idx), idy);
@@ -540,7 +540,7 @@
 }
 
 TEST_F(ResolverTest, Expr_ArrayAccessor_Matrix) {
-  Global("my_var", ty.mat2x3<f32>(), ast::StorageClass::kPrivate);
+  Global("my_var", ty.mat2x3<f32>(), ast::StorageClass::kInput);
 
   auto* acc = IndexAccessor("my_var", 2);
   WrapInFunction(acc);
@@ -556,7 +556,7 @@
 }
 
 TEST_F(ResolverTest, Expr_ArrayAccessor_Matrix_BothDimensions) {
-  Global("my_var", ty.mat2x3<f32>(), ast::StorageClass::kPrivate);
+  Global("my_var", ty.mat2x3<f32>(), ast::StorageClass::kInput);
 
   auto* acc = IndexAccessor(IndexAccessor("my_var", 2), 1);
   WrapInFunction(acc);
@@ -571,7 +571,7 @@
 }
 
 TEST_F(ResolverTest, Expr_ArrayAccessor_Vector_F32) {
-  Global("my_var", ty.vec3<f32>(), ast::StorageClass::kPrivate);
+  Global("my_var", ty.vec3<f32>(), ast::StorageClass::kInput);
   auto* acc = IndexAccessor("my_var", Expr(Source{{12, 34}}, 2.0f));
   WrapInFunction(acc);
 
@@ -581,7 +581,7 @@
 }
 
 TEST_F(ResolverTest, Expr_ArrayAccessor_Vector_Dynamic_Ref) {
-  Global("my_var", ty.vec3<f32>(), ast::StorageClass::kPrivate);
+  Global("my_var", ty.vec3<f32>(), ast::StorageClass::kInput);
   auto* idx = Var("idx", ty.i32(), Expr(2));
   auto* acc = IndexAccessor("my_var", idx);
   WrapInFunction(Decl(idx), acc);
@@ -599,7 +599,7 @@
 }
 
 TEST_F(ResolverTest, Expr_ArrayAccessor_Vector) {
-  Global("my_var", ty.vec3<f32>(), ast::StorageClass::kPrivate);
+  Global("my_var", ty.vec3<f32>(), ast::StorageClass::kInput);
 
   auto* acc = IndexAccessor("my_var", 2);
   WrapInFunction(acc);
@@ -737,7 +737,7 @@
 }
 
 TEST_F(ResolverTest, Expr_Identifier_GlobalVariable) {
-  auto* my_var = Global("my_var", ty.f32(), ast::StorageClass::kPrivate);
+  auto* my_var = Global("my_var", ty.f32(), ast::StorageClass::kInput);
 
   auto* ident = Expr("my_var");
   WrapInFunction(ident);
@@ -972,6 +972,8 @@
   auto* s = Structure("S", {Member("m", ty.u32())},
                       {create<ast::StructBlockDecoration>()});
 
+  auto* in_var = Global("in_var", ty.f32(), ast::StorageClass::kInput);
+  auto* out_var = Global("out_var", ty.f32(), ast::StorageClass::kOutput);
   auto* sb_var = Global("sb_var", ty.Of(s), ast::StorageClass::kStorage,
                         ast::Access::kReadWrite,
                         ast::DecorationList{
@@ -983,6 +985,7 @@
 
   auto* func = Func("my_func", ast::VariableList{}, ty.void_(),
                     {
+                        Assign("out_var", "in_var"),
                         Assign("wg_var", "wg_var"),
                         Assign("sb_var", "sb_var"),
                         Assign("priv_var", "priv_var"),
@@ -996,16 +999,20 @@
   EXPECT_TRUE(func_sem->ReturnType()->Is<sem::Void>());
 
   const auto& vars = func_sem->ReferencedModuleVariables();
-  ASSERT_EQ(vars.size(), 3u);
-  EXPECT_EQ(vars[0]->Declaration(), wg_var);
-  EXPECT_EQ(vars[1]->Declaration(), sb_var);
-  EXPECT_EQ(vars[2]->Declaration(), priv_var);
+  ASSERT_EQ(vars.size(), 5u);
+  EXPECT_EQ(vars[0]->Declaration(), out_var);
+  EXPECT_EQ(vars[1]->Declaration(), in_var);
+  EXPECT_EQ(vars[2]->Declaration(), wg_var);
+  EXPECT_EQ(vars[3]->Declaration(), sb_var);
+  EXPECT_EQ(vars[4]->Declaration(), priv_var);
 }
 
 TEST_F(ResolverTest, Function_RegisterInputOutputVariables_SubFunction) {
   auto* s = Structure("S", {Member("m", ty.u32())},
                       {create<ast::StructBlockDecoration>()});
 
+  auto* in_var = Global("in_var", ty.f32(), ast::StorageClass::kInput);
+  auto* out_var = Global("out_var", ty.f32(), ast::StorageClass::kOutput);
   auto* sb_var = Global("sb_var", ty.Of(s), ast::StorageClass::kStorage,
                         ast::Access::kReadWrite,
                         ast::DecorationList{
@@ -1016,13 +1023,14 @@
   auto* priv_var = Global("priv_var", ty.f32(), ast::StorageClass::kPrivate);
 
   Func("my_func", ast::VariableList{}, ty.f32(),
-       {Assign("wg_var", "wg_var"), Assign("sb_var", "sb_var"),
-        Assign("priv_var", "priv_var"), Return(0.0f)},
+       {Assign("out_var", "in_var"), Assign("wg_var", "wg_var"),
+        Assign("sb_var", "sb_var"), Assign("priv_var", "priv_var"),
+        Return(0.0f)},
        ast::DecorationList{});
 
   auto* func2 = Func("func", ast::VariableList{}, ty.void_(),
                      {
-                         WrapInStatement(Call("my_func")),
+                         Assign("out_var", Call("my_func")),
                      },
                      ast::DecorationList{});
 
@@ -1033,10 +1041,12 @@
   EXPECT_EQ(func2_sem->Parameters().size(), 0u);
 
   const auto& vars = func2_sem->ReferencedModuleVariables();
-  ASSERT_EQ(vars.size(), 3u);
-  EXPECT_EQ(vars[0]->Declaration(), wg_var);
-  EXPECT_EQ(vars[1]->Declaration(), sb_var);
-  EXPECT_EQ(vars[2]->Declaration(), priv_var);
+  ASSERT_EQ(vars.size(), 5u);
+  EXPECT_EQ(vars[0]->Declaration(), out_var);
+  EXPECT_EQ(vars[1]->Declaration(), in_var);
+  EXPECT_EQ(vars[2]->Declaration(), wg_var);
+  EXPECT_EQ(vars[3]->Declaration(), sb_var);
+  EXPECT_EQ(vars[4]->Declaration(), priv_var);
 }
 
 TEST_F(ResolverTest, Function_NotRegisterFunctionVariable) {
@@ -1275,7 +1285,7 @@
 TEST_F(ResolverTest, Expr_MemberAccessor_Struct) {
   auto* st = Structure("S", {Member("first_member", ty.i32()),
                              Member("second_member", ty.f32())});
-  Global("my_struct", ty.Of(st), ast::StorageClass::kPrivate);
+  Global("my_struct", ty.Of(st), ast::StorageClass::kInput);
 
   auto* mem = MemberAccessor("my_struct", "second_member");
   WrapInFunction(mem);
@@ -1299,7 +1309,7 @@
   auto* st = Structure("S", {Member("first_member", ty.i32()),
                              Member("second_member", ty.f32())});
   auto* alias = Alias("alias", ty.Of(st));
-  Global("my_struct", ty.Of(alias), ast::StorageClass::kPrivate);
+  Global("my_struct", ty.Of(alias), ast::StorageClass::kInput);
 
   auto* mem = MemberAccessor("my_struct", "second_member");
   WrapInFunction(mem);
@@ -1318,7 +1328,7 @@
 }
 
 TEST_F(ResolverTest, Expr_MemberAccessor_VectorSwizzle) {
-  Global("my_vec", ty.vec4<f32>(), ast::StorageClass::kPrivate);
+  Global("my_vec", ty.vec4<f32>(), ast::StorageClass::kInput);
 
   auto* mem = MemberAccessor("my_vec", "xzyw");
   WrapInFunction(mem);
@@ -1335,7 +1345,7 @@
 }
 
 TEST_F(ResolverTest, Expr_MemberAccessor_VectorSwizzle_SingleElement) {
-  Global("my_vec", ty.vec3<f32>(), ast::StorageClass::kPrivate);
+  Global("my_vec", ty.vec3<f32>(), ast::StorageClass::kInput);
 
   auto* mem = MemberAccessor("my_vec", "b");
   WrapInFunction(mem);
@@ -1379,7 +1389,7 @@
 
   auto* stB = Structure("B", {Member("foo", ty.vec4<f32>())});
   auto* stA = Structure("A", {Member("mem", ty.vec(ty.Of(stB), 3))});
-  Global("c", ty.Of(stA), ast::StorageClass::kPrivate);
+  Global("c", ty.Of(stA), ast::StorageClass::kInput);
 
   auto* mem = MemberAccessor(
       MemberAccessor(IndexAccessor(MemberAccessor("c", "mem"), 0), "foo"),
@@ -1398,7 +1408,7 @@
 TEST_F(ResolverTest, Expr_MemberAccessor_InBinaryOp) {
   auto* st = Structure("S", {Member("first_member", ty.f32()),
                              Member("second_member", ty.f32())});
-  Global("my_struct", ty.Of(st), ast::StorageClass::kPrivate);
+  Global("my_struct", ty.Of(st), ast::StorageClass::kInput);
 
   auto* expr = Add(MemberAccessor("my_struct", "first_member"),
                    MemberAccessor("my_struct", "second_member"));
@@ -1704,8 +1714,8 @@
      << FriendlyName(rhs_type);
   SCOPED_TRACE(ss.str());
 
-  Global("lhs", lhs_type, ast::StorageClass::kPrivate);
-  Global("rhs", rhs_type, ast::StorageClass::kPrivate);
+  Global("lhs", lhs_type, ast::StorageClass::kInput);
+  Global("rhs", rhs_type, ast::StorageClass::kInput);
 
   auto* expr =
       create<ast::BinaryExpression>(params.op, Expr("lhs"), Expr("rhs"));
@@ -1746,8 +1756,8 @@
      << " " << FriendlyName(rhs_type);
   SCOPED_TRACE(ss.str());
 
-  Global("lhs", lhs_type, ast::StorageClass::kPrivate);
-  Global("rhs", rhs_type, ast::StorageClass::kPrivate);
+  Global("lhs", lhs_type, ast::StorageClass::kInput);
+  Global("rhs", rhs_type, ast::StorageClass::kInput);
 
   auto* expr =
       create<ast::BinaryExpression>(params.op, Expr("lhs"), Expr("rhs"));
@@ -1798,8 +1808,8 @@
   ss << FriendlyName(lhs_type) << " " << op << " " << FriendlyName(rhs_type);
   SCOPED_TRACE(ss.str());
 
-  Global("lhs", lhs_type, ast::StorageClass::kPrivate);
-  Global("rhs", rhs_type, ast::StorageClass::kPrivate);
+  Global("lhs", lhs_type, ast::StorageClass::kInput);
+  Global("rhs", rhs_type, ast::StorageClass::kInput);
 
   auto* expr = create<ast::BinaryExpression>(Source{{12, 34}}, op, Expr("lhs"),
                                              Expr("rhs"));
@@ -1844,8 +1854,8 @@
     is_valid_expr = vec_size == mat_cols;
   }
 
-  Global("lhs", lhs_type, ast::StorageClass::kPrivate);
-  Global("rhs", rhs_type, ast::StorageClass::kPrivate);
+  Global("lhs", lhs_type, ast::StorageClass::kInput);
+  Global("rhs", rhs_type, ast::StorageClass::kInput);
 
   auto* expr = Mul(Source{{12, 34}}, Expr("lhs"), Expr("rhs"));
   WrapInFunction(expr);
@@ -1885,8 +1895,8 @@
   auto* col = create<sem::Vector>(f32, lhs_mat_rows);
   auto* result_type = create<sem::Matrix>(col, rhs_mat_cols);
 
-  Global("lhs", lhs_type, ast::StorageClass::kPrivate);
-  Global("rhs", rhs_type, ast::StorageClass::kPrivate);
+  Global("lhs", lhs_type, ast::StorageClass::kInput);
+  Global("rhs", rhs_type, ast::StorageClass::kInput);
 
   auto* expr = Mul(Source{{12, 34}}, Expr("lhs"), Expr("rhs"));
   WrapInFunction(expr);
@@ -1917,7 +1927,7 @@
 TEST_P(UnaryOpExpressionTest, Expr_UnaryOp) {
   auto op = GetParam();
 
-  Global("ident", ty.vec4<f32>(), ast::StorageClass::kPrivate);
+  Global("ident", ty.vec4<f32>(), ast::StorageClass::kInput);
   auto* der = create<ast::UnaryOpExpression>(op, Expr("ident"));
   WrapInFunction(der);
 
diff --git a/src/resolver/storage_class_validation_test.cc b/src/resolver/storage_class_validation_test.cc
index 5c6ca3d..84e7463 100644
--- a/src/resolver/storage_class_validation_test.cc
+++ b/src/resolver/storage_class_validation_test.cc
@@ -50,8 +50,9 @@
 }
 
 TEST_F(ResolverStorageClassValidationTest, StorageBufferPointer) {
-  // var<storage> g : vec4<f32>;
-  Global(Source{{56, 78}}, "g", ty.vec4<f32>(), ast::StorageClass::kStorage,
+  // var<storage> g : ptr<i32, input>;
+  Global(Source{{56, 78}}, "g", ty.pointer<i32>(ast::StorageClass::kInput),
+         ast::StorageClass::kStorage,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
              create<ast::GroupDecoration>(0),
@@ -186,8 +187,9 @@
 }
 
 TEST_F(ResolverStorageClassValidationTest, UniformBufferPointer) {
-  // var<uniform> g : vec4<f32>;
-  Global(Source{{56, 78}}, "g", ty.vec4<f32>(), ast::StorageClass::kUniform,
+  // var<uniform> g : ptr<i32, input>;
+  Global(Source{{56, 78}}, "g", ty.pointer<i32>(ast::StorageClass::kInput),
+         ast::StorageClass::kUniform,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
              create<ast::GroupDecoration>(0),
diff --git a/src/resolver/type_validation_test.cc b/src/resolver/type_validation_test.cc
index b1aea9c..d1b1c35 100644
--- a/src/resolver/type_validation_test.cc
+++ b/src/resolver/type_validation_test.cc
@@ -85,17 +85,17 @@
 }
 
 TEST_F(ResolverTypeValidationTest, GlobalVariableWithStorageClass_Pass) {
-  // var<private> global_var: f32;
-  Global(Source{{12, 34}}, "global_var", ty.f32(), ast::StorageClass::kPrivate);
+  // var<in> global_var: f32;
+  Global(Source{{12, 34}}, "global_var", ty.f32(), ast::StorageClass::kInput);
 
   EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
 
 TEST_F(ResolverTypeValidationTest, GlobalConstantWithStorageClass_Fail) {
-  // const<private> global_var: f32;
+  // const<in> global_var: f32;
   AST().AddGlobalVariable(create<ast::Variable>(
       Source{{12, 34}}, Symbols().Register("global_var"),
-      ast::StorageClass::kPrivate, ast::Access::kUndefined, ty.f32(), true,
+      ast::StorageClass::kInput, ast::Access::kUndefined, ty.f32(), true,
       Expr(1.23f), ast::DecorationList{}));
 
   EXPECT_FALSE(r()->Resolve());
diff --git a/src/resolver/validation_test.cc b/src/resolver/validation_test.cc
index d288c04..600a588 100644
--- a/src/resolver/validation_test.cc
+++ b/src/resolver/validation_test.cc
@@ -349,7 +349,7 @@
 }
 
 TEST_F(ResolverValidationTest, Expr_MemberAccessor_VectorSwizzle_BadChar) {
-  Global("my_vec", ty.vec3<f32>(), ast::StorageClass::kPrivate);
+  Global("my_vec", ty.vec3<f32>(), ast::StorageClass::kInput);
 
   auto* ident = create<ast::IdentifierExpression>(
       Source{{Source::Location{3, 3}, Source::Location{3, 7}}},
@@ -363,7 +363,7 @@
 }
 
 TEST_F(ResolverValidationTest, Expr_MemberAccessor_VectorSwizzle_MixedChars) {
-  Global("my_vec", ty.vec4<f32>(), ast::StorageClass::kPrivate);
+  Global("my_vec", ty.vec4<f32>(), ast::StorageClass::kInput);
 
   auto* ident = create<ast::IdentifierExpression>(
       Source{{Source::Location{3, 3}, Source::Location{3, 7}}},
@@ -379,7 +379,7 @@
 }
 
 TEST_F(ResolverValidationTest, Expr_MemberAccessor_VectorSwizzle_BadLength) {
-  Global("my_vec", ty.vec3<f32>(), ast::StorageClass::kPrivate);
+  Global("my_vec", ty.vec3<f32>(), ast::StorageClass::kInput);
 
   auto* ident = create<ast::IdentifierExpression>(
       Source{{Source::Location{3, 3}, Source::Location{3, 8}}},
@@ -392,7 +392,7 @@
 }
 
 TEST_F(ResolverValidationTest, Expr_MemberAccessor_VectorSwizzle_BadIndex) {
-  Global("my_vec", ty.vec2<f32>(), ast::StorageClass::kPrivate);
+  Global("my_vec", ty.vec2<f32>(), ast::StorageClass::kInput);
 
   auto* ident = create<ast::IdentifierExpression>(Source{{3, 3}},
                                                   Symbols().Register("z"));
@@ -1848,7 +1848,7 @@
 
 TEST_F(ResolverValidationTest, Expr_Constructor_Vector_Alias_Argument_Error) {
   auto* alias = Alias("UnsignedInt", ty.u32());
-  Global("uint_var", ty.Of(alias), ast::StorageClass::kPrivate);
+  Global("uint_var", ty.Of(alias), ast::StorageClass::kInput);
 
   auto* tc = vec2<f32>(Expr(Source{{12, 34}}, "uint_var"));
   WrapInFunction(tc);
@@ -1862,8 +1862,8 @@
 TEST_F(ResolverValidationTest, Expr_Constructor_Vector_Alias_Argument_Success) {
   auto* f32_alias = Alias("Float32", ty.f32());
   auto* vec2_alias = Alias("VectorFloat2", ty.vec2<f32>());
-  Global("my_f32", ty.Of(f32_alias), ast::StorageClass::kPrivate);
-  Global("my_vec2", ty.Of(vec2_alias), ast::StorageClass::kPrivate);
+  Global("my_f32", ty.Of(f32_alias), ast::StorageClass::kInput);
+  Global("my_vec2", ty.Of(vec2_alias), ast::StorageClass::kInput);
 
   auto* tc = vec3<f32>("my_vec2", "my_f32");
   WrapInFunction(tc);
diff --git a/src/transform/msl.cc b/src/transform/msl.cc
index 50e4cc9..6d45462 100644
--- a/src/transform/msl.cc
+++ b/src/transform/msl.cc
@@ -207,7 +207,8 @@
           // scope. Disable storage class validation on this variable.
           auto* disable_validation =
               ctx.dst->ASTNodes().Create<ast::DisableValidationDecoration>(
-                  ctx.dst->ID(), ast::DisabledValidation::kIgnoreStorageClass);
+                  ctx.dst->ID(),
+                  ast::DisabledValidation::kFunctionVarStorageClass);
           auto* constructor = ctx.Clone(var->Declaration()->constructor());
           auto* local_var = ctx.dst->Var(
               new_var_symbol, store_type, var->StorageClass(), constructor,
diff --git a/src/transform/msl_test.cc b/src/transform/msl_test.cc
index cc83854..6b0968c 100644
--- a/src/transform/msl_test.cc
+++ b/src/transform/msl_test.cc
@@ -36,8 +36,8 @@
   auto* expect = R"(
 [[stage(compute)]]
 fn main([[builtin(local_invocation_index)]] local_invocation_index : u32) {
-  [[internal(disable_validation__ignore_storage_class)]] var<workgroup> tint_symbol_1 : f32;
-  [[internal(disable_validation__ignore_storage_class)]] var<private> tint_symbol_2 : f32;
+  [[internal(disable_validation__function_var_storage_class)]] var<workgroup> tint_symbol_1 : f32;
+  [[internal(disable_validation__function_var_storage_class)]] var<private> tint_symbol_2 : f32;
   if ((local_invocation_index == 0u)) {
     tint_symbol_1 = f32();
   }
@@ -93,8 +93,8 @@
 
 [[stage(compute)]]
 fn main([[builtin(local_invocation_index)]] local_invocation_index : u32) {
-  [[internal(disable_validation__ignore_storage_class)]] var<workgroup> tint_symbol_5 : f32;
-  [[internal(disable_validation__ignore_storage_class)]] var<private> tint_symbol_6 : f32;
+  [[internal(disable_validation__function_var_storage_class)]] var<workgroup> tint_symbol_5 : f32;
+  [[internal(disable_validation__function_var_storage_class)]] var<private> tint_symbol_6 : f32;
   if ((local_invocation_index == 0u)) {
     tint_symbol_5 = f32();
   }
@@ -122,8 +122,8 @@
   auto* expect = R"(
 [[stage(compute)]]
 fn main() {
-  [[internal(disable_validation__ignore_storage_class)]] var<private> tint_symbol : f32 = 1.0;
-  [[internal(disable_validation__ignore_storage_class)]] var<private> tint_symbol_1 : f32 = f32();
+  [[internal(disable_validation__function_var_storage_class)]] var<private> tint_symbol : f32 = 1.0;
+  [[internal(disable_validation__function_var_storage_class)]] var<private> tint_symbol_1 : f32 = f32();
   let x : f32 = (tint_symbol + tint_symbol_1);
 }
 )";
@@ -150,8 +150,8 @@
   auto* expect = R"(
 [[stage(compute)]]
 fn main([[builtin(local_invocation_index)]] local_invocation_index : u32) {
-  [[internal(disable_validation__ignore_storage_class)]] var<workgroup> tint_symbol_1 : f32;
-  [[internal(disable_validation__ignore_storage_class)]] var<private> tint_symbol_2 : f32;
+  [[internal(disable_validation__function_var_storage_class)]] var<workgroup> tint_symbol_1 : f32;
+  [[internal(disable_validation__function_var_storage_class)]] var<private> tint_symbol_2 : f32;
   if ((local_invocation_index == 0u)) {
     tint_symbol_1 = f32();
   }
diff --git a/src/transform/spirv.cc b/src/transform/spirv.cc
index 874c114..81fdbb9 100644
--- a/src/transform/spirv.cc
+++ b/src/transform/spirv.cc
@@ -18,7 +18,6 @@
 #include <utility>
 
 #include "src/ast/call_statement.h"
-#include "src/ast/disable_validation_decoration.h"
 #include "src/ast/return_statement.h"
 #include "src/ast/stage_decoration.h"
 #include "src/program_builder.h"
@@ -273,10 +272,7 @@
   Symbol pointsize = ctx.dst->Symbols().New("tint_pointsize");
   ctx.dst->Global(
       pointsize, ctx.dst->ty.f32(), ast::StorageClass::kOutput,
-      ast::DecorationList{
-          ctx.dst->Builtin(ast::Builtin::kPointSize),
-          ctx.dst->ASTNodes().Create<ast::DisableValidationDecoration>(
-              ctx.dst->ID(), ast::DisabledValidation::kIgnoreStorageClass)});
+      ast::DecorationList{ctx.dst->Builtin(ast::Builtin::kPointSize)});
 
   // Assign 1.0 to the global at the start of all vertex shader entry points.
   ctx.ReplaceAll([&ctx, pointsize](ast::Function* func) -> ast::Function* {
@@ -313,9 +309,6 @@
           return !deco->IsAnyOf<ast::BuiltinDecoration,
                                 ast::LocationDecoration>();
         });
-    new_decorations.push_back(
-        ctx.dst->ASTNodes().Create<ast::DisableValidationDecoration>(
-            ctx.dst->ID(), ast::DisabledValidation::kIgnoreStorageClass));
     auto global_var_symbol = ctx.dst->Sym();
     auto* global_var =
         ctx.dst->Var(global_var_symbol, ctx.Clone(declared_ty),
@@ -370,9 +363,6 @@
           return !deco->IsAnyOf<ast::BuiltinDecoration,
                                 ast::LocationDecoration>();
         });
-    new_decorations.push_back(
-        ctx.dst->ASTNodes().Create<ast::DisableValidationDecoration>(
-            ctx.dst->ID(), ast::DisabledValidation::kIgnoreStorageClass));
     auto global_var_symbol = ctx.dst->Sym();
     auto* global_var =
         ctx.dst->Var(global_var_symbol, ctx.Clone(declared_ty),
diff --git a/src/transform/spirv_test.cc b/src/transform/spirv_test.cc
index cf15d8c..150167a 100644
--- a/src/transform/spirv_test.cc
+++ b/src/transform/spirv_test.cc
@@ -38,18 +38,18 @@
 )";
 
   auto* expect = R"(
-[[builtin(position), internal(disable_validation__ignore_storage_class)]] var<in> tint_symbol : vec4<f32>;
+[[builtin(position)]] var<in> tint_symbol : vec4<f32>;
 
-[[location(1), internal(disable_validation__ignore_storage_class)]] var<in> tint_symbol_1 : f32;
+[[location(1)]] var<in> tint_symbol_1 : f32;
 
 [[stage(fragment)]]
 fn frag_main() {
   var col : f32 = (tint_symbol.x * tint_symbol_1);
 }
 
-[[builtin(local_invocation_id), internal(disable_validation__ignore_storage_class)]] var<in> tint_symbol_2 : vec3<u32>;
+[[builtin(local_invocation_id)]] var<in> tint_symbol_2 : vec3<u32>;
 
-[[builtin(local_invocation_index), internal(disable_validation__ignore_storage_class)]] var<in> tint_symbol_3 : u32;
+[[builtin(local_invocation_index)]] var<in> tint_symbol_3 : u32;
 
 [[stage(compute)]]
 fn compute_main() {
@@ -74,7 +74,7 @@
   auto* expect = R"(
 type myf32 = f32;
 
-[[location(1), internal(disable_validation__ignore_storage_class)]] var<in> tint_symbol : myf32;
+[[location(1)]] var<in> tint_symbol : myf32;
 
 [[stage(fragment)]]
 fn frag_main() {
@@ -95,7 +95,7 @@
 )";
 
   auto* expect = R"(
-[[builtin(position), internal(disable_validation__ignore_storage_class)]] var<out> tint_symbol_1 : vec4<f32>;
+[[builtin(position)]] var<out> tint_symbol_1 : vec4<f32>;
 
 fn tint_symbol_2(tint_symbol : vec4<f32>) {
   tint_symbol_1 = tint_symbol;
@@ -125,9 +125,9 @@
 )";
 
   auto* expect = R"(
-[[location(0), internal(disable_validation__ignore_storage_class)]] var<in> tint_symbol : u32;
+[[location(0)]] var<in> tint_symbol : u32;
 
-[[location(0), internal(disable_validation__ignore_storage_class)]] var<out> tint_symbol_2 : f32;
+[[location(0)]] var<out> tint_symbol_2 : f32;
 
 fn tint_symbol_3(tint_symbol_1 : f32) {
   tint_symbol_2 = tint_symbol_1;
@@ -165,9 +165,9 @@
   auto* expect = R"(
 type myf32 = f32;
 
-[[location(0), internal(disable_validation__ignore_storage_class)]] var<in> tint_symbol : u32;
+[[location(0)]] var<in> tint_symbol : u32;
 
-[[location(0), internal(disable_validation__ignore_storage_class)]] var<out> tint_symbol_2 : myf32;
+[[location(0)]] var<out> tint_symbol_2 : myf32;
 
 fn tint_symbol_3(tint_symbol_1 : myf32) {
   tint_symbol_2 = tint_symbol_1;
@@ -208,9 +208,9 @@
   value : f32;
 };
 
-[[builtin(position), internal(disable_validation__ignore_storage_class)]] var<in> tint_symbol : vec4<f32>;
+[[builtin(position)]] var<in> tint_symbol : vec4<f32>;
 
-[[location(1), internal(disable_validation__ignore_storage_class)]] var<in> tint_symbol_1 : f32;
+[[location(1)]] var<in> tint_symbol_1 : f32;
 
 [[stage(fragment)]]
 fn frag_main() {
@@ -240,7 +240,7 @@
   value : f32;
 };
 
-[[location(1), internal(disable_validation__ignore_storage_class)]] var<in> tint_symbol : f32;
+[[location(1)]] var<in> tint_symbol : f32;
 
 [[stage(fragment)]]
 fn frag_main() {
@@ -275,9 +275,9 @@
   value : f32;
 };
 
-[[builtin(position), internal(disable_validation__ignore_storage_class)]] var<out> tint_symbol_1 : vec4<f32>;
+[[builtin(position)]] var<out> tint_symbol_1 : vec4<f32>;
 
-[[location(1), internal(disable_validation__ignore_storage_class)]] var<out> tint_symbol_2 : f32;
+[[location(1)]] var<out> tint_symbol_2 : f32;
 
 fn tint_symbol_3(tint_symbol : VertexOutput) {
   tint_symbol_1 = tint_symbol.pos;
@@ -318,9 +318,9 @@
   value : f32;
 };
 
-[[location(1), internal(disable_validation__ignore_storage_class)]] var<in> tint_symbol : f32;
+[[location(1)]] var<in> tint_symbol : f32;
 
-[[location(1), internal(disable_validation__ignore_storage_class)]] var<out> tint_symbol_3 : f32;
+[[location(1)]] var<out> tint_symbol_3 : f32;
 
 fn tint_symbol_4(tint_symbol_2 : Interface) {
   tint_symbol_3 = tint_symbol_2.value;
@@ -363,9 +363,9 @@
   value : f32;
 };
 
-[[builtin(position), internal(disable_validation__ignore_storage_class)]] var<out> tint_symbol_1 : vec4<f32>;
+[[builtin(position)]] var<out> tint_symbol_1 : vec4<f32>;
 
-[[location(1), internal(disable_validation__ignore_storage_class)]] var<out> tint_symbol_2 : f32;
+[[location(1)]] var<out> tint_symbol_2 : f32;
 
 fn tint_symbol_3(tint_symbol : Interface) {
   tint_symbol_1 = tint_symbol.pos;
@@ -378,9 +378,9 @@
   return;
 }
 
-[[builtin(position), internal(disable_validation__ignore_storage_class)]] var<in> tint_symbol_4 : vec4<f32>;
+[[builtin(position)]] var<in> tint_symbol_4 : vec4<f32>;
 
-[[location(1), internal(disable_validation__ignore_storage_class)]] var<in> tint_symbol_5 : f32;
+[[location(1)]] var<in> tint_symbol_5 : f32;
 
 [[stage(fragment)]]
 fn frag_main() {
@@ -426,11 +426,11 @@
   value : f32;
 };
 
-[[location(1), internal(disable_validation__ignore_storage_class)]] var<in> tint_symbol : f32;
+[[location(1)]] var<in> tint_symbol : f32;
 
-[[builtin(position), internal(disable_validation__ignore_storage_class)]] var<in> tint_symbol_1 : vec4<f32>;
+[[builtin(position)]] var<in> tint_symbol_1 : vec4<f32>;
 
-[[location(1), internal(disable_validation__ignore_storage_class)]] var<out> tint_symbol_4 : f32;
+[[location(1)]] var<out> tint_symbol_4 : f32;
 
 fn tint_symbol_5(tint_symbol_3 : FragmentOutput) {
   tint_symbol_4 = tint_symbol_3.value;
@@ -472,7 +472,7 @@
   Position : vec4<f32>;
 };
 
-[[builtin(position), internal(disable_validation__ignore_storage_class)]] var<out> tint_symbol_1 : vec4<f32>;
+[[builtin(position)]] var<out> tint_symbol_1 : vec4<f32>;
 
 fn tint_symbol_2(tint_symbol : VertexOutput) {
   tint_symbol_1 = tint_symbol.Position;
@@ -501,11 +501,11 @@
 )";
 
   auto* expect = R"(
-[[builtin(sample_index), internal(disable_validation__ignore_storage_class)]] var<in> tint_symbol : u32;
+[[builtin(sample_index)]] var<in> tint_symbol : u32;
 
-[[builtin(sample_mask), internal(disable_validation__ignore_storage_class)]] var<in> tint_symbol_1 : array<u32, 1>;
+[[builtin(sample_mask)]] var<in> tint_symbol_1 : array<u32, 1>;
 
-[[builtin(sample_mask), internal(disable_validation__ignore_storage_class)]] var<out> tint_symbol_3 : array<u32, 1>;
+[[builtin(sample_mask)]] var<out> tint_symbol_3 : array<u32, 1>;
 
 fn tint_symbol_4(tint_symbol_2 : u32) {
   tint_symbol_3[0] = tint_symbol_2;
@@ -549,9 +549,9 @@
   return input;
 }
 
-[[builtin(sample_mask), internal(disable_validation__ignore_storage_class)]] var<in> tint_symbol : array<u32, 1>;
+[[builtin(sample_mask)]] var<in> tint_symbol : array<u32, 1>;
 
-[[builtin(sample_mask), internal(disable_validation__ignore_storage_class)]] var<out> tint_symbol_2 : array<u32, 1>;
+[[builtin(sample_mask)]] var<out> tint_symbol_2 : array<u32, 1>;
 
 fn tint_symbol_3(tint_symbol_1 : u32) {
   tint_symbol_2[0] = tint_symbol_1;
@@ -582,12 +582,12 @@
 )";
 
   auto* expect = R"(
-[[builtin(pointsize), internal(disable_validation__ignore_storage_class)]] var<out> tint_pointsize : f32;
+[[builtin(pointsize)]] var<out> tint_pointsize : f32;
 
 fn non_entry_point() {
 }
 
-[[builtin(position), internal(disable_validation__ignore_storage_class)]] var<out> tint_symbol_1 : vec4<f32>;
+[[builtin(position)]] var<out> tint_symbol_1 : vec4<f32>;
 
 fn tint_symbol_2(tint_symbol : vec4<f32>) {
   tint_symbol_1 = tint_symbol;
@@ -628,9 +628,9 @@
 )";
 
   auto* expect = R"(
-[[builtin(pointsize), internal(disable_validation__ignore_storage_class)]] var<out> tint_pointsize : f32;
+[[builtin(pointsize)]] var<out> tint_pointsize : f32;
 
-[[builtin(position), internal(disable_validation__ignore_storage_class)]] var<out> tint_symbol_1 : vec4<f32>;
+[[builtin(position)]] var<out> tint_symbol_1 : vec4<f32>;
 
 fn tint_symbol_2(tint_symbol : vec4<f32>) {
   tint_symbol_1 = tint_symbol;
@@ -643,7 +643,7 @@
   return;
 }
 
-[[builtin(position), internal(disable_validation__ignore_storage_class)]] var<out> tint_symbol_4 : vec4<f32>;
+[[builtin(position)]] var<out> tint_symbol_4 : vec4<f32>;
 
 fn tint_symbol_5(tint_symbol_3 : vec4<f32>) {
   tint_symbol_4 = tint_symbol_3;
@@ -656,7 +656,7 @@
   return;
 }
 
-[[builtin(position), internal(disable_validation__ignore_storage_class)]] var<out> tint_symbol_7 : vec4<f32>;
+[[builtin(position)]] var<out> tint_symbol_7 : vec4<f32>;
 
 fn tint_symbol_8(tint_symbol_6 : vec4<f32>) {
   tint_symbol_7 = tint_symbol_6;
@@ -722,9 +722,9 @@
 )";
 
   auto* expect = R"(
-[[builtin(pointsize), internal(disable_validation__ignore_storage_class)]] var<out> tint_pointsize : f32;
+[[builtin(pointsize)]] var<out> tint_pointsize : f32;
 
-[[builtin(position), internal(disable_validation__ignore_storage_class)]] var<out> tint_symbol_1 : vec4<f32>;
+[[builtin(position)]] var<out> tint_symbol_1 : vec4<f32>;
 
 fn tint_symbol_2(tint_symbol : vec4<f32>) {
   tint_symbol_1 = tint_symbol;
@@ -737,11 +737,11 @@
   return;
 }
 
-[[builtin(sample_index), internal(disable_validation__ignore_storage_class)]] var<in> tint_symbol_3 : u32;
+[[builtin(sample_index)]] var<in> tint_symbol_3 : u32;
 
-[[builtin(sample_mask), internal(disable_validation__ignore_storage_class)]] var<in> tint_symbol_4 : array<u32, 1>;
+[[builtin(sample_mask)]] var<in> tint_symbol_4 : array<u32, 1>;
 
-[[builtin(sample_mask), internal(disable_validation__ignore_storage_class)]] var<out> tint_symbol_6 : array<u32, 1>;
+[[builtin(sample_mask)]] var<out> tint_symbol_6 : array<u32, 1>;
 
 fn tint_symbol_7(tint_symbol_5 : u32) {
   tint_symbol_6[0] = tint_symbol_5;
diff --git a/src/writer/append_vector_test.cc b/src/writer/append_vector_test.cc
index 8c045b0..eb5053b 100644
--- a/src/writer/append_vector_test.cc
+++ b/src/writer/append_vector_test.cc
@@ -87,7 +87,7 @@
 }
 
 TEST_F(AppendVectorTest, Vec2i32Var_i32) {
-  Global("vec_12", ty.vec2<i32>(), ast::StorageClass::kPrivate);
+  Global("vec_12", ty.vec2<i32>(), ast::StorageClass::kInput);
   auto* vec_12 = Expr("vec_12");
   auto* scalar_3 = Expr(3);
   WrapInFunction(vec_12, scalar_3);
@@ -104,7 +104,7 @@
 }
 
 TEST_F(AppendVectorTest, Vec2i32_i32Var) {
-  Global("scalar_3", ty.i32(), ast::StorageClass::kPrivate);
+  Global("scalar_3", ty.i32(), ast::StorageClass::kInput);
   auto* scalar_1 = Expr(1);
   auto* scalar_2 = Expr(2);
   auto* scalar_3 = Expr("scalar_3");
@@ -124,8 +124,8 @@
 }
 
 TEST_F(AppendVectorTest, Vec2i32Var_i32Var) {
-  Global("vec_12", ty.vec2<i32>(), ast::StorageClass::kPrivate);
-  Global("scalar_3", ty.i32(), ast::StorageClass::kPrivate);
+  Global("vec_12", ty.vec2<i32>(), ast::StorageClass::kInput);
+  Global("scalar_3", ty.i32(), ast::StorageClass::kInput);
   auto* vec_12 = Expr("vec_12");
   auto* scalar_3 = Expr("scalar_3");
   WrapInFunction(vec_12, scalar_3);
@@ -142,8 +142,8 @@
 }
 
 TEST_F(AppendVectorTest, Vec2i32Var_f32Var) {
-  Global("vec_12", ty.vec2<i32>(), ast::StorageClass::kPrivate);
-  Global("scalar_3", ty.f32(), ast::StorageClass::kPrivate);
+  Global("vec_12", ty.vec2<i32>(), ast::StorageClass::kInput);
+  Global("scalar_3", ty.f32(), ast::StorageClass::kInput);
   auto* vec_12 = Expr("vec_12");
   auto* scalar_3 = Expr("scalar_3");
   WrapInFunction(vec_12, scalar_3);
@@ -163,8 +163,8 @@
 }
 
 TEST_F(AppendVectorTest, Vec2boolVar_boolVar) {
-  Global("vec_12", ty.vec2<bool>(), ast::StorageClass::kPrivate);
-  Global("scalar_3", ty.bool_(), ast::StorageClass::kPrivate);
+  Global("vec_12", ty.vec2<bool>(), ast::StorageClass::kInput);
+  Global("scalar_3", ty.bool_(), ast::StorageClass::kInput);
   auto* vec_12 = Expr("vec_12");
   auto* scalar_3 = Expr("scalar_3");
   WrapInFunction(vec_12, scalar_3);
diff --git a/src/writer/spirv/builder.cc b/src/writer/spirv/builder.cc
index 62caefd..463e822 100644
--- a/src/writer/spirv/builder.cc
+++ b/src/writer/spirv/builder.cc
@@ -20,7 +20,6 @@
 #include "spirv/unified1/GLSL.std.450.h"
 #include "src/ast/call_statement.h"
 #include "src/ast/fallthrough_statement.h"
-#include "src/ast/internal_decoration.h"
 #include "src/ast/override_decoration.h"
 #include "src/sem/array.h"
 #include "src/sem/atomic_type.h"
@@ -844,7 +843,7 @@
                                        Operand::Int(group->value())});
     } else if (deco->Is<ast::OverrideDecoration>()) {
       // Spec constants are handled elsewhere
-    } else if (!deco->Is<ast::InternalDecoration>()) {
+    } else {
       error_ = "unknown decoration";
       return false;
     }
diff --git a/src/writer/spirv/builder_assign_test.cc b/src/writer/spirv/builder_assign_test.cc
index f611143..c1894b6 100644
--- a/src/writer/spirv/builder_assign_test.cc
+++ b/src/writer/spirv/builder_assign_test.cc
@@ -23,7 +23,7 @@
 using BuilderTest = TestHelper;
 
 TEST_F(BuilderTest, Assign_Var) {
-  auto* v = Global("var", ty.f32(), ast::StorageClass::kPrivate);
+  auto* v = Global("var", ty.f32(), ast::StorageClass::kOutput);
 
   auto* assign = Assign("var", 1.f);
 
@@ -39,9 +39,9 @@
   EXPECT_FALSE(b.has_error());
 
   EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
-%2 = OpTypePointer Private %3
+%2 = OpTypePointer Output %3
 %4 = OpConstantNull %3
-%1 = OpVariable %2 Private %4
+%1 = OpVariable %2 Output %4
 %5 = OpConstant %3 1
 )");
 
@@ -51,7 +51,7 @@
 }
 
 TEST_F(BuilderTest, Assign_Var_OutsideFunction_IsError) {
-  auto* v = Global("var", ty.f32(), ast::StorageClass::kPrivate);
+  auto* v = Global("var", ty.f32(), ast::StorageClass::kOutput);
 
   auto* assign = Assign("var", Expr(1.f));
 
@@ -70,7 +70,7 @@
 }
 
 TEST_F(BuilderTest, Assign_Var_ZeroConstructor) {
-  auto* v = Global("var", ty.vec3<f32>(), ast::StorageClass::kPrivate);
+  auto* v = Global("var", ty.vec3<f32>(), ast::StorageClass::kOutput);
 
   auto* val = vec3<f32>();
   auto* assign = Assign("var", val);
@@ -88,9 +88,9 @@
 
   EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
 %3 = OpTypeVector %4 3
-%2 = OpTypePointer Private %3
+%2 = OpTypePointer Output %3
 %5 = OpConstantNull %3
-%1 = OpVariable %2 Private %5
+%1 = OpVariable %2 Output %5
 )");
 
   EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
@@ -101,7 +101,7 @@
 TEST_F(BuilderTest, Assign_Var_Complex_ConstructorWithExtract) {
   auto* init = vec3<f32>(vec2<f32>(1.f, 2.f), 3.f);
 
-  auto* v = Global("var", ty.vec3<f32>(), ast::StorageClass::kPrivate);
+  auto* v = Global("var", ty.vec3<f32>(), ast::StorageClass::kOutput);
 
   auto* assign = Assign("var", init);
 
@@ -118,9 +118,9 @@
 
   EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
 %3 = OpTypeVector %4 3
-%2 = OpTypePointer Private %3
+%2 = OpTypePointer Output %3
 %5 = OpConstantNull %3
-%1 = OpVariable %2 Private %5
+%1 = OpVariable %2 Output %5
 %6 = OpTypeVector %4 2
 %7 = OpConstant %4 1
 %8 = OpConstant %4 2
@@ -138,7 +138,7 @@
 TEST_F(BuilderTest, Assign_Var_Complex_Constructor) {
   auto* init = vec3<f32>(1.f, 2.f, 3.f);
 
-  auto* v = Global("var", ty.vec3<f32>(), ast::StorageClass::kPrivate);
+  auto* v = Global("var", ty.vec3<f32>(), ast::StorageClass::kOutput);
 
   auto* assign = Assign("var", init);
 
@@ -155,9 +155,9 @@
 
   EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
 %3 = OpTypeVector %4 3
-%2 = OpTypePointer Private %3
+%2 = OpTypePointer Output %3
 %5 = OpConstantNull %3
-%1 = OpVariable %2 Private %5
+%1 = OpVariable %2 Output %5
 %6 = OpConstant %4 1
 %7 = OpConstant %4 2
 %8 = OpConstant %4 3
@@ -213,7 +213,7 @@
 }
 
 TEST_F(BuilderTest, Assign_Vector) {
-  auto* v = Global("var", ty.vec3<f32>(), ast::StorageClass::kPrivate);
+  auto* v = Global("var", ty.vec3<f32>(), ast::StorageClass::kOutput);
 
   auto* val = vec3<f32>(1.f, 1.f, 3.f);
   auto* assign = Assign("var", val);
@@ -231,9 +231,9 @@
 
   EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
 %3 = OpTypeVector %4 3
-%2 = OpTypePointer Private %3
+%2 = OpTypePointer Output %3
 %5 = OpConstantNull %3
-%1 = OpVariable %2 Private %5
+%1 = OpVariable %2 Output %5
 %6 = OpConstant %4 1
 %7 = OpConstant %4 3
 %8 = OpConstantComposite %3 %6 %6 %7
@@ -247,7 +247,7 @@
 TEST_F(BuilderTest, Assign_Vector_MemberByName) {
   // var.y = 1
 
-  auto* v = Global("var", ty.vec3<f32>(), ast::StorageClass::kPrivate);
+  auto* v = Global("var", ty.vec3<f32>(), ast::StorageClass::kOutput);
 
   auto* assign = Assign(MemberAccessor("var", "y"), Expr(1.f));
 
@@ -264,12 +264,12 @@
 
   EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
 %3 = OpTypeVector %4 3
-%2 = OpTypePointer Private %3
+%2 = OpTypePointer Output %3
 %5 = OpConstantNull %3
-%1 = OpVariable %2 Private %5
+%1 = OpVariable %2 Output %5
 %6 = OpTypeInt 32 0
 %7 = OpConstant %6 1
-%8 = OpTypePointer Private %4
+%8 = OpTypePointer Output %4
 %10 = OpConstant %4 1
 )");
 
@@ -282,7 +282,7 @@
 TEST_F(BuilderTest, Assign_Vector_MemberByIndex) {
   // var[1] = 1
 
-  auto* v = Global("var", ty.vec3<f32>(), ast::StorageClass::kPrivate);
+  auto* v = Global("var", ty.vec3<f32>(), ast::StorageClass::kOutput);
 
   auto* assign = Assign(IndexAccessor("var", 1), Expr(1.f));
 
@@ -299,12 +299,12 @@
 
   EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
 %3 = OpTypeVector %4 3
-%2 = OpTypePointer Private %3
+%2 = OpTypePointer Output %3
 %5 = OpConstantNull %3
-%1 = OpVariable %2 Private %5
+%1 = OpVariable %2 Output %5
 %6 = OpTypeInt 32 1
 %7 = OpConstant %6 1
-%8 = OpTypePointer Private %4
+%8 = OpTypePointer Output %4
 %10 = OpConstant %4 1
 )");
 
diff --git a/src/writer/spirv/builder_function_decoration_test.cc b/src/writer/spirv/builder_function_decoration_test.cc
index 21bb928..c2f8b5e 100644
--- a/src/writer/spirv/builder_function_decoration_test.cc
+++ b/src/writer/spirv/builder_function_decoration_test.cc
@@ -51,22 +51,17 @@
   auto params = GetParam();
 
   ast::Variable* var = nullptr;
-  ast::Type* ret_type = nullptr;
-  ast::DecorationList ret_type_decos;
   ast::StatementList body;
   if (params.stage == ast::PipelineStage::kVertex) {
-    ret_type = ty.vec4<f32>();
-    ret_type_decos.push_back(Builtin(ast::Builtin::kPosition));
-    body.push_back(Return(Construct(ty.vec4<f32>())));
-  } else {
-    ret_type = ty.void_();
+    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", {}, ret_type, body,
+  auto* func = Func("main", {}, ty.void_(), body,
                     ast::DecorationList{
                         Stage(params.stage),
-                    },
-                    ret_type_decos);
+                    });
 
   spirv::Builder& b = Build();
 
@@ -93,6 +88,87 @@
                     FunctionStageData{ast::PipelineStage::kCompute,
                                       SpvExecutionModelGLCompute}));
 
+TEST_F(BuilderTest, Decoration_Stage_WithUnusedInterfaceIds) {
+  auto* func = Func("main", {}, ty.void_(), ast::StatementList{},
+                    ast::DecorationList{
+                        Stage(ast::PipelineStage::kFragment),
+                    });
+
+  auto* v_in = Global("my_in", ty.f32(), ast::StorageClass::kInput);
+  auto* v_out = Global("my_out", ty.f32(), ast::StorageClass::kOutput);
+  auto* v_wg = Global("my_wg", ty.f32(), ast::StorageClass::kWorkgroup);
+
+  spirv::Builder& b = Build();
+
+  EXPECT_TRUE(b.GenerateGlobalVariable(v_in)) << b.error();
+  EXPECT_TRUE(b.GenerateGlobalVariable(v_out)) << b.error();
+  EXPECT_TRUE(b.GenerateGlobalVariable(v_wg)) << b.error();
+
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %1 "my_in"
+OpName %4 "my_out"
+OpName %7 "my_wg"
+OpName %11 "main"
+)");
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
+%2 = OpTypePointer Input %3
+%1 = OpVariable %2 Input
+%5 = OpTypePointer Output %3
+%6 = OpConstantNull %3
+%4 = OpVariable %5 Output %6
+%8 = OpTypePointer Workgroup %3
+%7 = OpVariable %8 Workgroup
+%10 = OpTypeVoid
+%9 = OpTypeFunction %10
+)");
+  EXPECT_EQ(DumpInstructions(b.entry_points()),
+            R"(OpEntryPoint Fragment %11 "main"
+)");
+}
+
+TEST_F(BuilderTest, Decoration_Stage_WithUsedInterfaceIds) {
+  auto* v_in = Global("my_in", ty.f32(), ast::StorageClass::kInput);
+  auto* v_out = Global("my_out", ty.f32(), ast::StorageClass::kOutput);
+  auto* v_wg = Global("my_wg", ty.f32(), ast::StorageClass::kWorkgroup);
+
+  auto* func = Func(
+      "main", {}, ty.void_(),
+      ast::StatementList{Assign("my_out", "my_in"), Assign("my_wg", "my_wg"),
+                         // Add duplicate usages so we show they
+                         // don't get output multiple times.
+                         Assign("my_out", "my_in")},
+      ast::DecorationList{
+          Stage(ast::PipelineStage::kFragment),
+      });
+
+  spirv::Builder& b = Build();
+
+  EXPECT_TRUE(b.GenerateGlobalVariable(v_in)) << b.error();
+  EXPECT_TRUE(b.GenerateGlobalVariable(v_out)) << b.error();
+  EXPECT_TRUE(b.GenerateGlobalVariable(v_wg)) << b.error();
+
+  ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %1 "my_in"
+OpName %4 "my_out"
+OpName %7 "my_wg"
+OpName %11 "main"
+)");
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
+%2 = OpTypePointer Input %3
+%1 = OpVariable %2 Input
+%5 = OpTypePointer Output %3
+%6 = OpConstantNull %3
+%4 = OpVariable %5 Output %6
+%8 = OpTypePointer Workgroup %3
+%7 = OpVariable %8 Workgroup
+%10 = OpTypeVoid
+%9 = OpTypeFunction %10
+)");
+  EXPECT_EQ(DumpInstructions(b.entry_points()),
+            R"(OpEntryPoint Fragment %11 "main" %4 %1
+)");
+}
+
 TEST_F(BuilderTest, Decoration_ExecutionMode_Fragment_OriginUpperLeft) {
   auto* func = Func("main", {}, ty.void_(), ast::StatementList{},
                     ast::DecorationList{
@@ -247,22 +323,22 @@
 }
 
 TEST_F(BuilderTest, Decoration_ExecutionMode_FragDepth) {
-  Func("main", ast::VariableList{}, ty.f32(),
-       ast::StatementList{
-           Return(Expr(1.f)),
-       },
-       ast::DecorationList{Stage(ast::PipelineStage::kFragment)},
-       ast::DecorationList{
-           Builtin(ast::Builtin::kFragDepth),
-       });
+  Global("fragdepth", ty.f32(), ast::StorageClass::kOutput, nullptr,
+         ast::DecorationList{
+             Builtin(ast::Builtin::kFragDepth),
+         });
 
-  spirv::Builder& b = SanitizeAndBuild();
+  auto* func = Func("main", ast::VariableList{}, ty.void_(),
+                    ast::StatementList{
+                        Assign("fragdepth", Expr(1.f)),
+                    },
+                    ast::DecorationList{});
 
-  ASSERT_TRUE(b.Build());
+  spirv::Builder& b = Build();
 
+  ASSERT_TRUE(b.GenerateExecutionModes(func, 3)) << b.error();
   EXPECT_EQ(DumpInstructions(b.execution_modes()),
-            R"(OpExecutionMode %11 OriginUpperLeft
-OpExecutionMode %11 DepthReplacing
+            R"(OpExecutionMode %3 DepthReplacing
 )");
 }
 
diff --git a/src/writer/spirv/builder_global_variable_test.cc b/src/writer/spirv/builder_global_variable_test.cc
index d744996..c11c12c 100644
--- a/src/writer/spirv/builder_global_variable_test.cc
+++ b/src/writer/spirv/builder_global_variable_test.cc
@@ -26,7 +26,7 @@
 using BuilderTest = TestHelper;
 
 TEST_F(BuilderTest, GlobalVar_WithStorageClass) {
-  auto* v = Global("var", ty.f32(), ast::StorageClass::kPrivate);
+  auto* v = Global("var", ty.f32(), ast::StorageClass::kOutput);
 
   spirv::Builder& b = Build();
 
@@ -34,16 +34,30 @@
   EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %1 "var"
 )");
   EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
-%2 = OpTypePointer Private %3
+%2 = OpTypePointer Output %3
 %4 = OpConstantNull %3
-%1 = OpVariable %2 Private %4
+%1 = OpVariable %2 Output %4
+)");
+}
+
+TEST_F(BuilderTest, GlobalVar_WithStorageClass_Input) {
+  auto* v = Global("var", ty.f32(), ast::StorageClass::kInput);
+
+  spirv::Builder& b = Build();
+
+  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
+  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %1 "var"
+)");
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
+%2 = OpTypePointer Input %3
+%1 = OpVariable %2 Input
 )");
 }
 
 TEST_F(BuilderTest, GlobalVar_WithConstructor) {
   auto* init = vec3<f32>(1.f, 1.f, 3.f);
 
-  auto* v = Global("var", ty.vec3<f32>(), ast::StorageClass::kPrivate, init);
+  auto* v = Global("var", ty.vec3<f32>(), ast::StorageClass::kOutput, init);
 
   spirv::Builder& b = Build();
 
@@ -57,8 +71,8 @@
 %3 = OpConstant %2 1
 %4 = OpConstant %2 3
 %5 = OpConstantComposite %1 %3 %3 %4
-%7 = OpTypePointer Private %1
-%6 = OpVariable %7 Private %5
+%7 = OpTypePointer Output %1
+%6 = OpVariable %7 Output %5
 )");
 }
 
@@ -127,6 +141,26 @@
 )");
 }
 
+TEST_F(BuilderTest, GlobalVar_WithLocation) {
+  auto* v = Global("var", ty.f32(), ast::StorageClass::kOutput, nullptr,
+                   ast::DecorationList{
+                       Location(5),
+                   });
+
+  spirv::Builder& b = Build();
+
+  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
+  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %1 "var"
+)");
+  EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %1 Location 5
+)");
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
+%2 = OpTypePointer Output %3
+%4 = OpConstantNull %3
+%1 = OpVariable %2 Output %4
+)");
+}
+
 TEST_F(BuilderTest, GlobalVar_WithBindingAndGroup) {
   auto* v = Global("var", ty.sampler(ast::SamplerKind::kSampler),
                    ast::StorageClass::kNone, nullptr,
@@ -149,6 +183,26 @@
 )");
 }
 
+TEST_F(BuilderTest, GlobalVar_WithBuiltin) {
+  auto* v = Global("var", ty.f32(), ast::StorageClass::kOutput, nullptr,
+                   ast::DecorationList{
+                       Builtin(ast::Builtin::kPosition),
+                   });
+
+  spirv::Builder& b = Build();
+
+  EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
+  EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %1 "var"
+)");
+  EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %1 BuiltIn Position
+)");
+  EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
+%2 = OpTypePointer Output %3
+%4 = OpConstantNull %3
+%1 = OpVariable %2 Output %4
+)");
+}
+
 TEST_F(BuilderTest, GlobalVar_Override_Bool) {
   auto* v = GlobalConst("var", ty.bool_(), Expr(true),
                         ast::DecorationList{
@@ -607,6 +661,96 @@
 )");
 }
 
+TEST_F(BuilderTest, SampleIndex) {
+  auto* var =
+      Global("sample_index", ty.u32(), ast::StorageClass::kInput, nullptr,
+             ast::DecorationList{
+                 Builtin(ast::Builtin::kSampleIndex),
+             });
+
+  spirv::Builder& b = Build();
+
+  EXPECT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
+  EXPECT_EQ(DumpInstructions(b.capabilities()),
+            "OpCapability SampleRateShading\n");
+  EXPECT_EQ(DumpInstructions(b.annots()), "OpDecorate %1 BuiltIn SampleId\n");
+  EXPECT_EQ(DumpInstructions(b.types()),
+            "%3 = OpTypeInt 32 0\n"
+            "%2 = OpTypePointer Input %3\n"
+            "%1 = OpVariable %2 Input\n");
+}
+
+TEST_F(BuilderTest, SampleMask) {
+  // Input:
+  // [[builtin(sample_mask)]] var<in> mask_in : u32;
+  // [[builtin(sample_mask)]] var<out> mask_out : u32;
+  // [[stage(fragment)]]
+  // fn main() {
+  //   mask_out = mask_in;
+  // }
+
+  // After sanitization:
+  // [[builtin(sample_mask)]] var<in> mask_in : array<u32, 1>;
+  // [[builtin(sample_mask)]] var<out> mask_out : array<u32, 1>;
+  // [[stage(fragment)]]
+  // fn main() {
+  //   mask_out[0] = mask_in[0];
+  // }
+
+  Global("mask_in", ty.u32(), ast::StorageClass::kInput, nullptr,
+         ast::DecorationList{
+             Builtin(ast::Builtin::kSampleMask),
+         });
+  Global("mask_out", ty.u32(), ast::StorageClass::kOutput, nullptr,
+         ast::DecorationList{
+             Builtin(ast::Builtin::kSampleMask),
+         });
+  Func("main", ast::VariableList{}, ty.void_(),
+       ast::StatementList{
+           Assign("mask_out", "mask_in"),
+       },
+       ast::DecorationList{
+           Stage(ast::PipelineStage::kCompute),
+       });
+
+  spirv::Builder& b = SanitizeAndBuild();
+
+  ASSERT_TRUE(b.Build());
+  EXPECT_EQ(DumpBuilder(b), R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %11 "main" %6 %1
+OpExecutionMode %11 LocalSize 1 1 1
+OpName %1 "mask_in"
+OpName %6 "mask_out"
+OpName %11 "main"
+OpDecorate %3 ArrayStride 4
+OpDecorate %1 BuiltIn SampleMask
+OpDecorate %6 BuiltIn SampleMask
+%4 = OpTypeInt 32 0
+%5 = OpConstant %4 1
+%3 = OpTypeArray %4 %5
+%2 = OpTypePointer Input %3
+%1 = OpVariable %2 Input
+%7 = OpTypePointer Output %3
+%8 = OpConstantNull %3
+%6 = OpVariable %7 Output %8
+%10 = OpTypeVoid
+%9 = OpTypeFunction %10
+%13 = OpTypeInt 32 1
+%14 = OpConstant %13 0
+%15 = OpTypePointer Output %4
+%17 = OpTypePointer Input %4
+%11 = OpFunction %10 None %9
+%12 = OpLabel
+%16 = OpAccessChain %15 %6 %14
+%18 = OpAccessChain %17 %1 %14
+%19 = OpLoad %4 %18
+OpStore %16 %19
+OpReturn
+OpFunctionEnd
+)");
+}
+
 }  // namespace
 }  // namespace spirv
 }  // namespace writer
diff --git a/src/writer/spirv/builder_ident_expression_test.cc b/src/writer/spirv/builder_ident_expression_test.cc
index 2b8299e..c57b44a 100644
--- a/src/writer/spirv/builder_ident_expression_test.cc
+++ b/src/writer/spirv/builder_ident_expression_test.cc
@@ -46,7 +46,7 @@
 }
 
 TEST_F(BuilderTest, IdentifierExpression_GlobalVar) {
-  auto* v = Global("var", ty.f32(), ast::StorageClass::kPrivate);
+  auto* v = Global("var", ty.f32(), ast::StorageClass::kOutput);
 
   auto* expr = Expr("var");
   WrapInFunction(expr);
@@ -58,9 +58,9 @@
   EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %1 "var"
 )");
   EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
-%2 = OpTypePointer Private %3
+%2 = OpTypePointer Output %3
 %4 = OpConstantNull %3
-%1 = OpVariable %2 Private %4
+%1 = OpVariable %2 Output %4
 )");
 
   EXPECT_EQ(b.GenerateIdentifierExpression(expr), 1u);
diff --git a/src/writer/spirv/builder_type_test.cc b/src/writer/spirv/builder_type_test.cc
index 45a8d34..1bae74f 100644
--- a/src/writer/spirv/builder_type_test.cc
+++ b/src/writer/spirv/builder_type_test.cc
@@ -70,7 +70,7 @@
 
 TEST_F(BuilderTest_Type, GenerateArray) {
   auto* ary = ty.array(ty.i32(), 4);
-  Global("a", ary, ast::StorageClass::kPrivate);
+  Global("a", ary, ast::StorageClass::kInput);
 
   spirv::Builder& b = Build();
 
@@ -87,7 +87,7 @@
 
 TEST_F(BuilderTest_Type, GenerateArray_WithStride) {
   auto* ary = ty.array(ty.i32(), 4, 16u);
-  Global("a", ary, ast::StorageClass::kPrivate);
+  Global("a", ary, ast::StorageClass::kInput);
 
   spirv::Builder& b = Build();
 
@@ -107,7 +107,7 @@
 
 TEST_F(BuilderTest_Type, ReturnsGeneratedArray) {
   auto* ary = ty.array(ty.i32(), 4);
-  Global("a", ary, ast::StorageClass::kPrivate);
+  Global("a", ary, ast::StorageClass::kInput);
 
   spirv::Builder& b = Build();
 
diff --git a/src/writer/wgsl/generator_impl_global_decl_test.cc b/src/writer/wgsl/generator_impl_global_decl_test.cc
index c67d9ef..fcb5104 100644
--- a/src/writer/wgsl/generator_impl_global_decl_test.cc
+++ b/src/writer/wgsl/generator_impl_global_decl_test.cc
@@ -55,7 +55,7 @@
        },
        ast::DecorationList{});
 
-  Global("a1", ty.f32(), ast::StorageClass::kPrivate);
+  Global("a1", ty.f32(), ast::StorageClass::kOutput);
 
   auto* s1 = Structure("S1", {Member("a", ty.i32())});
 
@@ -84,7 +84,7 @@
     return a0;
   }
 
-  var<private> a1 : f32;
+  var<out> a1 : f32;
 
   struct S1 {
     a : i32;