resolver: Validate storage buffers have the [[block]] decoration

Fixed: tint:94
Change-Id: I1d3e512c030ec16031b8c8fcfbde0cd1db5d1ea4
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/48380
Reviewed-by: Ryan Harrison <rharrison@chromium.org>
Reviewed-by: James Price <jrprice@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
diff --git a/src/inspector/inspector_test.cc b/src/inspector/inspector_test.cc
index 2155563..826c3b6 100644
--- a/src/inspector/inspector_test.cc
+++ b/src/inspector/inspector_test.cc
@@ -241,17 +241,6 @@
     return struct_type;
   }
 
-  /// Returns true if the struct with `member_types` requires a block decoration
-  /// @param member_types a vector of member types
-  /// @returns true if block decoration is required
-  bool StructRequiresBlockDecoration(
-      std::vector<type::Type*> member_types) const {
-    // Structure needs a [[block]] attribute if the last member is a
-    // dynamically-sized array.
-    return member_types.back()->Is<type::Array>(
-        [](auto&& a) { return a->IsRuntimeArray(); });
-  }
-
   /// Generates types appropriate for using in a storage buffer
   /// @param name name for the type
   /// @param member_types a vector of member types
@@ -261,8 +250,7 @@
   std::tuple<type::Struct*, type::AccessControl*> MakeStorageBufferTypes(
       const std::string& name,
       std::vector<type::Type*> member_types) {
-    bool is_block = StructRequiresBlockDecoration(member_types);
-    auto* struct_type = MakeStructType(name, member_types, is_block);
+    auto* struct_type = MakeStructType(name, member_types, true);
     auto* access_type = create<type::AccessControl>(
         ast::AccessControl::kReadWrite, struct_type);
     return {struct_type, std::move(access_type)};
@@ -277,8 +265,7 @@
   std::tuple<type::Struct*, type::AccessControl*>
   MakeReadOnlyStorageBufferTypes(const std::string& name,
                                  std::vector<type::Type*> member_types) {
-    bool is_block = StructRequiresBlockDecoration(member_types);
-    auto* struct_type = MakeStructType(name, member_types, is_block);
+    auto* struct_type = MakeStructType(name, member_types, true);
     auto* access_type =
         create<type::AccessControl>(ast::AccessControl::kReadOnly, struct_type);
     return {struct_type, std::move(access_type)};
diff --git a/src/resolver/host_shareable_validation_test.cc b/src/resolver/host_shareable_validation_test.cc
index 9da185c..85b67cd 100644
--- a/src/resolver/host_shareable_validation_test.cc
+++ b/src/resolver/host_shareable_validation_test.cc
@@ -15,6 +15,7 @@
 #include "src/resolver/resolver.h"
 
 #include "gmock/gmock.h"
+#include "src/ast/struct_block_decoration.h"
 #include "src/resolver/resolver_test_helper.h"
 #include "src/sem/struct.h"
 #include "src/type/access_control_type.h"
@@ -26,7 +27,8 @@
 using ResolverHostShareableValidationTest = ResolverTest;
 
 TEST_F(ResolverHostShareableValidationTest, BoolMember) {
-  auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.bool_())});
+  auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.bool_())},
+                      {create<ast::StructBlockDecoration>()});
   auto* a = ty.access(ast::AccessControl::kReadOnly, s);
   Global(Source{{56, 78}}, "g", a, ast::StorageClass::kStorage);
 
@@ -40,7 +42,8 @@
 }
 
 TEST_F(ResolverHostShareableValidationTest, BoolVectorMember) {
-  auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.vec3<bool>())});
+  auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.vec3<bool>())},
+                      {create<ast::StructBlockDecoration>()});
   auto* a = ty.access(ast::AccessControl::kReadOnly, s);
   Global(Source{{56, 78}}, "g", a, ast::StorageClass::kStorage);
 
@@ -55,7 +58,8 @@
 
 TEST_F(ResolverHostShareableValidationTest, Aliases) {
   auto* a1 = ty.alias("a1", ty.bool_());
-  auto* s = Structure("S", {Member(Source{{12, 34}}, "x", a1)});
+  auto* s = Structure("S", {Member(Source{{12, 34}}, "x", a1)},
+                      {create<ast::StructBlockDecoration>()});
   auto* ac = ty.access(ast::AccessControl::kReadOnly, s);
   auto* a2 = ty.alias("a2", ac);
   Global(Source{{56, 78}}, "g", a2, ast::StorageClass::kStorage);
@@ -74,7 +78,8 @@
   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)});
