Resolver: Validate <storage> var types

https://gpuweb.github.io/gpuweb/wgsl/#variable-declaration
Variables in the storage storage class and variables with a storage
texture type must have an access attribute applied to the store type.

https://gpuweb.github.io/gpuweb/wgsl/#module-scope-variables
A variable in the storage storage class is a storage buffer variable. Its
store type must be a host-shareable structure type with block attribute,
satisfying the storage class constraints.

Fixup tests, including those that were producing warnings about `var <in>`

The WGSL writer seems to want to put a newline after every decoration block, leading to some ugly output. I'll fix this as a separate change.

Fixes: tint:531
Fixes: tint:692
Change-Id: If09d987477247ab4a7c635f6ee6e616a06061515
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/47763
Commit-Queue: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: James Price <jrprice@google.com>
Reviewed-by: Ryan Harrison <rharrison@chromium.org>
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index a3c6828..3625fdb 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -488,6 +488,7 @@
     resolver/resolver_test_helper.cc
     resolver/resolver_test_helper.h
     resolver/resolver_test.cc
+    resolver/storage_class_validation_test.cc
     resolver/struct_layout_test.cc
     resolver/struct_pipeline_stage_use_test.cc
     resolver/struct_storage_class_use_test.cc
diff --git a/src/ast/module_clone_test.cc b/src/ast/module_clone_test.cc
index 4c5dd1c..fc6117e 100644
--- a/src/ast/module_clone_test.cc
+++ b/src/ast/module_clone_test.cc
@@ -40,7 +40,7 @@
 type t1 = array<vec4<f32>>;
 
 var<uniform> g0 : u32 = 20u;
-var<out> g1 : f32 = 123.0;
+var<private> g1 : f32 = 123.0;
 var g2 : texture_2d<f32>;
 var g3 : [[access(read)]] texture_storage_2d<r32uint>;
 var g4 : [[access(write)]] texture_storage_2d<rg32float>;
@@ -48,7 +48,8 @@
 var g6 : [[access(write)]] texture_storage_2d<rg32float>;
 
 [[builtin(position)]] var<uniform> g7 : vec3<f32>;
-[[group(10), binding(20)]] var<storage> g8 : S;
+[[group(10), binding(20)]] var<storage> g8 : [[access(write)]]
+S;
 [[group(10), binding(20)]] var<storage> g9 : [[access(read)]]
 S;
 [[group(10), binding(20)]] var<storage> g10 : [[access(read_write)]]
diff --git a/src/resolver/host_shareable_validation_test.cc b/src/resolver/host_shareable_validation_test.cc
index 82fecda..ef5aec7 100644
--- a/src/resolver/host_shareable_validation_test.cc
+++ b/src/resolver/host_shareable_validation_test.cc
@@ -25,32 +25,10 @@
 
 using ResolverHostShareableValidationTest = ResolverTest;
 
-TEST_F(ResolverHostShareableValidationTest, Bool) {
-  Global(Source{{56, 78}}, "g", ty.bool_(), ast::StorageClass::kStorage);
-
-  ASSERT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(
-      r()->error(),
-      R"(56:78 error: Type 'bool' cannot be used in storage class 'storage' as it is non-host-shareable
-56:78 note: while instantiating variable g)");
-}
-
-TEST_F(ResolverHostShareableValidationTest, Pointer) {
-  Global(Source{{56, 78}}, "g", ty.pointer<i32>(ast::StorageClass::kInput),
-         ast::StorageClass::kStorage);
-
-  ASSERT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(
-      r()->error(),
-      R"(56:78 error: Type 'ptr<in, i32>' cannot be used in storage class 'storage' as it is non-host-shareable
-56:78 note: while instantiating variable g)");
-}
-
 TEST_F(ResolverHostShareableValidationTest, BoolMember) {
   auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.bool_())});
-  Global(Source{{56, 78}}, "g", s, ast::StorageClass::kStorage);
+  auto* a = ty.access(ast::AccessControl::kReadOnly, s);
+  Global(Source{{56, 78}}, "g", a, ast::StorageClass::kStorage);
 
   ASSERT_FALSE(r()->Resolve());
 
@@ -63,7 +41,8 @@
 
 TEST_F(ResolverHostShareableValidationTest, BoolVectorMember) {
   auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.vec3<bool>())});
-  Global(Source{{56, 78}}, "g", s, ast::StorageClass::kStorage);
+  auto* a = ty.access(ast::AccessControl::kReadOnly, s);
+  Global(Source{{56, 78}}, "g", a, ast::StorageClass::kStorage);
 
   ASSERT_FALSE(r()->Resolve());
 
@@ -77,7 +56,8 @@
 TEST_F(ResolverHostShareableValidationTest, Aliases) {
   auto* a1 = ty.alias("a1", ty.bool_());
   auto* s = Structure("S", {Member(Source{{12, 34}}, "x", a1)});
-  auto* a2 = ty.alias("a2", s);
+  auto* ac = ty.access(ast::AccessControl::kReadOnly, s);
+  auto* a2 = ty.alias("a2", ac);
   Global(Source{{56, 78}}, "g", a2, ast::StorageClass::kStorage);
 
   ASSERT_FALSE(r()->Resolve());
@@ -89,27 +69,14 @@
 56:78 note: while instantiating variable g)");
 }
 
-TEST_F(ResolverHostShareableValidationTest, AccessControl) {
-  auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.bool_())});
-  auto* a = create<type::AccessControl>(ast::AccessControl::kReadOnly, s);
-  Global(Source{{56, 78}}, "g", a, ast::StorageClass::kStorage);
-
-  ASSERT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(
-      r()->error(),
-      R"(56:78 error: Type 'bool' cannot be used in storage class 'storage' as it is non-host-shareable
-12:34 note: while analysing structure member S.x
-56:78 note: while instantiating variable g)");
-}
-
 TEST_F(ResolverHostShareableValidationTest, NestedStructures) {
   auto* i1 = Structure("I1", {Member(Source{{1, 2}}, "x", ty.bool_())});
   auto* i2 = Structure("I2", {Member(Source{{3, 4}}, "y", i1)});
   auto* i3 = Structure("I3", {Member(Source{{5, 6}}, "z", i2)});
 
   auto* s = Structure("S", {Member(Source{{7, 8}}, "m", i3)});
-  Global(Source{{9, 10}}, "g", s, ast::StorageClass::kStorage);
+  auto* a = ty.access(ast::AccessControl::kReadOnly, s);
+  Global(Source{{9, 10}}, "g", a, ast::StorageClass::kStorage);
 
   ASSERT_FALSE(r()->Resolve());
 
@@ -143,7 +110,8 @@
                       });
 
   auto* s = Structure("S", {Member(Source{{7, 8}}, "m", i3)});
-  Global(Source{{9, 10}}, "g", s, ast::StorageClass::kStorage);
+  auto* a = ty.access(ast::AccessControl::kReadOnly, s);
+  Global(Source{{9, 10}}, "g", a, ast::StorageClass::kStorage);
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
diff --git a/src/resolver/resolver.cc b/src/resolver/resolver.cc
index 257c822..b69ccb2 100644
--- a/src/resolver/resolver.cc
+++ b/src/resolver/resolver.cc
@@ -253,6 +253,27 @@
     return false;
   }
 
+  if (info->storage_class == ast::StorageClass::kStorage) {
+    // https://gpuweb.github.io/gpuweb/wgsl/#variable-declaration
+    // Variables in the storage storage class and variables with a storage
+    // texture type must have an access attribute applied to the store type.
+
+    // https://gpuweb.github.io/gpuweb/wgsl/#module-scope-variables
+    // A variable in the storage storage class is a storage buffer variable. Its
+    // store type must be a host-shareable structure type with block attribute,
+    // satisfying the storage class constraints.
+
+    auto* access = info->type->As<type::AccessControl>();
+    auto* str = access ? access->type()->As<type::Struct>() : nullptr;
+    if (!str) {
+      diagnostics_.add_error(
+          "variables declared in the <storage> storage class must be of an "
+          "[[access]] qualified structure type",
+          var->source());
+      return false;
+    }
+  }
+
   for (auto* deco : var->decorations()) {
     if (!(deco->Is<ast::BindingDecoration>() ||
           deco->Is<ast::BuiltinDecoration>() ||
diff --git a/src/resolver/resolver_test.cc b/src/resolver/resolver_test.cc
index 389fcec..fba46ed 100644
--- a/src/resolver/resolver_test.cc
+++ b/src/resolver/resolver_test.cc
@@ -763,9 +763,12 @@
 }
 
 TEST_F(ResolverTest, Function_RegisterInputOutputVariables) {
+  auto* s = Structure("S", {Member("m", ty.u32())});
+  auto* a = ty.access(ast::AccessControl::kReadOnly, s);
+
   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.f32(), ast::StorageClass::kStorage);
+  auto* sb_var = Global("sb_var", a, ast::StorageClass::kStorage);
   auto* wg_var = Global("wg_var", ty.f32(), ast::StorageClass::kWorkgroup);
   auto* priv_var = Global("priv_var", ty.f32(), ast::StorageClass::kPrivate);
 
@@ -795,9 +798,12 @@
 }
 
 TEST_F(ResolverTest, Function_RegisterInputOutputVariables_SubFunction) {
+  auto* s = Structure("S", {Member("m", ty.u32())});
+  auto* a = ty.access(ast::AccessControl::kReadOnly, s);
+
   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.f32(), ast::StorageClass::kStorage);
+  auto* sb_var = Global("sb_var", a, ast::StorageClass::kStorage);
   auto* wg_var = Global("wg_var", ty.f32(), ast::StorageClass::kWorkgroup);
   auto* priv_var = Global("priv_var", ty.f32(), ast::StorageClass::kPrivate);
 
diff --git a/src/resolver/storage_class_validation_test.cc b/src/resolver/storage_class_validation_test.cc
new file mode 100644
index 0000000..0fde1a1
--- /dev/null
+++ b/src/resolver/storage_class_validation_test.cc
@@ -0,0 +1,123 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/resolver/resolver.h"
+
+#include "gmock/gmock.h"
+#include "src/resolver/resolver_test_helper.h"
+#include "src/semantic/struct.h"
+#include "src/type/access_control_type.h"
+
+namespace tint {
+namespace resolver {
+namespace {
+
+using ResolverStorageClassValidationTest = ResolverTest;
+
+TEST_F(ResolverStorageClassValidationTest, GlobalVariableNoStorageClass_Fail) {
+  // var g : f32;
+  Global(Source{{12, 34}}, "g", ty.f32(), ast::StorageClass::kNone);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error v-0022: global variables must have a storage class");
+}
+
+TEST_F(ResolverStorageClassValidationTest, Bool) {
+  // var<storage> g : bool;
+  Global(Source{{56, 78}}, "g", ty.bool_(), ast::StorageClass::kStorage);
+
+  ASSERT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(
+      r()->error(),
+      R"(56:78 error: variables declared in the <storage> storage class must be of an [[access]] qualified structure type)");
+}
+
+TEST_F(ResolverStorageClassValidationTest, Pointer) {
+  // var<storage> g : ptr<i32, input>;
+  Global(Source{{56, 78}}, "g", ty.pointer<i32>(ast::StorageClass::kInput),
+         ast::StorageClass::kStorage);
+
+  ASSERT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(
+      r()->error(),
+      R"(56:78 error: variables declared in the <storage> storage class must be of an [[access]] qualified structure type)");
+}
+
+TEST_F(ResolverStorageClassValidationTest, Array) {
+  // var<storage> g : [[access(read)]] array<S, 3>;
+  auto* s = Structure("S", {Member("a", ty.f32())});
+  auto* a = ty.array(s, 3);
+  auto* ac = ty.access(ast::AccessControl::kReadOnly, a);
+  Global(Source{{56, 78}}, "g", ac, ast::StorageClass::kStorage);
+
+  ASSERT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(
+      r()->error(),
+      R"(56:78 error: variables declared in the <storage> storage class must be of an [[access]] qualified structure type)");
+}
+
+TEST_F(ResolverStorageClassValidationTest, BoolAlias) {
+  // type a = bool;
+  // var<storage> g : [[access(read)]] a;
+  auto* a = ty.alias("a", ty.bool_());
+  Global(Source{{56, 78}}, "g", a, ast::StorageClass::kStorage);
+
+  ASSERT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(
+      r()->error(),
+      R"(56:78 error: variables declared in the <storage> storage class must be of an [[access]] qualified structure type)");
+}
+
+TEST_F(ResolverStorageClassValidationTest, NoAccessControl) {
+  // var<storage> g : S;
+  auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.i32())});
+  Global(Source{{56, 78}}, "g", s, ast::StorageClass::kStorage);
+
+  ASSERT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(
+      r()->error(),
+      R"(56:78 error: variables declared in the <storage> storage class must be of an [[access]] qualified structure type)");
+}
+
+TEST_F(ResolverStorageClassValidationTest, NoError_Basic) {
+  // var<storage> g : [[access(read)]] S;
+  auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.i32())});
+  auto* a = ty.access(ast::AccessControl::kReadOnly, s);
+  Global(Source{{56, 78}}, "g", a, ast::StorageClass::kStorage);
+
+  ASSERT_TRUE(r()->Resolve());
+}
+
+TEST_F(ResolverStorageClassValidationTest, NoError_Aliases) {
+  // type a1 = S;
+  // type a2 = [[access(read)]] a1;
+  // var<storage> g : a2;
+  auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.i32())});
+  auto* a1 = ty.alias("a1", s);
+  auto* ac = ty.access(ast::AccessControl::kReadOnly, a1);
+  auto* a2 = ty.alias("a2", ac);
+  Global(Source{{56, 78}}, "g", a2, ast::StorageClass::kStorage);
+
+  ASSERT_TRUE(r()->Resolve());
+}
+
+}  // namespace
+}  // namespace resolver
+}  // namespace tint
diff --git a/src/resolver/struct_storage_class_use_test.cc b/src/resolver/struct_storage_class_use_test.cc
index ac1b8f5..80a1f4d 100644
--- a/src/resolver/struct_storage_class_use_test.cc
+++ b/src/resolver/struct_storage_class_use_test.cc
@@ -65,53 +65,53 @@
 TEST_F(ResolverStorageClassUseTest, StructReachableFromGlobal) {
   auto* s = Structure("S", {Member("a", ty.f32())});
 
-  Global("g", s, ast::StorageClass::kStorage);
+  Global("g", s, ast::StorageClass::kUniform);
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
   auto* sem = Sem().Get(s);
   ASSERT_NE(sem, nullptr);
   EXPECT_THAT(sem->StorageClassUsage(),
-              UnorderedElementsAre(ast::StorageClass::kStorage));
+              UnorderedElementsAre(ast::StorageClass::kUniform));
 }
 
 TEST_F(ResolverStorageClassUseTest, StructReachableViaGlobalAlias) {
   auto* s = Structure("S", {Member("a", ty.f32())});
   auto* a = ty.alias("A", s);
-  Global("g", a, ast::StorageClass::kStorage);
+  Global("g", a, ast::StorageClass::kUniform);
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
   auto* sem = Sem().Get(s);
   ASSERT_NE(sem, nullptr);
   EXPECT_THAT(sem->StorageClassUsage(),
-              UnorderedElementsAre(ast::StorageClass::kStorage));
+              UnorderedElementsAre(ast::StorageClass::kUniform));
 }
 
 TEST_F(ResolverStorageClassUseTest, StructReachableViaGlobalStruct) {
   auto* s = Structure("S", {Member("a", ty.f32())});
   auto* o = Structure("O", {Member("a", s)});
-  Global("g", o, ast::StorageClass::kStorage);
+  Global("g", o, ast::StorageClass::kUniform);
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
   auto* sem = Sem().Get(s);
   ASSERT_NE(sem, nullptr);
   EXPECT_THAT(sem->StorageClassUsage(),
-              UnorderedElementsAre(ast::StorageClass::kStorage));
+              UnorderedElementsAre(ast::StorageClass::kUniform));
 }
 
 TEST_F(ResolverStorageClassUseTest, StructReachableViaGlobalArray) {
   auto* s = Structure("S", {Member("a", ty.f32())});
   auto* a = ty.array(s, 3);
-  Global("g", a, ast::StorageClass::kStorage);
+  Global("g", a, ast::StorageClass::kUniform);
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
 
   auto* sem = Sem().Get(s);
   ASSERT_NE(sem, nullptr);
   EXPECT_THAT(sem->StorageClassUsage(),
-              UnorderedElementsAre(ast::StorageClass::kStorage));
+              UnorderedElementsAre(ast::StorageClass::kUniform));
 }
 
 TEST_F(ResolverStorageClassUseTest, StructReachableFromLocal) {
@@ -168,8 +168,9 @@
 
 TEST_F(ResolverStorageClassUseTest, StructMultipleStorageClassUses) {
   auto* s = Structure("S", {Member("a", ty.f32())});
-  Global("x", s, ast::StorageClass::kStorage);
-  Global("y", s, ast::StorageClass::kUniform);
+  auto* ac = ty.access(ast::AccessControl::kReadOnly, s);
+  Global("x", s, ast::StorageClass::kUniform);
+  Global("y", ac, ast::StorageClass::kStorage);
   WrapInFunction(Var("g", s, ast::StorageClass::kFunction));
 
   ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -177,8 +178,8 @@
   auto* sem = Sem().Get(s);
   ASSERT_NE(sem, nullptr);
   EXPECT_THAT(sem->StorageClassUsage(),
-              UnorderedElementsAre(ast::StorageClass::kStorage,
-                                   ast::StorageClass::kUniform,
+              UnorderedElementsAre(ast::StorageClass::kUniform,
+                                   ast::StorageClass::kStorage,
                                    ast::StorageClass::kFunction));
 }
 
diff --git a/src/resolver/type_validation_test.cc b/src/resolver/type_validation_test.cc
index 213099b..e580129 100644
--- a/src/resolver/type_validation_test.cc
+++ b/src/resolver/type_validation_test.cc
@@ -56,15 +56,6 @@
   EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
 
-TEST_F(ResolverTypeValidationTest, GlobalVariableNoStorageClass_Fail) {
-  // var global_var: f32;
-  Global(Source{{12, 34}}, "global_var", ty.f32(), ast::StorageClass::kNone);
-
-  EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error v-0022: global variables must have a storage class");
-}
-
 TEST_F(ResolverTypeValidationTest, GlobalConstantWithStorageClass_Fail) {
   // const<in> global_var: f32;
   AST().AddGlobalVariable(
diff --git a/src/transform/binding_remapper_test.cc b/src/transform/binding_remapper_test.cc
index 2ca2fd1..4d36ccd 100644
--- a/src/transform/binding_remapper_test.cc
+++ b/src/transform/binding_remapper_test.cc
@@ -30,9 +30,11 @@
 struct S {
 };
 
-[[group(2), binding(1)]] var<storage> a : S;
+[[group(2), binding(1)]] var<storage> a : [[access(read)]]
+S;
 
-[[group(3), binding(2)]] var<storage> b : S;
+[[group(3), binding(2)]] var<storage> b : [[access(read)]]
+S;
 
 [[stage(compute)]]
 fn f() {
@@ -55,9 +57,11 @@
 struct S {
 };
 
-[[group(2), binding(1)]] var<storage> a : S;
+[[group(2), binding(1)]] var<storage> a : [[access(read)]]
+S;
 
-[[group(3), binding(2)]] var<storage> b : S;
+[[group(3), binding(2)]] var<storage> b : [[access(read)]]
+S;
 
 [[stage(compute)]]
 fn f() {
@@ -69,9 +73,11 @@
 struct S {
 };
 
-[[group(1), binding(2)]] var<storage> a : S;
+[[group(1), binding(2)]] var<storage> a : [[access(read)]]
+S;
 
-[[group(3), binding(2)]] var<storage> b : S;
+[[group(3), binding(2)]] var<storage> b : [[access(read)]]
+S;
 
 [[stage(compute)]]
 fn f() {
@@ -103,7 +109,8 @@
 [[group(3), binding(2)]] var<storage> b : [[access(write)]]
 S;
 
-[[group(4), binding(3)]] var<storage> c : S;
+[[group(4), binding(3)]] var<storage> c : [[access(read)]]
+S;
 
 [[stage(compute)]]
 fn f() {
@@ -216,7 +223,8 @@
 [[group(2), binding(1)]] var<storage> a : [[access(read)]]
 S;
 
-[[group(3), binding(2)]] var<storage> b : S;
+[[group(3), binding(2)]] var<storage> b : [[access(read)]]
+S;
 
 [[stage(compute)]]
 fn f() {
@@ -260,8 +268,10 @@
 struct S {
 };
 
-[[group(2), binding(1)]] var<storage> a : S;
-[[group(3), binding(2)]] var<storage> b : S;
+[[group(2), binding(1)]] var<storage> a : [[access(read)]]
+S;
+[[group(3), binding(2)]] var<storage> b : [[access(read)]]
+S;
 
 [[stage(compute)]]
 fn f() {}
diff --git a/src/transform/bound_array_accessors_test.cc b/src/transform/bound_array_accessors_test.cc
index 7ab6534..72f2db1 100644
--- a/src/transform/bound_array_accessors_test.cc
+++ b/src/transform/bound_array_accessors_test.cc
@@ -24,22 +24,22 @@
 
 TEST_F(BoundArrayAccessorsTest, Ptrs_Clamp) {
   auto* src = R"(
-var<storage> a : array<f32, 3>;
+var<private> a : array<f32, 3>;
 
 let c : u32 = 1u;
 
 fn f() {
-  let b : ptr<storage, f32> = a[c];
+  let b : ptr<private, f32> = a[c];
 }
 )";
 
   auto* expect = R"(
-var<storage> a : array<f32, 3>;
+var<private> a : array<f32, 3>;
 
 let c : u32 = 1u;
 
 fn f() {
-  let b : ptr<storage, f32> = a[min(u32(c), 2u)];
+  let b : ptr<private, f32> = a[min(u32(c), 2u)];
 }
 )";
 
@@ -50,11 +50,11 @@
 
 TEST_F(BoundArrayAccessorsTest, Array_Idx_Nested_Scalar) {
   auto* src = R"(
-var<in> a : array<f32, 3>;
+var<private> a : array<f32, 3>;
 
-var<in> b : array<f32, 5>;
+var<private> b : array<f32, 5>;
 
-var<in> i : u32;
+var<private> i : u32;
 
 fn f() {
   var c : f32 = a[ b[i] ];
@@ -62,11 +62,11 @@
 )";
 
   auto* expect = R"(
-var<in> a : array<f32, 3>;
+var<private> a : array<f32, 3>;
 
-var<in> b : array<f32, 5>;
+var<private> b : array<f32, 5>;
 
-var<in> i : u32;
+var<private> i : u32;
 
 fn f() {
   var c : f32 = a[min(u32(b[min(u32(i), 4u)]), 2u)];
@@ -80,7 +80,7 @@
 
 TEST_F(BoundArrayAccessorsTest, Array_Idx_Scalar) {
   auto* src = R"(
-var<in> a : array<f32, 3>;
+var<private> a : array<f32, 3>;
 
 fn f() {
   var b : f32 = a[1];
@@ -88,7 +88,7 @@
 )";
 
   auto* expect = R"(
-var<in> a : array<f32, 3>;
+var<private> a : array<f32, 3>;
 
 fn f() {
   var b : f32 = a[1];
@@ -102,9 +102,9 @@
 
 TEST_F(BoundArrayAccessorsTest, Array_Idx_Expr) {
   auto* src = R"(
-var<in> a : array<f32, 3>;
+var<private> a : array<f32, 3>;
 
-var<in> c : i32;
+var<private> c : i32;
 
 fn f() {
   var b : f32 = a[c + 2 - 3];
@@ -112,9 +112,9 @@
 )";
 
   auto* expect = R"(
-var<in> a : array<f32, 3>;
+var<private> a : array<f32, 3>;
 
-var<in> c : i32;
+var<private> c : i32;
 
 fn f() {
   var b : f32 = a[min(u32(((c + 2) - 3)), 2u)];
@@ -128,7 +128,7 @@
 
 TEST_F(BoundArrayAccessorsTest, Array_Idx_Negative) {
   auto* src = R"(
-var<in> a : array<f32, 3>;
+var<private> a : array<f32, 3>;
 
 fn f() {
   var b : f32 = a[-1];
@@ -136,7 +136,7 @@
 )";
 
   auto* expect = R"(
-var<in> a : array<f32, 3>;
+var<private> a : array<f32, 3>;
 
 fn f() {
   var b : f32 = a[0];
@@ -150,7 +150,7 @@
 
 TEST_F(BoundArrayAccessorsTest, Array_Idx_OutOfBounds) {
   auto* src = R"(
-var<in> a : array<f32, 3>;
+var<private> a : array<f32, 3>;
 
 fn f() {
   var b : f32 = a[3];
@@ -158,7 +158,7 @@
 )";
 
   auto* expect = R"(
-var<in> a : array<f32, 3>;
+var<private> a : array<f32, 3>;
 
 fn f() {
   var b : f32 = a[2];
@@ -172,7 +172,7 @@
 
 TEST_F(BoundArrayAccessorsTest, Vector_Idx_Scalar) {
   auto* src = R"(
-var<in> a : vec3<f32>;
+var<private> a : vec3<f32>;
 
 fn f() {
   var b : f32 = a[1];
@@ -180,7 +180,7 @@
 )";
 
   auto* expect = R"(
-var<in> a : vec3<f32>;
+var<private> a : vec3<f32>;
 
 fn f() {
   var b : f32 = a[1];
@@ -194,9 +194,9 @@
 
 TEST_F(BoundArrayAccessorsTest, Vector_Idx_Expr) {
   auto* src = R"(
-var<in> a : vec3<f32>;
+var<private> a : vec3<f32>;
 
-var<in> c : i32;
+var<private> c : i32;
 
 fn f() {
   var b : f32 = a[c + 2 - 3];
@@ -204,9 +204,9 @@
 )";
 
   auto* expect = R"(
-var<in> a : vec3<f32>;
+var<private> a : vec3<f32>;
 
-var<in> c : i32;
+var<private> c : i32;
 
 fn f() {
   var b : f32 = a[min(u32(((c + 2) - 3)), 2u)];
@@ -220,7 +220,7 @@
 
 TEST_F(BoundArrayAccessorsTest, Vector_Swizzle_Idx_Scalar) {
   auto* src = R"(
-var<in> a : vec3<f32>;
+var<private> a : vec3<f32>;
 
 fn f() {
   var b : f32 = a.xy[2];
@@ -228,7 +228,7 @@
 )";
 
   auto* expect = R"(
-var<in> a : vec3<f32>;
+var<private> a : vec3<f32>;
 
 fn f() {
   var b : f32 = a.xy[1];
@@ -242,9 +242,9 @@
 
 TEST_F(BoundArrayAccessorsTest, Vector_Swizzle_Idx_Var) {
   auto* src = R"(
-var<in> a : vec3<f32>;
+var<private> a : vec3<f32>;
 
-var<in> c : i32;
+var<private> c : i32;
 
 fn f() {
   var b : f32 = a.xy[c];
@@ -252,9 +252,9 @@
 )";
 
   auto* expect = R"(
-var<in> a : vec3<f32>;
+var<private> a : vec3<f32>;
 
-var<in> c : i32;
+var<private> c : i32;
 
 fn f() {
   var b : f32 = a.xy[min(u32(c), 1u)];
@@ -267,9 +267,9 @@
 }
 TEST_F(BoundArrayAccessorsTest, Vector_Swizzle_Idx_Expr) {
   auto* src = R"(
-var<in> a : vec3<f32>;
+var<private> a : vec3<f32>;
 
-var<in> c : i32;
+var<private> c : i32;
 
 fn f() {
   var b : f32 = a.xy[c + 2 - 3];
@@ -277,9 +277,9 @@
 )";
 
   auto* expect = R"(
-var<in> a : vec3<f32>;
+var<private> a : vec3<f32>;
 
-var<in> c : i32;
+var<private> c : i32;
 
 fn f() {
   var b : f32 = a.xy[min(u32(((c + 2) - 3)), 1u)];
@@ -293,7 +293,7 @@
 
 TEST_F(BoundArrayAccessorsTest, Vector_Idx_Negative) {
   auto* src = R"(
-var<in> a : vec3<f32>;
+var<private> a : vec3<f32>;
 
 fn f() {
   var b : f32 = a[-1];
@@ -301,7 +301,7 @@
 )";
 
   auto* expect = R"(
-var<in> a : vec3<f32>;
+var<private> a : vec3<f32>;
 
 fn f() {
   var b : f32 = a[0];
@@ -315,7 +315,7 @@
 
 TEST_F(BoundArrayAccessorsTest, Vector_Idx_OutOfBounds) {
   auto* src = R"(
-var<in> a : vec3<f32>;
+var<private> a : vec3<f32>;
 
 fn f() {
   var b : f32 = a[3];
@@ -323,7 +323,7 @@
 )";
 
   auto* expect = R"(
-var<in> a : vec3<f32>;
+var<private> a : vec3<f32>;
 
 fn f() {
   var b : f32 = a[2];
@@ -337,7 +337,7 @@
 
 TEST_F(BoundArrayAccessorsTest, Matrix_Idx_Scalar) {
   auto* src = R"(
-var<in> a : mat3x2<f32>;
+var<private> a : mat3x2<f32>;
 
 fn f() {
   var b : f32 = a[2][1];
@@ -345,7 +345,7 @@
 )";
 
   auto* expect = R"(
-var<in> a : mat3x2<f32>;
+var<private> a : mat3x2<f32>;
 
 fn f() {
   var b : f32 = a[2][1];
@@ -359,9 +359,9 @@
 
 TEST_F(BoundArrayAccessorsTest, Matrix_Idx_Expr_Column) {
   auto* src = R"(
-var<in> a : mat3x2<f32>;
+var<private> a : mat3x2<f32>;
 
-var<in> c : i32;
+var<private> c : i32;
 
 fn f() {
   var b : f32 = a[c + 2 - 3][1];
@@ -369,9 +369,9 @@
 )";
 
   auto* expect = R"(
-var<in> a : mat3x2<f32>;
+var<private> a : mat3x2<f32>;
 
-var<in> c : i32;
+var<private> c : i32;
 
 fn f() {
   var b : f32 = a[min(u32(((c + 2) - 3)), 2u)][1];
@@ -385,9 +385,9 @@
 
 TEST_F(BoundArrayAccessorsTest, Matrix_Idx_Expr_Row) {
   auto* src = R"(
-var<in> a : mat3x2<f32>;
+var<private> a : mat3x2<f32>;
 
-var<in> c : i32;
+var<private> c : i32;
 
 fn f() {
   var b : f32 = a[1][c + 2 - 3];
@@ -395,9 +395,9 @@
 )";
 
   auto* expect = R"(
-var<in> a : mat3x2<f32>;
+var<private> a : mat3x2<f32>;
 
-var<in> c : i32;
+var<private> c : i32;
 
 fn f() {
   var b : f32 = a[1][min(u32(((c + 2) - 3)), 1u)];
@@ -411,7 +411,7 @@
 
 TEST_F(BoundArrayAccessorsTest, Matrix_Idx_Negative_Column) {
   auto* src = R"(
-var<in> a : mat3x2<f32>;
+var<private> a : mat3x2<f32>;
 
 fn f() {
   var b : f32 = a[-1][1];
@@ -419,7 +419,7 @@
 )";
 
   auto* expect = R"(
-var<in> a : mat3x2<f32>;
+var<private> a : mat3x2<f32>;
 
 fn f() {
   var b : f32 = a[0][1];
@@ -433,7 +433,7 @@
 
 TEST_F(BoundArrayAccessorsTest, Matrix_Idx_Negative_Row) {
   auto* src = R"(
-var<in> a : mat3x2<f32>;
+var<private> a : mat3x2<f32>;
 
 fn f() {
   var b : f32 = a[2][-1];
@@ -441,7 +441,7 @@
 )";
 
   auto* expect = R"(
-var<in> a : mat3x2<f32>;
+var<private> a : mat3x2<f32>;
 
 fn f() {
   var b : f32 = a[2][0];
@@ -455,7 +455,7 @@
 
 TEST_F(BoundArrayAccessorsTest, Matrix_Idx_OutOfBounds_Column) {
   auto* src = R"(
-var<in> a : mat3x2<f32>;
+var<private> a : mat3x2<f32>;
 
 fn f() {
   var b : f32 = a[5][1];
@@ -463,7 +463,7 @@
 )";
 
   auto* expect = R"(
-var<in> a : mat3x2<f32>;
+var<private> a : mat3x2<f32>;
 
 fn f() {
   var b : f32 = a[2][1];
@@ -477,7 +477,7 @@
 
 TEST_F(BoundArrayAccessorsTest, Matrix_Idx_OutOfBounds_Row) {
   auto* src = R"(
-var<in> a : mat3x2<f32>;
+var<private> a : mat3x2<f32>;
 
 fn f() {
   var b : f32 = a[2][5];
@@ -485,7 +485,7 @@
 )";
 
   auto* expect = R"(
-var<in> a : mat3x2<f32>;
+var<private> a : mat3x2<f32>;
 
 fn f() {
   var b : f32 = a[2][1];
@@ -540,7 +540,8 @@
   a : f32;
   b : array<f32>;
 };
-var<storage> s : S;
+var<storage> s : [[access(read)]]
+S;
 
 fn f() {
   var d : f32 = s.b[25];
@@ -554,7 +555,8 @@
   b : array<f32>;
 };
 
-var<storage> s : S;
+var<storage> s : [[access(read)]]
+S;
 
 fn f() {
   var d : f32 = s.b[min(u32(25), (arrayLength(s.b) - 1u))];
@@ -601,7 +603,8 @@
   b : array<f32>;
 };
 
-var<storage> s : S;
+var<storage> s : [[access(read)]]
+S;
 
 let c : u32 = 1u;
 
@@ -619,7 +622,8 @@
   b : array<f32>;
 };
 
-var<storage> s : S;
+var<storage> s : [[access(read)]]
+S;
 
 let c : u32 = 1u;
 
diff --git a/src/transform/calculate_array_length_test.cc b/src/transform/calculate_array_length_test.cc
index 7d27364..91f1291 100644
--- a/src/transform/calculate_array_length_test.cc
+++ b/src/transform/calculate_array_length_test.cc
@@ -30,7 +30,8 @@
   arr : array<i32>;
 };
 
-var<storage> sb : SB;
+var<storage> sb : [[access(read)]]
+SB;
 
 [[stage(vertex)]]
 fn main() {
@@ -48,7 +49,8 @@
 [[internal(intrinsic_buffer_size)]]
 fn tint_symbol(buffer : SB, result : ptr<function, u32>)
 
-var<storage> sb : SB;
+var<storage> sb : [[access(read)]]
+SB;
 
 [[stage(vertex)]]
 fn main() {
@@ -72,7 +74,8 @@
   arr : array<i32>;
 };
 
-var<storage> sb : SB;
+var<storage> sb : [[access(read)]]
+SB;
 
 [[stage(vertex)]]
 fn main() {
@@ -92,7 +95,8 @@
 [[internal(intrinsic_buffer_size)]]
 fn tint_symbol(buffer : SB, result : ptr<function, u32>)
 
-var<storage> sb : SB;
+var<storage> sb : [[access(read)]]
+SB;
 
 [[stage(vertex)]]
 fn main() {
@@ -119,7 +123,8 @@
   arr : [[stride(64)]] array<i32>;
 };
 
-var<storage> sb : SB;
+var<storage> sb : [[access(read)]]
+SB;
 
 [[stage(vertex)]]
 fn main() {
@@ -138,7 +143,8 @@
 [[internal(intrinsic_buffer_size)]]
 fn tint_symbol(buffer : SB, result : ptr<function, u32>)
 
-var<storage> sb : SB;
+var<storage> sb : [[access(read)]]
+SB;
 
 [[stage(vertex)]]
 fn main() {
@@ -162,7 +168,8 @@
   arr : array<i32>;
 };
 
-var<storage> sb : SB;
+var<storage> sb : [[access(read)]]
+SB;
 
 [[stage(vertex)]]
 fn main() {
@@ -186,7 +193,8 @@
 [[internal(intrinsic_buffer_size)]]
 fn tint_symbol(buffer : SB, result : ptr<function, u32>)
 
-var<storage> sb : SB;
+var<storage> sb : [[access(read)]]
+SB;
 
 [[stage(vertex)]]
 fn main() {
@@ -225,9 +233,11 @@
   arr2 : array<vec4<f32>>;
 };
 
-var<storage> sb1 : SB1;
+var<storage> sb1 : [[access(read)]]
+SB1;
 
-var<storage> sb2 : SB2;
+var<storage> sb2 : [[access(read)]]
+SB2;
 
 [[stage(vertex)]]
 fn main() {
@@ -256,9 +266,11 @@
 [[internal(intrinsic_buffer_size)]]
 fn tint_symbol_3(buffer : SB2, result : ptr<function, u32>)
 
-var<storage> sb1 : SB1;
+var<storage> sb1 : [[access(read)]]
+SB1;
 
-var<storage> sb2 : SB2;
+var<storage> sb2 : [[access(read)]]
+SB2;
 
 [[stage(vertex)]]
 fn main() {
diff --git a/src/transform/decompose_storage_access_test.cc b/src/transform/decompose_storage_access_test.cc
index b65d364..29bbc78 100644
--- a/src/transform/decompose_storage_access_test.cc
+++ b/src/transform/decompose_storage_access_test.cc
@@ -50,7 +50,8 @@
   v : array<vec3<f32>, 2>;
 };
 
-var<storage> sb : SB;
+var<storage> sb : [[access(read_write)]]
+SB;
 
 [[stage(compute)]]
 fn main() {
@@ -107,91 +108,117 @@
 };
 
 [[internal(intrinsic_load_i32)]]
-fn tint_symbol(buffer : SB, offset : u32) -> i32
+fn tint_symbol(buffer : [[access(read_write)]]
+SB, offset : u32) -> i32
 
 [[internal(intrinsic_load_u32)]]
-fn tint_symbol_1(buffer : SB, offset : u32) -> u32
+fn tint_symbol_1(buffer : [[access(read_write)]]
+SB, offset : u32) -> u32
 
 [[internal(intrinsic_load_f32)]]
-fn tint_symbol_2(buffer : SB, offset : u32) -> f32
+fn tint_symbol_2(buffer : [[access(read_write)]]
+SB, offset : u32) -> f32
 
 [[internal(intrinsic_load_vec2_i32)]]
-fn tint_symbol_3(buffer : SB, offset : u32) -> vec2<i32>
+fn tint_symbol_3(buffer : [[access(read_write)]]
+SB, offset : u32) -> vec2<i32>
 
 [[internal(intrinsic_load_vec2_u32)]]
-fn tint_symbol_4(buffer : SB, offset : u32) -> vec2<u32>
+fn tint_symbol_4(buffer : [[access(read_write)]]
+SB, offset : u32) -> vec2<u32>
 
 [[internal(intrinsic_load_vec2_f32)]]
-fn tint_symbol_5(buffer : SB, offset : u32) -> vec2<f32>
+fn tint_symbol_5(buffer : [[access(read_write)]]
+SB, offset : u32) -> vec2<f32>
 
 [[internal(intrinsic_load_vec3_i32)]]
-fn tint_symbol_6(buffer : SB, offset : u32) -> vec3<i32>
+fn tint_symbol_6(buffer : [[access(read_write)]]
+SB, offset : u32) -> vec3<i32>
 
 [[internal(intrinsic_load_vec3_u32)]]
-fn tint_symbol_7(buffer : SB, offset : u32) -> vec3<u32>
+fn tint_symbol_7(buffer : [[access(read_write)]]
+SB, offset : u32) -> vec3<u32>
 
 [[internal(intrinsic_load_vec3_f32)]]
-fn tint_symbol_8(buffer : SB, offset : u32) -> vec3<f32>
+fn tint_symbol_8(buffer : [[access(read_write)]]
+SB, offset : u32) -> vec3<f32>
 
 [[internal(intrinsic_load_vec4_i32)]]
-fn tint_symbol_9(buffer : SB, offset : u32) -> vec4<i32>
+fn tint_symbol_9(buffer : [[access(read_write)]]
+SB, offset : u32) -> vec4<i32>
 
 [[internal(intrinsic_load_vec4_u32)]]
-fn tint_symbol_10(buffer : SB, offset : u32) -> vec4<u32>
+fn tint_symbol_10(buffer : [[access(read_write)]]
+SB, offset : u32) -> vec4<u32>
 
 [[internal(intrinsic_load_vec4_f32)]]
-fn tint_symbol_11(buffer : SB, offset : u32) -> vec4<f32>
+fn tint_symbol_11(buffer : [[access(read_write)]]
+SB, offset : u32) -> vec4<f32>
 
 [[internal(intrinsic_load_vec2_f32)]]
-fn tint_symbol_12(buffer : SB, offset : u32) -> vec2<f32>
+fn tint_symbol_12(buffer : [[access(read_write)]]
+SB, offset : u32) -> vec2<f32>
 
-fn tint_symbol_13(buffer : SB, offset : u32) -> mat2x2<f32> {
+fn tint_symbol_13(buffer : [[access(read_write)]]
+SB, offset : u32) -> mat2x2<f32> {
   return mat2x2<f32>(tint_symbol_12(buffer, (offset + 0u)), tint_symbol_12(buffer, (offset + 8u)));
 }
 
 [[internal(intrinsic_load_vec3_f32)]]
-fn tint_symbol_14(buffer : SB, offset : u32) -> vec3<f32>
+fn tint_symbol_14(buffer : [[access(read_write)]]
+SB, offset : u32) -> vec3<f32>
 
-fn tint_symbol_15(buffer : SB, offset : u32) -> mat2x3<f32> {
+fn tint_symbol_15(buffer : [[access(read_write)]]
+SB, offset : u32) -> mat2x3<f32> {
   return mat2x3<f32>(tint_symbol_14(buffer, (offset + 0u)), tint_symbol_14(buffer, (offset + 16u)));
 }
 
 [[internal(intrinsic_load_vec4_f32)]]
-fn tint_symbol_16(buffer : SB, offset : u32) -> vec4<f32>
+fn tint_symbol_16(buffer : [[access(read_write)]]
+SB, offset : u32) -> vec4<f32>
 
-fn tint_symbol_17(buffer : SB, offset : u32) -> mat2x4<f32> {
+fn tint_symbol_17(buffer : [[access(read_write)]]
+SB, offset : u32) -> mat2x4<f32> {
   return mat2x4<f32>(tint_symbol_16(buffer, (offset + 0u)), tint_symbol_16(buffer, (offset + 16u)));
 }
 
-fn tint_symbol_18(buffer : SB, offset : u32) -> mat3x2<f32> {
+fn tint_symbol_18(buffer : [[access(read_write)]]
+SB, offset : u32) -> mat3x2<f32> {
   return mat3x2<f32>(tint_symbol_12(buffer, (offset + 0u)), tint_symbol_12(buffer, (offset + 8u)), tint_symbol_12(buffer, (offset + 16u)));
 }
 
-fn tint_symbol_19(buffer : SB, offset : u32) -> mat3x3<f32> {
+fn tint_symbol_19(buffer : [[access(read_write)]]
+SB, offset : u32) -> mat3x3<f32> {
   return mat3x3<f32>(tint_symbol_14(buffer, (offset + 0u)), tint_symbol_14(buffer, (offset + 16u)), tint_symbol_14(buffer, (offset + 32u)));
 }
 
-fn tint_symbol_20(buffer : SB, offset : u32) -> mat3x4<f32> {
+fn tint_symbol_20(buffer : [[access(read_write)]]
+SB, offset : u32) -> mat3x4<f32> {
   return mat3x4<f32>(tint_symbol_16(buffer, (offset + 0u)), tint_symbol_16(buffer, (offset + 16u)), tint_symbol_16(buffer, (offset + 32u)));
 }
 
-fn tint_symbol_21(buffer : SB, offset : u32) -> mat4x2<f32> {
+fn tint_symbol_21(buffer : [[access(read_write)]]
+SB, offset : u32) -> mat4x2<f32> {
   return mat4x2<f32>(tint_symbol_12(buffer, (offset + 0u)), tint_symbol_12(buffer, (offset + 8u)), tint_symbol_12(buffer, (offset + 16u)), tint_symbol_12(buffer, (offset + 24u)));
 }
 
-fn tint_symbol_22(buffer : SB, offset : u32) -> mat4x3<f32> {
+fn tint_symbol_22(buffer : [[access(read_write)]]
+SB, offset : u32) -> mat4x3<f32> {
   return mat4x3<f32>(tint_symbol_14(buffer, (offset + 0u)), tint_symbol_14(buffer, (offset + 16u)), tint_symbol_14(buffer, (offset + 32u)), tint_symbol_14(buffer, (offset + 48u)));
 }
 
-fn tint_symbol_23(buffer : SB, offset : u32) -> mat4x4<f32> {
+fn tint_symbol_23(buffer : [[access(read_write)]]
+SB, offset : u32) -> mat4x4<f32> {
   return mat4x4<f32>(tint_symbol_16(buffer, (offset + 0u)), tint_symbol_16(buffer, (offset + 16u)), tint_symbol_16(buffer, (offset + 32u)), tint_symbol_16(buffer, (offset + 48u)));
 }
 
-fn tint_symbol_24(buffer : SB, offset : u32) -> array<vec3<f32>, 2> {
+fn tint_symbol_24(buffer : [[access(read_write)]]
+SB, offset : u32) -> array<vec3<f32>, 2> {
   return array<vec3<f32>, 2>(tint_symbol_8(buffer, (offset + 0u)), tint_symbol_8(buffer, (offset + 16u)));
 }
 
-var<storage> sb : SB;
+var<storage> sb : [[access(read_write)]]
+SB;
 
 [[stage(compute)]]
 fn main() {
@@ -253,7 +280,8 @@
   v : array<vec3<f32>, 2>;
 };
 
-var<storage> sb : SB;
+var<storage> sb : [[access(read_write)]]
+SB;
 
 [[stage(compute)]]
 fn main() {
@@ -310,110 +338,136 @@
 };
 
 [[internal(intrinsic_store_i32)]]
-fn tint_symbol(buffer : SB, offset : u32, value : i32)
+fn tint_symbol(buffer : [[access(read_write)]]
+SB, offset : u32, value : i32)
 
 [[internal(intrinsic_store_u32)]]
-fn tint_symbol_1(buffer : SB, offset : u32, value : u32)
+fn tint_symbol_1(buffer : [[access(read_write)]]
+SB, offset : u32, value : u32)
 
 [[internal(intrinsic_store_f32)]]
-fn tint_symbol_2(buffer : SB, offset : u32, value : f32)
+fn tint_symbol_2(buffer : [[access(read_write)]]
+SB, offset : u32, value : f32)
 
 [[internal(intrinsic_store_vec2_u32)]]
-fn tint_symbol_3(buffer : SB, offset : u32, value : vec2<i32>)
+fn tint_symbol_3(buffer : [[access(read_write)]]
+SB, offset : u32, value : vec2<i32>)
 
 [[internal(intrinsic_store_vec2_f32)]]
-fn tint_symbol_4(buffer : SB, offset : u32, value : vec2<u32>)
+fn tint_symbol_4(buffer : [[access(read_write)]]
+SB, offset : u32, value : vec2<u32>)
 
 [[internal(intrinsic_store_vec2_i32)]]
-fn tint_symbol_5(buffer : SB, offset : u32, value : vec2<f32>)
+fn tint_symbol_5(buffer : [[access(read_write)]]
+SB, offset : u32, value : vec2<f32>)
 
 [[internal(intrinsic_store_vec3_u32)]]
-fn tint_symbol_6(buffer : SB, offset : u32, value : vec3<i32>)
+fn tint_symbol_6(buffer : [[access(read_write)]]
+SB, offset : u32, value : vec3<i32>)
 
 [[internal(intrinsic_store_vec3_f32)]]
-fn tint_symbol_7(buffer : SB, offset : u32, value : vec3<u32>)
+fn tint_symbol_7(buffer : [[access(read_write)]]
+SB, offset : u32, value : vec3<u32>)
 
 [[internal(intrinsic_store_vec3_i32)]]
-fn tint_symbol_8(buffer : SB, offset : u32, value : vec3<f32>)
+fn tint_symbol_8(buffer : [[access(read_write)]]
+SB, offset : u32, value : vec3<f32>)
 
 [[internal(intrinsic_store_vec4_u32)]]
-fn tint_symbol_9(buffer : SB, offset : u32, value : vec4<i32>)
+fn tint_symbol_9(buffer : [[access(read_write)]]
+SB, offset : u32, value : vec4<i32>)
 
 [[internal(intrinsic_store_vec4_f32)]]
-fn tint_symbol_10(buffer : SB, offset : u32, value : vec4<u32>)
+fn tint_symbol_10(buffer : [[access(read_write)]]
+SB, offset : u32, value : vec4<u32>)
 
 [[internal(intrinsic_store_vec4_i32)]]
-fn tint_symbol_11(buffer : SB, offset : u32, value : vec4<f32>)
+fn tint_symbol_11(buffer : [[access(read_write)]]
+SB, offset : u32, value : vec4<f32>)
 
 [[internal(intrinsic_store_vec2_i32)]]
-fn tint_symbol_12(buffer : SB, offset : u32, value : vec2<f32>)
+fn tint_symbol_12(buffer : [[access(read_write)]]
+SB, offset : u32, value : vec2<f32>)
 
-fn tint_symbol_13(buffer : SB, offset : u32, value : mat2x2<f32>) {
+fn tint_symbol_13(buffer : [[access(read_write)]]
+SB, offset : u32, value : mat2x2<f32>) {
   tint_symbol_12(buffer, (offset + 0u), value[0u]);
   tint_symbol_12(buffer, (offset + 8u), value[1u]);
 }
 
 [[internal(intrinsic_store_vec3_i32)]]
-fn tint_symbol_14(buffer : SB, offset : u32, value : vec3<f32>)
+fn tint_symbol_14(buffer : [[access(read_write)]]
+SB, offset : u32, value : vec3<f32>)
 
-fn tint_symbol_15(buffer : SB, offset : u32, value : mat2x3<f32>) {
+fn tint_symbol_15(buffer : [[access(read_write)]]
+SB, offset : u32, value : mat2x3<f32>) {
   tint_symbol_14(buffer, (offset + 0u), value[0u]);
   tint_symbol_14(buffer, (offset + 16u), value[1u]);
 }
 
 [[internal(intrinsic_store_vec4_i32)]]
-fn tint_symbol_16(buffer : SB, offset : u32, value : vec4<f32>)
+fn tint_symbol_16(buffer : [[access(read_write)]]
+SB, offset : u32, value : vec4<f32>)
 
-fn tint_symbol_17(buffer : SB, offset : u32, value : mat2x4<f32>) {
+fn tint_symbol_17(buffer : [[access(read_write)]]
+SB, offset : u32, value : mat2x4<f32>) {
   tint_symbol_16(buffer, (offset + 0u), value[0u]);
   tint_symbol_16(buffer, (offset + 16u), value[1u]);
 }
 
-fn tint_symbol_18(buffer : SB, offset : u32, value : mat3x2<f32>) {
+fn tint_symbol_18(buffer : [[access(read_write)]]
+SB, offset : u32, value : mat3x2<f32>) {
   tint_symbol_12(buffer, (offset + 0u), value[0u]);
   tint_symbol_12(buffer, (offset + 8u), value[1u]);
   tint_symbol_12(buffer, (offset + 16u), value[2u]);
 }
 
-fn tint_symbol_19(buffer : SB, offset : u32, value : mat3x3<f32>) {
+fn tint_symbol_19(buffer : [[access(read_write)]]
+SB, offset : u32, value : mat3x3<f32>) {
   tint_symbol_14(buffer, (offset + 0u), value[0u]);
   tint_symbol_14(buffer, (offset + 16u), value[1u]);
   tint_symbol_14(buffer, (offset + 32u), value[2u]);
 }
 
-fn tint_symbol_20(buffer : SB, offset : u32, value : mat3x4<f32>) {
+fn tint_symbol_20(buffer : [[access(read_write)]]
+SB, offset : u32, value : mat3x4<f32>) {
   tint_symbol_16(buffer, (offset + 0u), value[0u]);
   tint_symbol_16(buffer, (offset + 16u), value[1u]);
   tint_symbol_16(buffer, (offset + 32u), value[2u]);
 }
 
-fn tint_symbol_21(buffer : SB, offset : u32, value : mat4x2<f32>) {
+fn tint_symbol_21(buffer : [[access(read_write)]]
+SB, offset : u32, value : mat4x2<f32>) {
   tint_symbol_12(buffer, (offset + 0u), value[0u]);
   tint_symbol_12(buffer, (offset + 8u), value[1u]);
   tint_symbol_12(buffer, (offset + 16u), value[2u]);
   tint_symbol_12(buffer, (offset + 24u), value[3u]);
 }
 
-fn tint_symbol_22(buffer : SB, offset : u32, value : mat4x3<f32>) {
+fn tint_symbol_22(buffer : [[access(read_write)]]
+SB, offset : u32, value : mat4x3<f32>) {
   tint_symbol_14(buffer, (offset + 0u), value[0u]);
   tint_symbol_14(buffer, (offset + 16u), value[1u]);
   tint_symbol_14(buffer, (offset + 32u), value[2u]);
   tint_symbol_14(buffer, (offset + 48u), value[3u]);
 }
 
-fn tint_symbol_23(buffer : SB, offset : u32, value : mat4x4<f32>) {
+fn tint_symbol_23(buffer : [[access(read_write)]]
+SB, offset : u32, value : mat4x4<f32>) {
   tint_symbol_16(buffer, (offset + 0u), value[0u]);
   tint_symbol_16(buffer, (offset + 16u), value[1u]);
   tint_symbol_16(buffer, (offset + 32u), value[2u]);
   tint_symbol_16(buffer, (offset + 48u), value[3u]);
 }
 
-fn tint_symbol_24(buffer : SB, offset : u32, value : array<vec3<f32>, 2>) {
+fn tint_symbol_24(buffer : [[access(read_write)]]
+SB, offset : u32, value : array<vec3<f32>, 2>) {
   tint_symbol_8(buffer, (offset + 0u), value[0u]);
   tint_symbol_8(buffer, (offset + 16u), value[1u]);
 }
 
-var<storage> sb : SB;
+var<storage> sb : [[access(read_write)]]
+SB;
 
 [[stage(compute)]]
 fn main() {
@@ -475,7 +529,8 @@
   v : array<vec3<f32>, 2>;
 };
 
-var<storage> sb : SB;
+var<storage> sb : [[access(read_write)]]
+SB;
 
 [[stage(compute)]]
 fn main() {
@@ -511,95 +566,122 @@
 };
 
 [[internal(intrinsic_load_i32)]]
-fn tint_symbol(buffer : SB, offset : u32) -> i32
+fn tint_symbol(buffer : [[access(read_write)]]
+SB, offset : u32) -> i32
 
 [[internal(intrinsic_load_u32)]]
-fn tint_symbol_1(buffer : SB, offset : u32) -> u32
+fn tint_symbol_1(buffer : [[access(read_write)]]
+SB, offset : u32) -> u32
 
 [[internal(intrinsic_load_f32)]]
-fn tint_symbol_2(buffer : SB, offset : u32) -> f32
+fn tint_symbol_2(buffer : [[access(read_write)]]
+SB, offset : u32) -> f32
 
 [[internal(intrinsic_load_vec2_i32)]]
-fn tint_symbol_3(buffer : SB, offset : u32) -> vec2<i32>
+fn tint_symbol_3(buffer : [[access(read_write)]]
+SB, offset : u32) -> vec2<i32>
 
 [[internal(intrinsic_load_vec2_u32)]]
-fn tint_symbol_4(buffer : SB, offset : u32) -> vec2<u32>
+fn tint_symbol_4(buffer : [[access(read_write)]]
+SB, offset : u32) -> vec2<u32>
 
 [[internal(intrinsic_load_vec2_f32)]]
-fn tint_symbol_5(buffer : SB, offset : u32) -> vec2<f32>
+fn tint_symbol_5(buffer : [[access(read_write)]]
+SB, offset : u32) -> vec2<f32>
 
 [[internal(intrinsic_load_vec3_i32)]]
-fn tint_symbol_6(buffer : SB, offset : u32) -> vec3<i32>
+fn tint_symbol_6(buffer : [[access(read_write)]]
+SB, offset : u32) -> vec3<i32>
 
 [[internal(intrinsic_load_vec3_u32)]]
-fn tint_symbol_7(buffer : SB, offset : u32) -> vec3<u32>
+fn tint_symbol_7(buffer : [[access(read_write)]]
+SB, offset : u32) -> vec3<u32>
 
 [[internal(intrinsic_load_vec3_f32)]]
-fn tint_symbol_8(buffer : SB, offset : u32) -> vec3<f32>
+fn tint_symbol_8(buffer : [[access(read_write)]]
+SB, offset : u32) -> vec3<f32>
 
 [[internal(intrinsic_load_vec4_i32)]]
-fn tint_symbol_9(buffer : SB, offset : u32) -> vec4<i32>
+fn tint_symbol_9(buffer : [[access(read_write)]]
+SB, offset : u32) -> vec4<i32>
 
 [[internal(intrinsic_load_vec4_u32)]]
-fn tint_symbol_10(buffer : SB, offset : u32) -> vec4<u32>
+fn tint_symbol_10(buffer : [[access(read_write)]]
+SB, offset : u32) -> vec4<u32>
 
 [[internal(intrinsic_load_vec4_f32)]]
-fn tint_symbol_11(buffer : SB, offset : u32) -> vec4<f32>
+fn tint_symbol_11(buffer : [[access(read_write)]]
+SB, offset : u32) -> vec4<f32>
 
 [[internal(intrinsic_load_vec2_f32)]]
-fn tint_symbol_12(buffer : SB, offset : u32) -> vec2<f32>
+fn tint_symbol_12(buffer : [[access(read_write)]]
+SB, offset : u32) -> vec2<f32>
 
-fn tint_symbol_13(buffer : SB, offset : u32) -> mat2x2<f32> {
+fn tint_symbol_13(buffer : [[access(read_write)]]
+SB, offset : u32) -> mat2x2<f32> {
   return mat2x2<f32>(tint_symbol_12(buffer, (offset + 0u)), tint_symbol_12(buffer, (offset + 8u)));
 }
 
 [[internal(intrinsic_load_vec3_f32)]]
-fn tint_symbol_14(buffer : SB, offset : u32) -> vec3<f32>
+fn tint_symbol_14(buffer : [[access(read_write)]]
+SB, offset : u32) -> vec3<f32>
 
-fn tint_symbol_15(buffer : SB, offset : u32) -> mat2x3<f32> {
+fn tint_symbol_15(buffer : [[access(read_write)]]
+SB, offset : u32) -> mat2x3<f32> {
   return mat2x3<f32>(tint_symbol_14(buffer, (offset + 0u)), tint_symbol_14(buffer, (offset + 16u)));
 }
 
 [[internal(intrinsic_load_vec4_f32)]]
-fn tint_symbol_16(buffer : SB, offset : u32) -> vec4<f32>
+fn tint_symbol_16(buffer : [[access(read_write)]]
+SB, offset : u32) -> vec4<f32>
 
-fn tint_symbol_17(buffer : SB, offset : u32) -> mat2x4<f32> {
+fn tint_symbol_17(buffer : [[access(read_write)]]
+SB, offset : u32) -> mat2x4<f32> {
   return mat2x4<f32>(tint_symbol_16(buffer, (offset + 0u)), tint_symbol_16(buffer, (offset + 16u)));
 }
 
-fn tint_symbol_18(buffer : SB, offset : u32) -> mat3x2<f32> {
+fn tint_symbol_18(buffer : [[access(read_write)]]
+SB, offset : u32) -> mat3x2<f32> {
   return mat3x2<f32>(tint_symbol_12(buffer, (offset + 0u)), tint_symbol_12(buffer, (offset + 8u)), tint_symbol_12(buffer, (offset + 16u)));
 }
 
-fn tint_symbol_19(buffer : SB, offset : u32) -> mat3x3<f32> {
+fn tint_symbol_19(buffer : [[access(read_write)]]
+SB, offset : u32) -> mat3x3<f32> {
   return mat3x3<f32>(tint_symbol_14(buffer, (offset + 0u)), tint_symbol_14(buffer, (offset + 16u)), tint_symbol_14(buffer, (offset + 32u)));
 }
 
-fn tint_symbol_20(buffer : SB, offset : u32) -> mat3x4<f32> {
+fn tint_symbol_20(buffer : [[access(read_write)]]
+SB, offset : u32) -> mat3x4<f32> {
   return mat3x4<f32>(tint_symbol_16(buffer, (offset + 0u)), tint_symbol_16(buffer, (offset + 16u)), tint_symbol_16(buffer, (offset + 32u)));
 }
 
-fn tint_symbol_21(buffer : SB, offset : u32) -> mat4x2<f32> {
+fn tint_symbol_21(buffer : [[access(read_write)]]
+SB, offset : u32) -> mat4x2<f32> {
   return mat4x2<f32>(tint_symbol_12(buffer, (offset + 0u)), tint_symbol_12(buffer, (offset + 8u)), tint_symbol_12(buffer, (offset + 16u)), tint_symbol_12(buffer, (offset + 24u)));
 }
 
-fn tint_symbol_22(buffer : SB, offset : u32) -> mat4x3<f32> {
+fn tint_symbol_22(buffer : [[access(read_write)]]
+SB, offset : u32) -> mat4x3<f32> {
   return mat4x3<f32>(tint_symbol_14(buffer, (offset + 0u)), tint_symbol_14(buffer, (offset + 16u)), tint_symbol_14(buffer, (offset + 32u)), tint_symbol_14(buffer, (offset + 48u)));
 }
 
-fn tint_symbol_23(buffer : SB, offset : u32) -> mat4x4<f32> {
+fn tint_symbol_23(buffer : [[access(read_write)]]
+SB, offset : u32) -> mat4x4<f32> {
   return mat4x4<f32>(tint_symbol_16(buffer, (offset + 0u)), tint_symbol_16(buffer, (offset + 16u)), tint_symbol_16(buffer, (offset + 32u)), tint_symbol_16(buffer, (offset + 48u)));
 }
 
-fn tint_symbol_24(buffer : SB, offset : u32) -> array<vec3<f32>, 2> {
+fn tint_symbol_24(buffer : [[access(read_write)]]
+SB, offset : u32) -> array<vec3<f32>, 2> {
   return array<vec3<f32>, 2>(tint_symbol_8(buffer, (offset + 0u)), tint_symbol_8(buffer, (offset + 16u)));
 }
 
-fn tint_symbol_25(buffer : SB, offset : u32) -> SB {
+fn tint_symbol_25(buffer : [[access(read_write)]]
+SB, offset : u32) -> SB {
   return SB(tint_symbol(buffer, (offset + 0u)), tint_symbol_1(buffer, (offset + 4u)), tint_symbol_2(buffer, (offset + 8u)), tint_symbol_3(buffer, (offset + 16u)), tint_symbol_4(buffer, (offset + 24u)), tint_symbol_5(buffer, (offset + 32u)), tint_symbol_6(buffer, (offset + 48u)), tint_symbol_7(buffer, (offset + 64u)), tint_symbol_8(buffer, (offset + 80u)), tint_symbol_9(buffer, (offset + 96u)), tint_symbol_10(buffer, (offset + 112u)), tint_symbol_11(buffer, (offset + 128u)), tint_symbol_13(buffer, (offset + 144u)), tint_symbol_15(buffer, (offset + 160u)), tint_symbol_17(buffer, (offset + 192u)), tint_symbol_18(buffer, (offset + 224u)), tint_symbol_19(buffer, (offset + 256u)), tint_symbol_20(buffer, (offset + 304u)), tint_symbol_21(buffer, (offset + 352u)), tint_symbol_22(buffer, (offset + 384u)), tint_symbol_23(buffer, (offset + 448u)), tint_symbol_24(buffer, (offset + 512u)));
 }
 
-var<storage> sb : SB;
+var<storage> sb : [[access(read_write)]]
+SB;
 
 [[stage(compute)]]
 fn main() {
@@ -640,7 +722,8 @@
   v : array<vec3<f32>, 2>;
 };
 
-var<storage> sb : SB;
+var<storage> sb : [[access(read_write)]]
+SB;
 
 [[stage(compute)]]
 fn main() {
@@ -676,110 +759,136 @@
 };
 
 [[internal(intrinsic_store_i32)]]
-fn tint_symbol(buffer : SB, offset : u32, value : i32)
+fn tint_symbol(buffer : [[access(read_write)]]
+SB, offset : u32, value : i32)
 
 [[internal(intrinsic_store_u32)]]
-fn tint_symbol_1(buffer : SB, offset : u32, value : u32)
+fn tint_symbol_1(buffer : [[access(read_write)]]
+SB, offset : u32, value : u32)
 
 [[internal(intrinsic_store_f32)]]
-fn tint_symbol_2(buffer : SB, offset : u32, value : f32)
+fn tint_symbol_2(buffer : [[access(read_write)]]
+SB, offset : u32, value : f32)
 
 [[internal(intrinsic_store_vec2_u32)]]
-fn tint_symbol_3(buffer : SB, offset : u32, value : vec2<i32>)
+fn tint_symbol_3(buffer : [[access(read_write)]]
+SB, offset : u32, value : vec2<i32>)
 
 [[internal(intrinsic_store_vec2_f32)]]
-fn tint_symbol_4(buffer : SB, offset : u32, value : vec2<u32>)
+fn tint_symbol_4(buffer : [[access(read_write)]]
+SB, offset : u32, value : vec2<u32>)
 
 [[internal(intrinsic_store_vec2_i32)]]
-fn tint_symbol_5(buffer : SB, offset : u32, value : vec2<f32>)
+fn tint_symbol_5(buffer : [[access(read_write)]]
+SB, offset : u32, value : vec2<f32>)
 
 [[internal(intrinsic_store_vec3_u32)]]
-fn tint_symbol_6(buffer : SB, offset : u32, value : vec3<i32>)
+fn tint_symbol_6(buffer : [[access(read_write)]]
+SB, offset : u32, value : vec3<i32>)
 
 [[internal(intrinsic_store_vec3_f32)]]
-fn tint_symbol_7(buffer : SB, offset : u32, value : vec3<u32>)
+fn tint_symbol_7(buffer : [[access(read_write)]]
+SB, offset : u32, value : vec3<u32>)
 
 [[internal(intrinsic_store_vec3_i32)]]
-fn tint_symbol_8(buffer : SB, offset : u32, value : vec3<f32>)
+fn tint_symbol_8(buffer : [[access(read_write)]]
+SB, offset : u32, value : vec3<f32>)
 
 [[internal(intrinsic_store_vec4_u32)]]
-fn tint_symbol_9(buffer : SB, offset : u32, value : vec4<i32>)
+fn tint_symbol_9(buffer : [[access(read_write)]]
+SB, offset : u32, value : vec4<i32>)
 
 [[internal(intrinsic_store_vec4_f32)]]
-fn tint_symbol_10(buffer : SB, offset : u32, value : vec4<u32>)
+fn tint_symbol_10(buffer : [[access(read_write)]]
+SB, offset : u32, value : vec4<u32>)
 
 [[internal(intrinsic_store_vec4_i32)]]
-fn tint_symbol_11(buffer : SB, offset : u32, value : vec4<f32>)
+fn tint_symbol_11(buffer : [[access(read_write)]]
+SB, offset : u32, value : vec4<f32>)
 
 [[internal(intrinsic_store_vec2_i32)]]
-fn tint_symbol_12(buffer : SB, offset : u32, value : vec2<f32>)
+fn tint_symbol_12(buffer : [[access(read_write)]]
+SB, offset : u32, value : vec2<f32>)
 
-fn tint_symbol_13(buffer : SB, offset : u32, value : mat2x2<f32>) {
+fn tint_symbol_13(buffer : [[access(read_write)]]
+SB, offset : u32, value : mat2x2<f32>) {
   tint_symbol_12(buffer, (offset + 0u), value[0u]);
   tint_symbol_12(buffer, (offset + 8u), value[1u]);
 }
 
 [[internal(intrinsic_store_vec3_i32)]]
-fn tint_symbol_14(buffer : SB, offset : u32, value : vec3<f32>)
+fn tint_symbol_14(buffer : [[access(read_write)]]
+SB, offset : u32, value : vec3<f32>)
 
-fn tint_symbol_15(buffer : SB, offset : u32, value : mat2x3<f32>) {
+fn tint_symbol_15(buffer : [[access(read_write)]]
+SB, offset : u32, value : mat2x3<f32>) {
   tint_symbol_14(buffer, (offset + 0u), value[0u]);
   tint_symbol_14(buffer, (offset + 16u), value[1u]);
 }
 
 [[internal(intrinsic_store_vec4_i32)]]
-fn tint_symbol_16(buffer : SB, offset : u32, value : vec4<f32>)
+fn tint_symbol_16(buffer : [[access(read_write)]]
+SB, offset : u32, value : vec4<f32>)
 
-fn tint_symbol_17(buffer : SB, offset : u32, value : mat2x4<f32>) {
+fn tint_symbol_17(buffer : [[access(read_write)]]
+SB, offset : u32, value : mat2x4<f32>) {
   tint_symbol_16(buffer, (offset + 0u), value[0u]);
   tint_symbol_16(buffer, (offset + 16u), value[1u]);
 }
 
-fn tint_symbol_18(buffer : SB, offset : u32, value : mat3x2<f32>) {
+fn tint_symbol_18(buffer : [[access(read_write)]]
+SB, offset : u32, value : mat3x2<f32>) {
   tint_symbol_12(buffer, (offset + 0u), value[0u]);
   tint_symbol_12(buffer, (offset + 8u), value[1u]);
   tint_symbol_12(buffer, (offset + 16u), value[2u]);
 }
 
-fn tint_symbol_19(buffer : SB, offset : u32, value : mat3x3<f32>) {
+fn tint_symbol_19(buffer : [[access(read_write)]]
+SB, offset : u32, value : mat3x3<f32>) {
   tint_symbol_14(buffer, (offset + 0u), value[0u]);
   tint_symbol_14(buffer, (offset + 16u), value[1u]);
   tint_symbol_14(buffer, (offset + 32u), value[2u]);
 }
 
-fn tint_symbol_20(buffer : SB, offset : u32, value : mat3x4<f32>) {
+fn tint_symbol_20(buffer : [[access(read_write)]]
+SB, offset : u32, value : mat3x4<f32>) {
   tint_symbol_16(buffer, (offset + 0u), value[0u]);
   tint_symbol_16(buffer, (offset + 16u), value[1u]);
   tint_symbol_16(buffer, (offset + 32u), value[2u]);
 }
 
-fn tint_symbol_21(buffer : SB, offset : u32, value : mat4x2<f32>) {
+fn tint_symbol_21(buffer : [[access(read_write)]]
+SB, offset : u32, value : mat4x2<f32>) {
   tint_symbol_12(buffer, (offset + 0u), value[0u]);
   tint_symbol_12(buffer, (offset + 8u), value[1u]);
   tint_symbol_12(buffer, (offset + 16u), value[2u]);
   tint_symbol_12(buffer, (offset + 24u), value[3u]);
 }
 
-fn tint_symbol_22(buffer : SB, offset : u32, value : mat4x3<f32>) {
+fn tint_symbol_22(buffer : [[access(read_write)]]
+SB, offset : u32, value : mat4x3<f32>) {
   tint_symbol_14(buffer, (offset + 0u), value[0u]);
   tint_symbol_14(buffer, (offset + 16u), value[1u]);
   tint_symbol_14(buffer, (offset + 32u), value[2u]);
   tint_symbol_14(buffer, (offset + 48u), value[3u]);
 }
 
-fn tint_symbol_23(buffer : SB, offset : u32, value : mat4x4<f32>) {
+fn tint_symbol_23(buffer : [[access(read_write)]]
+SB, offset : u32, value : mat4x4<f32>) {
   tint_symbol_16(buffer, (offset + 0u), value[0u]);
   tint_symbol_16(buffer, (offset + 16u), value[1u]);
   tint_symbol_16(buffer, (offset + 32u), value[2u]);
   tint_symbol_16(buffer, (offset + 48u), value[3u]);
 }
 
-fn tint_symbol_24(buffer : SB, offset : u32, value : array<vec3<f32>, 2>) {
+fn tint_symbol_24(buffer : [[access(read_write)]]
+SB, offset : u32, value : array<vec3<f32>, 2>) {
   tint_symbol_8(buffer, (offset + 0u), value[0u]);
   tint_symbol_8(buffer, (offset + 16u), value[1u]);
 }
 
-fn tint_symbol_25(buffer : SB, offset : u32, value : SB) {
+fn tint_symbol_25(buffer : [[access(read_write)]]
+SB, offset : u32, value : SB) {
   tint_symbol(buffer, (offset + 0u), value.a);
   tint_symbol_1(buffer, (offset + 4u), value.b);
   tint_symbol_2(buffer, (offset + 8u), value.c);
@@ -804,7 +913,8 @@
   tint_symbol_24(buffer, (offset + 512u), value.v);
 }
 
-var<storage> sb : SB;
+var<storage> sb : [[access(read_write)]]
+SB;
 
 [[stage(compute)]]
 fn main() {
@@ -838,7 +948,8 @@
   b : [[stride(256)]] array<S2>;
 };
 
-var<storage> sb : SB;
+var<storage> sb : [[access(read_write)]]
+SB;
 
 [[stage(compute)]]
 fn main() {
@@ -874,9 +985,11 @@
 };
 
 [[internal(intrinsic_load_f32)]]
-fn tint_symbol(buffer : SB, offset : u32) -> f32
+fn tint_symbol(buffer : [[access(read_write)]]
+SB, offset : u32) -> f32
 
-var<storage> sb : SB;
+var<storage> sb : [[access(read_write)]]
+SB;
 
 [[stage(compute)]]
 fn main() {
@@ -910,7 +1023,8 @@
   b : [[stride(256)]] array<S2>;
 };
 
-var<storage> sb : SB;
+var<storage> sb : [[access(read_write)]]
+SB;
 
 [[stage(compute)]]
 fn main() {
@@ -942,9 +1056,11 @@
 };
 
 [[internal(intrinsic_load_f32)]]
-fn tint_symbol(buffer : SB, offset : u32) -> f32
+fn tint_symbol(buffer : [[access(read_write)]]
+SB, offset : u32) -> f32
 
-var<storage> sb : SB;
+var<storage> sb : [[access(read_write)]]
+SB;
 
 [[stage(compute)]]
 fn main() {
@@ -989,7 +1105,8 @@
   b : A2_Array;
 };
 
-var<storage> sb : SB;
+var<storage> sb : [[access(read_write)]]
+SB;
 
 [[stage(compute)]]
 fn main() {
@@ -1029,9 +1146,11 @@
 };
 
 [[internal(intrinsic_load_f32)]]
-fn tint_symbol(buffer : SB, offset : u32) -> f32
+fn tint_symbol(buffer : [[access(read_write)]]
+SB, offset : u32) -> f32
 
-var<storage> sb : SB;
+var<storage> sb : [[access(read_write)]]
+SB;
 
 [[stage(compute)]]
 fn main() {
diff --git a/src/transform/vertex_pulling.cc b/src/transform/vertex_pulling.cc
index 3657ea5..a34acf3 100644
--- a/src/transform/vertex_pulling.cc
+++ b/src/transform/vertex_pulling.cc
@@ -209,12 +209,12 @@
         {
             ctx.dst->create<ast::StructBlockDecoration>(),
         });
-
+    auto* access =
+        ctx.dst->ty.access(ast::AccessControl::kReadOnly, struct_type);
     for (uint32_t i = 0; i < cfg.vertex_state.size(); ++i) {
       // The decorated variable with struct type
       ctx.dst->Global(
-          GetVertexBufferName(i), struct_type, ast::StorageClass::kStorage,
-          nullptr,
+          GetVertexBufferName(i), access, ast::StorageClass::kStorage, nullptr,
           ast::DecorationList{
               ctx.dst->create<ast::BindingDecoration>(i),
               ctx.dst->create<ast::GroupDecoration>(cfg.pulling_group),
diff --git a/src/transform/vertex_pulling_test.cc b/src/transform/vertex_pulling_test.cc
index 827b6d4..3689216 100644
--- a/src/transform/vertex_pulling_test.cc
+++ b/src/transform/vertex_pulling_test.cc
@@ -118,7 +118,8 @@
   tint_vertex_data : [[stride(4)]] array<u32>;
 };
 
-[[binding(0), group(4)]] var<storage> tint_pulling_vertex_buffer_0 : TintVertexData;
+[[binding(0), group(4)]] var<storage> tint_pulling_vertex_buffer_0 : [[access(read)]]
+TintVertexData;
 
 var<private> var_a : f32;
 
@@ -160,7 +161,8 @@
   tint_vertex_data : [[stride(4)]] array<u32>;
 };
 
-[[binding(0), group(4)]] var<storage> tint_pulling_vertex_buffer_0 : TintVertexData;
+[[binding(0), group(4)]] var<storage> tint_pulling_vertex_buffer_0 : [[access(read)]]
+TintVertexData;
 
 var<private> var_a : f32;
 
@@ -202,7 +204,8 @@
   tint_vertex_data : [[stride(4)]] array<u32>;
 };
 
-[[binding(0), group(5)]] var<storage> tint_pulling_vertex_buffer_0 : TintVertexData;
+[[binding(0), group(5)]] var<storage> tint_pulling_vertex_buffer_0 : [[access(read)]]
+TintVertexData;
 
 var<private> var_a : f32;
 
@@ -247,9 +250,11 @@
   tint_vertex_data : [[stride(4)]] array<u32>;
 };
 
-[[binding(0), group(4)]] var<storage> tint_pulling_vertex_buffer_0 : TintVertexData;
+[[binding(0), group(4)]] var<storage> tint_pulling_vertex_buffer_0 : [[access(read)]]
+TintVertexData;
 
-[[binding(1), group(4)]] var<storage> tint_pulling_vertex_buffer_1 : TintVertexData;
+[[binding(1), group(4)]] var<storage> tint_pulling_vertex_buffer_1 : [[access(read)]]
+TintVertexData;
 
 var<private> var_a : f32;
 
@@ -310,7 +315,8 @@
   tint_vertex_data : [[stride(4)]] array<u32>;
 };
 
-[[binding(0), group(4)]] var<storage> tint_pulling_vertex_buffer_0 : TintVertexData;
+[[binding(0), group(4)]] var<storage> tint_pulling_vertex_buffer_0 : [[access(read)]]
+TintVertexData;
 
 var<private> var_a : f32;
 
@@ -360,11 +366,14 @@
   tint_vertex_data : [[stride(4)]] array<u32>;
 };
 
-[[binding(0), group(4)]] var<storage> tint_pulling_vertex_buffer_0 : TintVertexData;
+[[binding(0), group(4)]] var<storage> tint_pulling_vertex_buffer_0 : [[access(read)]]
+TintVertexData;
 
-[[binding(1), group(4)]] var<storage> tint_pulling_vertex_buffer_1 : TintVertexData;
+[[binding(1), group(4)]] var<storage> tint_pulling_vertex_buffer_1 : [[access(read)]]
+TintVertexData;
 
-[[binding(2), group(4)]] var<storage> tint_pulling_vertex_buffer_2 : TintVertexData;
+[[binding(2), group(4)]] var<storage> tint_pulling_vertex_buffer_2 : [[access(read)]]
+TintVertexData;
 
 var<private> var_a : vec2<f32>;
 
@@ -423,7 +432,8 @@
   tint_vertex_data_1 : [[stride(4)]] array<u32>;
 };
 
-[[binding(0), group(4)]] var<storage> tint_pulling_vertex_buffer_0_1 : TintVertexData;
+[[binding(0), group(4)]] var<storage> tint_pulling_vertex_buffer_0_1 : [[access(read)]]
+TintVertexData;
 
 var<private> var_a : f32;
 
diff --git a/src/writer/hlsl/generator_impl_function_test.cc b/src/writer/hlsl/generator_impl_function_test.cc
index 0bbd402..cdadf42 100644
--- a/src/writer/hlsl/generator_impl_function_test.cc
+++ b/src/writer/hlsl/generator_impl_function_test.cc
@@ -777,8 +777,9 @@
 
 TEST_F(HlslGeneratorImplTest_Function,
        Emit_Decoration_Called_By_EntryPoint_With_StorageBuffer) {
-  type::AccessControl ac(ast::AccessControl::kReadWrite, ty.vec4<f32>());
-  Global("coord", &ac, ast::StorageClass::kStorage, nullptr,
+  auto* s = Structure("S", {Member("x", ty.f32())});
+  auto* ac = ty.access(ast::AccessControl::kReadWrite, s);
+  Global("coord", ac, ast::StorageClass::kStorage, nullptr,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
              create<ast::GroupDecoration>(1),
@@ -806,7 +807,8 @@
 
   ASSERT_TRUE(gen.Generate(out)) << gen.error();
   EXPECT_EQ(result(),
-            R"(RWByteAddressBuffer coord : register(u0, space1);
+            R"(
+RWByteAddressBuffer coord : register(u0, space1);
 
 float sub_func(float param) {
   return asfloat(coord.Load(0u));
diff --git a/src/writer/hlsl/generator_impl_type_test.cc b/src/writer/hlsl/generator_impl_type_test.cc
index 920434f..e03a7b49 100644
--- a/src/writer/hlsl/generator_impl_type_test.cc
+++ b/src/writer/hlsl/generator_impl_type_test.cc
@@ -177,7 +177,8 @@
                                Member("a", ty.i32()),
                                Member("b", ty.f32()),
                            });
-  Global("g", s, ast::StorageClass::kStorage);
+  Global("g", ty.access(ast::AccessControl::kReadWrite, s),
+         ast::StorageClass::kStorage);
 
   GeneratorImpl& gen = Build();
 
diff --git a/src/writer/msl/generator_impl_type_test.cc b/src/writer/msl/generator_impl_type_test.cc
index 51b0325..21920a5 100644
--- a/src/writer/msl/generator_impl_type_test.cc
+++ b/src/writer/msl/generator_impl_type_test.cc
@@ -231,7 +231,8 @@
                Member("z", ty.f32()),
            });
 
-  Global("G", s, ast::StorageClass::kStorage);
+  Global("G", ty.access(ast::AccessControl::kReadOnly, s),
+         ast::StorageClass::kStorage);
 
   GeneratorImpl& gen = Build();
 
@@ -333,7 +334,8 @@
                                Member("e", ty.f32()),
                            });
 
-  Global("G", s, ast::StorageClass::kStorage);
+  Global("G", ty.access(ast::AccessControl::kReadOnly, s),
+         ast::StorageClass::kStorage);
 
   GeneratorImpl& gen = Build();
 
@@ -422,7 +424,8 @@
                 },
                 ast::DecorationList{create<ast::StructBlockDecoration>()});
 
-  Global("G", s, ast::StorageClass::kStorage);
+  Global("G", ty.access(ast::AccessControl::kReadOnly, s),
+         ast::StorageClass::kStorage);
 
   GeneratorImpl& gen = Build();
 
@@ -500,7 +503,8 @@
                       },
                       {create<ast::StructBlockDecoration>()});
 
-  Global("G", s, ast::StorageClass::kStorage);
+  Global("G", ty.access(ast::AccessControl::kReadOnly, s),
+         ast::StorageClass::kStorage);
 
   GeneratorImpl& gen = Build();
 
diff --git a/src/writer/spirv/builder_intrinsic_test.cc b/src/writer/spirv/builder_intrinsic_test.cc
index 47fd65b..feb2c17 100644
--- a/src/writer/spirv/builder_intrinsic_test.cc
+++ b/src/writer/spirv/builder_intrinsic_test.cc
@@ -1379,7 +1379,8 @@
 TEST_F(IntrinsicBuilderTest, Call_ArrayLength) {
   auto* s = Structure("my_struct", {Member(0, "a", ty.array<f32>(4))},
                       {create<ast::StructBlockDecoration>()});
-  Global("b", s, ast::StorageClass::kStorage, nullptr,
+  auto* ac = ty.access(ast::AccessControl::kReadOnly, s);
+  Global("b", ac, ast::StorageClass::kStorage, nullptr,
          ast::DecorationList{
              create<ast::BindingDecoration>(1),
              create<ast::GroupDecoration>(2),
@@ -1428,8 +1429,8 @@
                           Member(4, "a", ty.array<f32>(4)),
                       },
                       {create<ast::StructBlockDecoration>()});
-
-  Global("b", s, ast::StorageClass::kStorage, nullptr,
+  auto* ac = ty.access(ast::AccessControl::kReadOnly, s);
+  Global("b", ac, ast::StorageClass::kStorage, nullptr,
          ast::DecorationList{
              create<ast::BindingDecoration>(1),
              create<ast::GroupDecoration>(2),
diff --git a/test/BUILD.gn b/test/BUILD.gn
index ccf6d9f..ba99f30 100644
--- a/test/BUILD.gn
+++ b/test/BUILD.gn
@@ -182,6 +182,7 @@
     "../src/resolver/resolver_test.cc",
     "../src/resolver/resolver_test_helper.cc",
     "../src/resolver/resolver_test_helper.h",
+    "../src/resolver/storage_class_validation_test.cc",
     "../src/resolver/struct_layout_test.cc",
     "../src/resolver/struct_pipeline_stage_use_test.cc",
     "../src/resolver/struct_storage_class_use_test.cc",