+  auto* s = Structure("S", {Member(Source{{7, 8}}, "m", i3)},
+                      {create<ast::StructBlockDecoration>()});
   auto* a = ty.access(ast::AccessControl::kReadOnly, s);
   Global(Source{{9, 10}}, "g", a, ast::StorageClass::kStorage);
 
@@ -109,7 +114,8 @@
                           Member(Source{{6, 1}}, "z3", ty.alias("a2", i2)),
                       });
 
-  auto* s = Structure("S", {Member(Source{{7, 8}}, "m", i3)});
+  auto* s = Structure("S", {Member(Source{{7, 8}}, "m", i3)},
+                      {create<ast::StructBlockDecoration>()});
   auto* a = ty.access(ast::AccessControl::kReadOnly, s);
   Global(Source{{9, 10}}, "g", a, ast::StorageClass::kStorage);
 
diff --git a/src/resolver/resolver.cc b/src/resolver/resolver.cc
index 556b555..783e997 100644
--- a/src/resolver/resolver.cc
+++ b/src/resolver/resolver.cc
@@ -284,27 +284,6 @@
     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()) {
     Mark(deco);
     if (!(deco->Is<ast::BindingDecoration>() ||
@@ -325,6 +304,10 @@
     }
   }
 
+  if (!ValidateGlobalVariable(info)) {
+    return false;
+  }
+
   if (!ApplyStorageClassUsageToType(var->declared_storage_class(), info->type,
                                     var->source())) {
     diagnostics_.add_note("while instantiating variable " +
@@ -336,6 +319,43 @@
   return true;
 }
 
+bool Resolver::ValidateGlobalVariable(const VariableInfo* info) {
+  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",
+          info->declaration->source());
+      return false;
+    }
+
+    if (!str->IsBlockDecorated()) {
+      diagnostics_.add_error(
+          "structure used as a storage buffer must be declared with the "
+          "[[block]] decoration",
+          str->impl()->source());
+      if (info->declaration->source().range.begin.line) {
+        diagnostics_.add_note("structure used as storage buffer here",
+                              info->declaration->source());
+      }
+      return false;
+    }
+  }
+
+  return true;
+}
+
 bool Resolver::ValidateVariable(const ast::Variable* var) {
   auto* type = variable_to_info_[var]->type;
   if (auto* r = type->UnwrapAll()->As<type::Array>()) {
diff --git a/src/resolver/resolver.h b/src/resolver/resolver.h
index 7bc2940..321d0aa 100644
--- a/src/resolver/resolver.h
+++ b/src/resolver/resolver.h
@@ -240,6 +240,7 @@
   bool ValidateBinary(ast::BinaryExpression* expr);
   bool ValidateEntryPoint(const ast::Function* func);
   bool ValidateFunction(const ast::Function* func);
+  bool ValidateGlobalVariable(const VariableInfo* var);
   bool ValidateMatrixConstructor(const type::Matrix* matrix_type,
                                  const ast::ExpressionList& values);
   bool ValidateParameter(const ast::Variable* param);
diff --git a/src/resolver/resolver_test.cc b/src/resolver/resolver_test.cc
index d9f8ac7..ffd7bb9 100644
--- a/src/resolver/resolver_test.cc
+++ b/src/resolver/resolver_test.cc
@@ -28,6 +28,7 @@
 #include "src/ast/loop_statement.h"
 #include "src/ast/return_statement.h"
 #include "src/ast/stage_decoration.h"
+#include "src/ast/struct_block_decoration.h"
 #include "src/ast/switch_statement.h"
 #include "src/ast/unary_op_expression.h"
 #include "src/ast/variable_decl_statement.h"
@@ -764,7 +765,8 @@
 }
 
 TEST_F(ResolverTest, Function_RegisterInputOutputVariables) {
-  auto* s = Structure("S", {Member("m", ty.u32())});
+  auto* s = Structure("S", {Member("m", ty.u32())},
+                      {create<ast::StructBlockDecoration>()});
   auto* a = ty.access(ast::AccessControl::kReadOnly, s);
 
   auto* in_var = Global("in_var", ty.f32(), ast::StorageClass::kInput);
@@ -799,7 +801,8 @@
 }
 
 TEST_F(ResolverTest, Function_RegisterInputOutputVariables_SubFunction) {
-  auto* s = Structure("S", {Member("m", ty.u32())});
+  auto* s = Structure("S", {Member("m", ty.u32())},
+                      {create<ast::StructBlockDecoration>()});
   auto* a = ty.access(ast::AccessControl::kReadOnly, s);
 
   auto* in_var = Global("in_var", ty.f32(), ast::StorageClass::kInput);
diff --git a/src/resolver/storage_class_validation_test.cc b/src/resolver/storage_class_validation_test.cc
index 5f9324e..5dfdd18 100644
--- a/src/resolver/storage_class_validation_test.cc
+++ b/src/resolver/storage_class_validation_test.cc
@@ -15,6 +15,7 @@
 #include "src/resolver/resolver.h"
 
 #include "gmock/gmock.h"
+#include "src/ast/struct_block_decoration.h"
 #include "src/resolver/resolver_test_helper.h"
 #include "src/sem/struct.h"
 #include "src/type/access_control_type.h"
@@ -34,7 +35,7 @@
             "12:34 error v-0022: global variables must have a storage class");
 }
 
-TEST_F(ResolverStorageClassValidationTest, Bool) {
+TEST_F(ResolverStorageClassValidationTest, StorageBufferBool) {
   // var<storage> g : bool;
   Global(Source{{56, 78}}, "g", ty.bool_(), ast::StorageClass::kStorage);
 
@@ -45,7 +46,7 @@
       R"(56:78 error: variables declared in the <storage> storage class must be of an [[access]] qualified structure type)");
 }
 
-TEST_F(ResolverStorageClassValidationTest, Pointer) {
+TEST_F(ResolverStorageClassValidationTest, StorageBufferPointer) {
   // var<storage> g : ptr<i32, input>;
   Global(Source{{56, 78}}, "g", ty.pointer<i32>(ast::StorageClass::kInput),
          ast::StorageClass::kStorage);
@@ -57,7 +58,7 @@
       R"(56:78 error: variables declared in the <storage> storage class must be of an [[access]] qualified structure type)");
 }
 
-TEST_F(ResolverStorageClassValidationTest, Array) {
+TEST_F(ResolverStorageClassValidationTest, StorageBufferArray) {
   // var<storage> g : [[access(read)]] array<S, 3>;
   auto* s = Structure("S", {Member("a", ty.f32())});
   auto* a = ty.array(s, 3);
@@ -71,7 +72,7 @@
       R"(56:78 error: variables declared in the <storage> storage class must be of an [[access]] qualified structure type)");
 }
 
-TEST_F(ResolverStorageClassValidationTest, BoolAlias) {
+TEST_F(ResolverStorageClassValidationTest, StorageBufferBoolAlias) {
   // type a = bool;
   // var<storage> g : [[access(read)]] a;
   auto* a = ty.alias("a", ty.bool_());
@@ -84,7 +85,7 @@
       R"(56:78 error: variables declared in the <storage> storage class must be of an [[access]] qualified structure type)");
 }
 
-TEST_F(ResolverStorageClassValidationTest, NoAccessControl) {
+TEST_F(ResolverStorageClassValidationTest, StorageBufferNoAccessControl) {
   // var<storage> g : S;
   auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.i32())});
   Global(Source{{56, 78}}, "g", s, ast::StorageClass::kStorage);
@@ -96,20 +97,39 @@
       R"(56:78 error: variables declared in the <storage> storage class must be of an [[access]] qualified structure type)");
 }
 
-TEST_F(ResolverStorageClassValidationTest, NoError_Basic) {
+TEST_F(ResolverStorageClassValidationTest, StorageBufferNoBlockDecoration) {
+  // struct S { x : i32 };
   // var<storage> g : [[access(read)]] S;
-  auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.i32())});
+  auto* s = Structure(Source{{12, 34}}, "S", {Member("x", ty.i32())});
+  auto* a = ty.access(ast::AccessControl::kReadOnly, s);
+  Global(Source{{56, 78}}, "g", a, ast::StorageClass::kStorage);
+
+  ASSERT_FALSE(r()->Resolve());
+
+  EXPECT_EQ(
+      r()->error(),
+      R"(12:34 error: structure used as a storage buffer must be declared with the [[block]] decoration
+56:78 note: structure used as storage buffer here)");
+}
+
+TEST_F(ResolverStorageClassValidationTest, StorageBufferNoError_Basic) {
+  // [[block]] struct S { x : i32 };
+  // var<storage> g : [[access(read)]] S;
+  auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.i32())},
+                      {create<ast::StructBlockDecoration>()});
   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) {
+TEST_F(ResolverStorageClassValidationTest, StorageBufferNoError_Aliases) {
+  // [[block]] struct S { x : i32 };
   // type a1 = S;
   // type a2 = [[access(read)]] a1;
   // var<storage> g : a2;
-  auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.i32())});
+  auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.i32())},
+                      {create<ast::StructBlockDecoration>()});
   auto* a1 = ty.alias("a1", s);
   auto* ac = ty.access(ast::AccessControl::kReadOnly, a1);
   auto* a2 = ty.alias("a2", ac);
diff --git a/src/resolver/struct_storage_class_use_test.cc b/src/resolver/struct_storage_class_use_test.cc
index 0521200..4751992 100644
--- a/src/resolver/struct_storage_class_use_test.cc
+++ b/src/resolver/struct_storage_class_use_test.cc
@@ -15,6 +15,7 @@
 #include "src/resolver/resolver.h"
 
 #include "gmock/gmock.h"
+#include "src/ast/struct_block_decoration.h"
 #include "src/resolver/resolver_test_helper.h"
 #include "src/sem/struct.h"
 
@@ -167,7 +168,8 @@
 }
 
 TEST_F(ResolverStorageClassUseTest, StructMultipleStorageClassUses) {
-  auto* s = Structure("S", {Member("a", ty.f32())});
+  auto* s = Structure("S", {Member("a", ty.f32())},
+                      {create<ast::StructBlockDecoration>()});
   auto* ac = ty.access(ast::AccessControl::kReadOnly, s);
   Global("x", s, ast::StorageClass::kUniform);
   Global("y", ac, ast::StorageClass::kStorage);
diff --git a/src/writer/hlsl/generator_impl_function_test.cc b/src/writer/hlsl/generator_impl_function_test.cc
index c3fa8c4..923ee9b 100644
--- a/src/writer/hlsl/generator_impl_function_test.cc
+++ b/src/writer/hlsl/generator_impl_function_test.cc
@@ -388,10 +388,12 @@
 
 TEST_F(HlslGeneratorImplTest_Function,
        Emit_Decoration_EntryPoint_With_RW_StorageBuffer_Read) {
-  auto* s = Structure("Data", {
-                                  Member("a", ty.i32()),
-                                  Member("b", ty.f32()),
-                              });
+  auto* s = Structure("Data",
+                      {
+                          Member("a", ty.i32()),
+                          Member("b", ty.f32()),
+                      },
+                      {create<ast::StructBlockDecoration>()});
 
   type::AccessControl ac(ast::AccessControl::kReadWrite, s);
 
@@ -432,10 +434,12 @@
 
 TEST_F(HlslGeneratorImplTest_Function,
        Emit_Decoration_EntryPoint_With_RO_StorageBuffer_Read) {
-  auto* s = Structure("Data", {
-                                  Member("a", ty.i32()),
-                                  Member("b", ty.f32()),
-                              });
+  auto* s = Structure("Data",
+                      {
+                          Member("a", ty.i32()),
+                          Member("b", ty.f32()),
+                      },
+                      {create<ast::StructBlockDecoration>()});
 
   type::AccessControl ac(ast::AccessControl::kReadOnly, s);
 
@@ -476,10 +480,12 @@
 
 TEST_F(HlslGeneratorImplTest_Function,
        Emit_Decoration_EntryPoint_With_WO_StorageBuffer_Store) {
-  auto* s = Structure("Data", {
-                                  Member("a", ty.i32()),
-                                  Member("b", ty.f32()),
-                              });
+  auto* s = Structure("Data",
+                      {
+                          Member("a", ty.i32()),
+                          Member("b", ty.f32()),
+                      },
+                      {create<ast::StructBlockDecoration>()});
 
   type::AccessControl ac(ast::AccessControl::kWriteOnly, s);
 
@@ -518,10 +524,12 @@
 
 TEST_F(HlslGeneratorImplTest_Function,
        Emit_Decoration_EntryPoint_With_StorageBuffer_Store) {
-  auto* s = Structure("Data", {
-                                  Member("a", ty.i32()),
-                                  Member("b", ty.f32()),
-                              });
+  auto* s = Structure("Data",
+                      {
+                          Member("a", ty.i32()),
+                          Member("b", ty.f32()),
+                      },
+                      {create<ast::StructBlockDecoration>()});
 
   type::AccessControl ac(ast::AccessControl::kReadWrite, s);
 
@@ -777,7 +785,8 @@
 
 TEST_F(HlslGeneratorImplTest_Function,
        Emit_Decoration_Called_By_EntryPoint_With_StorageBuffer) {
-  auto* s = Structure("S", {Member("x", ty.f32())});
+  auto* s = Structure("S", {Member("x", ty.f32())},
+                      {create<ast::StructBlockDecoration>()});
   auto* ac = ty.access(ast::AccessControl::kReadWrite, s);
   Global("coord", ac, ast::StorageClass::kStorage, nullptr,
          ast::DecorationList{
diff --git a/src/writer/hlsl/generator_impl_type_test.cc b/src/writer/hlsl/generator_impl_type_test.cc
index e03a7b49..d9bb495 100644
--- a/src/writer/hlsl/generator_impl_type_test.cc
+++ b/src/writer/hlsl/generator_impl_type_test.cc
@@ -173,10 +173,12 @@
 }
 
 TEST_F(HlslGeneratorImplTest_Type, EmitType_StructDecl_OmittedIfStorageBuffer) {
-  auto* s = Structure("S", {
-                               Member("a", ty.i32()),
-                               Member("b", ty.f32()),
-                           });
+  auto* s = Structure("S",
+                      {
+                          Member("a", ty.i32()),
+                          Member("b", ty.f32()),
+                      },
+                      {create<ast::StructBlockDecoration>()});
   Global("g", ty.access(ast::AccessControl::kReadWrite, s),
          ast::StorageClass::kStorage);
 
diff --git a/src/writer/msl/generator_impl_function_test.cc b/src/writer/msl/generator_impl_function_test.cc
index e815401..8cf5908 100644
--- a/src/writer/msl/generator_impl_function_test.cc
+++ b/src/writer/msl/generator_impl_function_test.cc
@@ -307,10 +307,12 @@
 
 TEST_F(MslGeneratorImplTest,
        Emit_FunctionDecoration_EntryPoint_With_RW_StorageBuffer) {
-  auto* s = Structure("Data", {
-                                  Member("a", ty.i32()),
-                                  Member("b", ty.f32()),
-                              });
+  auto* s = Structure("Data",
+                      {
+                          Member("a", ty.i32()),
+                          Member("b", ty.f32()),
+                      },
+                      {create<ast::StructBlockDecoration>()});
 
   type::AccessControl ac(ast::AccessControl::kReadWrite, s);
 
@@ -351,10 +353,12 @@
 
 TEST_F(MslGeneratorImplTest,
        Emit_FunctionDecoration_EntryPoint_With_RO_StorageBuffer) {
-  auto* s = Structure("Data", {
-                                  Member("a", ty.i32()),
-                                  Member("b", ty.f32()),
-                              });
+  auto* s = Structure("Data",
+                      {
+                          Member("a", ty.i32()),
+                          Member("b", ty.f32()),
+                      },
+                      {create<ast::StructBlockDecoration>()});
 
   type::AccessControl ac(ast::AccessControl::kReadOnly, s);
 
@@ -609,10 +613,12 @@
 
 TEST_F(MslGeneratorImplTest,
        Emit_FunctionDecoration_Called_By_EntryPoint_With_RW_StorageBuffer) {
-  auto* s = Structure("Data", {
-                                  Member("a", ty.i32()),
-                                  Member("b", ty.f32()),
-                              });
+  auto* s = Structure("Data",
+                      {
+                          Member("a", ty.i32()),
+                          Member("b", ty.f32()),
+                      },
+                      {create<ast::StructBlockDecoration>()});
 
   type::AccessControl ac(ast::AccessControl::kReadWrite, s);
 
@@ -665,10 +671,12 @@
 
 TEST_F(MslGeneratorImplTest,
        Emit_FunctionDecoration_Called_By_EntryPoint_With_RO_StorageBuffer) {
-  auto* s = Structure("Data", {
-                                  Member("a", ty.i32()),
-                                  Member("b", ty.f32()),
-                              });
+  auto* s = Structure("Data",
+                      {
+                          Member("a", ty.i32()),
+                          Member("b", ty.f32()),
+                      },
+                      {create<ast::StructBlockDecoration>()});
 
   type::AccessControl ac(ast::AccessControl::kReadOnly, s);
 
diff --git a/src/writer/msl/generator_impl_type_test.cc b/src/writer/msl/generator_impl_type_test.cc
index 69d36c9..fbad91a 100644
--- a/src/writer/msl/generator_impl_type_test.cc
+++ b/src/writer/msl/generator_impl_type_test.cc
@@ -201,35 +201,37 @@
 }
 
 TEST_F(MslGeneratorImplTest, EmitType_Struct_Layout_NonComposites) {
-  auto* s = Structure(
-      "S", {
-               Member("a", ty.i32(), {MemberSize(32)}),
-               Member("b", ty.f32(), {MemberAlign(128), MemberSize(128)}),
-               Member("c", ty.vec2<f32>()),
-               Member("d", ty.u32()),
-               Member("e", ty.vec3<f32>()),
-               Member("f", ty.u32()),
-               Member("g", ty.vec4<f32>()),
-               Member("h", ty.u32()),
-               Member("i", ty.mat2x2<f32>()),
-               Member("j", ty.u32()),
-               Member("k", ty.mat2x3<f32>()),
-               Member("l", ty.u32()),
-               Member("m", ty.mat2x4<f32>()),
-               Member("n", ty.u32()),
-               Member("o", ty.mat3x2<f32>()),
-               Member("p", ty.u32()),
-               Member("q", ty.mat3x3<f32>()),
-               Member("r", ty.u32()),
-               Member("s", ty.mat3x4<f32>()),
-               Member("t", ty.u32()),
-               Member("u", ty.mat4x2<f32>()),
-               Member("v", ty.u32()),
-               Member("w", ty.mat4x3<f32>()),
-               Member("x", ty.u32()),
-               Member("y", ty.mat4x4<f32>()),
-               Member("z", ty.f32()),
-           });
+  auto* s =
+      Structure("S",
+                {
+                    Member("a", ty.i32(), {MemberSize(32)}),
+                    Member("b", ty.f32(), {MemberAlign(128), MemberSize(128)}),
+                    Member("c", ty.vec2<f32>()),
+                    Member("d", ty.u32()),
+                    Member("e", ty.vec3<f32>()),
+                    Member("f", ty.u32()),
+                    Member("g", ty.vec4<f32>()),
+                    Member("h", ty.u32()),
+                    Member("i", ty.mat2x2<f32>()),
+                    Member("j", ty.u32()),
+                    Member("k", ty.mat2x3<f32>()),
+                    Member("l", ty.u32()),
+                    Member("m", ty.mat2x4<f32>()),
+                    Member("n", ty.u32()),
+                    Member("o", ty.mat3x2<f32>()),
+                    Member("p", ty.u32()),
+                    Member("q", ty.mat3x3<f32>()),
+                    Member("r", ty.u32()),
+                    Member("s", ty.mat3x4<f32>()),
+                    Member("t", ty.u32()),
+                    Member("u", ty.mat4x2<f32>()),
+                    Member("v", ty.u32()),
+                    Member("w", ty.mat4x3<f32>()),
+                    Member("x", ty.u32()),
+                    Member("y", ty.mat4x4<f32>()),
+                    Member("z", ty.f32()),
+                },
+                {create<ast::StructBlockDecoration>()});
 
   Global("G", ty.access(ast::AccessControl::kReadOnly, s),
          ast::StorageClass::kStorage);
@@ -326,13 +328,15 @@
                                Member("b", ty.f32()),
                            });
 
-  auto* s = Structure("S", {
-                               Member("a", ty.i32()),
-                               Member("b", inner_x),
-                               Member("c", ty.f32()),
-                               Member("d", inner_y),
-                               Member("e", ty.f32()),
-                           });
+  auto* s = Structure("S",
+                      {
+                          Member("a", ty.i32()),
+                          Member("b", inner_x),
+                          Member("c", ty.f32()),
+                          Member("d", inner_y),
+                          Member("e", ty.f32()),
+                      },
+                      {create<ast::StructBlockDecoration>()});
 
   Global("G", ty.access(ast::AccessControl::kReadOnly, s),
          ast::StorageClass::kStorage);
@@ -523,7 +527,8 @@
           Member("tint_pad_28", ty.u32()),
           Member("tint_pad_4", ty.mat4x4<f32>()),
           Member("tint_pad_21", ty.f32()),
-      });
+      },
+      {create<ast::StructBlockDecoration>()});
 
   Global("G", ty.access(ast::AccessControl::kReadOnly, s),
          ast::StorageClass::kStorage);
diff --git a/src/writer/spirv/builder_global_variable_test.cc b/src/writer/spirv/builder_global_variable_test.cc
index eb0ed78..6b541ef 100644
--- a/src/writer/spirv/builder_global_variable_test.cc
+++ b/src/writer/spirv/builder_global_variable_test.cc
@@ -14,6 +14,7 @@
 
 #include "src/ast/constant_id_decoration.h"
 #include "src/ast/stage_decoration.h"
+#include "src/ast/struct_block_decoration.h"
 #include "src/writer/spirv/spv_dump.h"
 #include "src/writer/spirv/test_helper.h"
 
@@ -387,10 +388,12 @@
   // };
   // var b : [[access(read)]] A
 
-  auto* A = Structure("A", {
-                               Member("a", ty.i32()),
-                               Member("b", ty.i32()),
-                           });
+  auto* A = Structure("A",
+                      {
+                          Member("a", ty.i32()),
+                          Member("b", ty.i32()),
+                      },
+                      {create<ast::StructBlockDecoration>()});
   auto* ac = create<type::AccessControl>(ast::AccessControl::kReadOnly, A);
 
   auto* var = Global("b", ac, ast::StorageClass::kStorage);
@@ -399,7 +402,8 @@
 
   EXPECT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
 
-  EXPECT_EQ(DumpInstructions(b.annots()), R"(OpMemberDecorate %3 0 Offset 0
+  EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %3 Block
+OpMemberDecorate %3 0 Offset 0
 OpMemberDecorate %3 0 NonWritable
 OpMemberDecorate %3 1 Offset 4
 OpMemberDecorate %3 1 NonWritable
@@ -423,7 +427,8 @@
   // type B = A;
   // var b : [[access(read)]] B
 
-  auto* A = Structure("A", {Member("a", ty.i32())});
+  auto* A = Structure("A", {Member("a", ty.i32())},
+                      {create<ast::StructBlockDecoration>()});
   auto* B = ty.alias("B", A);
   auto* ac = create<type::AccessControl>(ast::AccessControl::kReadOnly, B);
   auto* var = Global("b", ac, ast::StorageClass::kStorage);
@@ -432,7 +437,8 @@
 
   EXPECT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
 
-  EXPECT_EQ(DumpInstructions(b.annots()), R"(OpMemberDecorate %3 0 Offset 0
+  EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %3 Block
+OpMemberDecorate %3 0 Offset 0
 OpMemberDecorate %3 0 NonWritable
 )");
   EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %3 "A"
@@ -453,7 +459,8 @@
   // type B = [[access(read)]] A;
   // var b : B
 
-  auto* A = Structure("A", {Member("a", ty.i32())});
+  auto* A = Structure("A", {Member("a", ty.i32())},
+                      {create<ast::StructBlockDecoration>()});
   auto* ac = create<type::AccessControl>(ast::AccessControl::kReadOnly, A);
   auto* B = ty.alias("B", ac);
   auto* var = Global("b", B, ast::StorageClass::kStorage);
@@ -462,7 +469,8 @@
 
   EXPECT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
 
-  EXPECT_EQ(DumpInstructions(b.annots()), R"(OpMemberDecorate %3 0 Offset 0
+  EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %3 Block
+OpMemberDecorate %3 0 Offset 0
 OpMemberDecorate %3 0 NonWritable
 )");
   EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %3 "A"
@@ -483,7 +491,8 @@
   // var b : [[access(read)]] A
   // var c : [[access(read_write)]] A
 
-  auto* A = Structure("A", {Member("a", ty.i32())});
+  auto* A = Structure("A", {Member("a", ty.i32())},
+                      {create<ast::StructBlockDecoration>()});
   type::AccessControl read{ast::AccessControl::kReadOnly, A};
   type::AccessControl rw{ast::AccessControl::kReadWrite, A};
 
@@ -496,8 +505,10 @@
   EXPECT_TRUE(b.GenerateGlobalVariable(var_c)) << b.error();
 
   EXPECT_EQ(DumpInstructions(b.annots()),
-            R"(OpMemberDecorate %3 0 Offset 0
+            R"(OpDecorate %3 Block
+OpMemberDecorate %3 0 Offset 0
 OpMemberDecorate %3 0 NonWritable
+OpDecorate %7 Block
 OpMemberDecorate %7 0 Offset 0
 )");
   EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %3 "A"