Implement Default Struct Layout

Implements https://github.com/gpuweb/gpuweb/pull/1447

SPIR-V Reader is still TODO, but continues to function as the offset
decoration is still supported.

Bug: tint:626
Bug: tint:629
Change-Id: Id574eb3a5c6729559382812de37b23f0c68fd406
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/43640
Commit-Queue: Ben Clayton <bclayton@chromium.org>
Reviewed-by: David Neto <dneto@google.com>
diff --git a/samples/main.cc b/samples/main.cc
index 6a5d80e..eab18f4 100644
--- a/samples/main.cc
+++ b/samples/main.cc
@@ -750,8 +750,6 @@
                   << "]:" << std::endl;
         std::cout << "\t\t resource_type = "
                   << ResourceTypeToString(binding.resource_type) << std::endl;
-        std::cout << "\t\t min_buffer_binding_size = "
-                  << binding.min_buffer_binding_size << std::endl;
         std::cout << "\t\t dim = " << TextureDimensionToString(binding.dim)
                   << std::endl;
         std::cout << "\t\t sampled_kind = "
diff --git a/src/BUILD.gn b/src/BUILD.gn
index b5398bd..baaf5ae 100644
--- a/src/BUILD.gn
+++ b/src/BUILD.gn
@@ -317,8 +317,12 @@
     "ast/struct_block_decoration.h",
     "ast/struct_member.cc",
     "ast/struct_member.h",
+    "ast/struct_member_align_decoration.cc",
+    "ast/struct_member_align_decoration.h",
     "ast/struct_member_offset_decoration.cc",
     "ast/struct_member_offset_decoration.h",
+    "ast/struct_member_size_decoration.cc",
+    "ast/struct_member_size_decoration.h",
     "ast/switch_statement.cc",
     "ast/switch_statement.h",
     "ast/type_constructor_expression.cc",
@@ -367,11 +371,14 @@
     "resolver/resolver.cc",
     "resolver/resolver.h",
     "scope_stack.h",
+    "semantic/array.h",
     "semantic/call.h",
+    "semantic/call_target.h",
     "semantic/expression.h",
     "semantic/info.h",
     "semantic/intrinsic.h",
     "semantic/node.h",
+    "semantic/sem_array.cc",
     "semantic/sem_call.cc",
     "semantic/sem_call_target.cc",
     "semantic/sem_expression.cc",
@@ -381,6 +388,7 @@
     "semantic/sem_member_accessor_expression.cc",
     "semantic/sem_node.cc",
     "semantic/sem_statement.cc",
+    "semantic/sem_struct.cc",
     "semantic/sem_variable.cc",
     "semantic/type_mappings.h",
     "source.cc",
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 0ba3a57..006c202 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -132,8 +132,12 @@
   ast/struct_block_decoration.h
   ast/struct_member.cc
   ast/struct_member.h
+  ast/struct_member_align_decoration.cc
+  ast/struct_member_align_decoration.h
   ast/struct_member_offset_decoration.cc
   ast/struct_member_offset_decoration.h
+  ast/struct_member_size_decoration.cc
+  ast/struct_member_size_decoration.h
   ast/switch_statement.cc
   ast/switch_statement.h
   ast/type_constructor_expression.cc
@@ -182,11 +186,14 @@
   resolver/resolver.cc
   resolver/resolver.h
   scope_stack.h
+  semantic/array.h
   semantic/call.h
+  semantic/call_target.h
   semantic/expression.h
   semantic/info.h
   semantic/intrinsic.h
   semantic/node.h
+  semantic/sem_array.cc
   semantic/sem_call.cc
   semantic/sem_call_target.cc
   semantic/sem_expression.cc
@@ -196,6 +203,7 @@
   semantic/sem_intrinsic.cc
   semantic/sem_node.cc
   semantic/sem_statement.cc
+  semantic/sem_struct.cc
   semantic/sem_variable.cc
   semantic/type_mappings.h
   source.cc
@@ -436,7 +444,9 @@
     ast/sint_literal_test.cc
     ast/stage_decoration_test.cc
     ast/stride_decoration_test.cc
+    ast/struct_member_align_decoration_test.cc
     ast/struct_member_offset_decoration_test.cc
+    ast/struct_member_size_decoration_test.cc
     ast/struct_member_test.cc
     ast/struct_test.cc
     ast/switch_statement_test.cc
@@ -458,9 +468,11 @@
     intrinsic_table_test.cc
     program_test.cc
     resolver/intrinsic_test.cc
+    resolver/is_storeable_test.cc
     resolver/resolver_test_helper.cc
     resolver/resolver_test_helper.h
     resolver/resolver_test.cc
+    resolver/struct_layout_test.cc
     resolver/validation_test.cc
     scope_stack_test.cc
     semantic/sem_intrinsic_test.cc
diff --git a/src/ast/module_clone_test.cc b/src/ast/module_clone_test.cc
index 39095fd..4e3fea6 100644
--- a/src/ast/module_clone_test.cc
+++ b/src/ast/module_clone_test.cc
@@ -28,9 +28,8 @@
   // See also fuzzers/tint_ast_clone_fuzzer.cc for further coverage of cloning.
   Source::File file("test.wgsl", R"([[block]]
 struct S {
-  [[offset(0)]]
+  [[size(4)]]
   m0 : u32;
-  [[offset(4)]]
   m1 : array<u32>;
 };
 
@@ -38,7 +37,7 @@
 const c1 : bool = true;
 
 type t0 = [[stride(16)]] array<vec4<f32>>;
-type t1 = [[stride(32)]] array<vec4<f32>>;
+type t1 = array<vec4<f32>>;
 
 var<uniform> g0 : u32 = 20u;
 var<out> g1 : f32 = 123.0;
diff --git a/src/ast/struct_member_align_decoration.cc b/src/ast/struct_member_align_decoration.cc
new file mode 100644
index 0000000..03a353c
--- /dev/null
+++ b/src/ast/struct_member_align_decoration.cc
@@ -0,0 +1,46 @@
+// 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/ast/struct_member_align_decoration.h"
+
+#include "src/clone_context.h"
+#include "src/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::StructMemberAlignDecoration);
+
+namespace tint {
+namespace ast {
+
+StructMemberAlignDecoration::StructMemberAlignDecoration(const Source& source,
+                                                         uint32_t align)
+    : Base(source), align_(align) {}
+
+StructMemberAlignDecoration::~StructMemberAlignDecoration() = default;
+
+void StructMemberAlignDecoration::to_str(const semantic::Info&,
+                                         std::ostream& out,
+                                         size_t indent) const {
+  make_indent(out, indent);
+  out << "align " << std::to_string(align_);
+}
+
+StructMemberAlignDecoration* StructMemberAlignDecoration::Clone(
+    CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source());
+  return ctx->dst->create<StructMemberAlignDecoration>(src, align_);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/ast/struct_member_align_decoration.h b/src/ast/struct_member_align_decoration.h
new file mode 100644
index 0000000..e408e32
--- /dev/null
+++ b/src/ast/struct_member_align_decoration.h
@@ -0,0 +1,59 @@
+// 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.
+
+#ifndef SRC_AST_STRUCT_MEMBER_ALIGN_DECORATION_H_
+#define SRC_AST_STRUCT_MEMBER_ALIGN_DECORATION_H_
+
+#include <stddef.h>
+
+#include "src/ast/decoration.h"
+
+namespace tint {
+namespace ast {
+
+/// A struct member align decoration
+class StructMemberAlignDecoration
+    : public Castable<StructMemberAlignDecoration, Decoration> {
+ public:
+  /// constructor
+  /// @param source the source of this decoration
+  /// @param align the align value
+  StructMemberAlignDecoration(const Source& source, uint32_t align);
+  ~StructMemberAlignDecoration() override;
+
+  /// @returns the align value
+  uint32_t align() const { return align_; }
+
+  /// Outputs the decoration to the given stream
+  /// @param sem the semantic info for the program
+  /// @param out the stream to write to
+  /// @param indent number of spaces to indent the node when writing
+  void to_str(const semantic::Info& sem,
+              std::ostream& out,
+              size_t indent) const override;
+
+  /// Clones this node and all transitive child nodes using the `CloneContext`
+  /// `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned node
+  StructMemberAlignDecoration* Clone(CloneContext* ctx) const override;
+
+ private:
+  uint32_t const align_;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_AST_STRUCT_MEMBER_ALIGN_DECORATION_H_
diff --git a/src/ast/struct_member_align_decoration_test.cc b/src/ast/struct_member_align_decoration_test.cc
new file mode 100644
index 0000000..fc6e5d3
--- /dev/null
+++ b/src/ast/struct_member_align_decoration_test.cc
@@ -0,0 +1,37 @@
+// 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/ast/struct_member_align_decoration.h"
+
+#include "src/ast/test_helper.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using StructMemberAlignDecorationTest = TestHelper;
+
+TEST_F(StructMemberAlignDecorationTest, Creation) {
+  auto* d = create<StructMemberAlignDecoration>(2);
+  EXPECT_EQ(2u, d->align());
+}
+
+TEST_F(StructMemberAlignDecorationTest, Is) {
+  auto* d = create<StructMemberAlignDecoration>(2);
+  EXPECT_TRUE(d->Is<StructMemberAlignDecoration>());
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/ast/struct_member_offset_decoration.h b/src/ast/struct_member_offset_decoration.h
index d793e75..3ab806d 100644
--- a/src/ast/struct_member_offset_decoration.h
+++ b/src/ast/struct_member_offset_decoration.h
@@ -21,6 +21,8 @@
 namespace ast {
 
 /// A struct member offset decoration
+// [DEPRECATED] - Replaced with StructMemberAlignDecoration and
+// StructMemberSizeDecoration
 class StructMemberOffsetDecoration
     : public Castable<StructMemberOffsetDecoration, Decoration> {
  public:
diff --git a/src/ast/struct_member_size_decoration.cc b/src/ast/struct_member_size_decoration.cc
new file mode 100644
index 0000000..54e536a
--- /dev/null
+++ b/src/ast/struct_member_size_decoration.cc
@@ -0,0 +1,46 @@
+// 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/ast/struct_member_size_decoration.h"
+
+#include "src/clone_context.h"
+#include "src/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::StructMemberSizeDecoration);
+
+namespace tint {
+namespace ast {
+
+StructMemberSizeDecoration::StructMemberSizeDecoration(const Source& source,
+                                                       uint32_t size)
+    : Base(source), size_(size) {}
+
+StructMemberSizeDecoration::~StructMemberSizeDecoration() = default;
+
+void StructMemberSizeDecoration::to_str(const semantic::Info&,
+                                        std::ostream& out,
+                                        size_t indent) const {
+  make_indent(out, indent);
+  out << "size " << std::to_string(size_);
+}
+
+StructMemberSizeDecoration* StructMemberSizeDecoration::Clone(
+    CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source());
+  return ctx->dst->create<StructMemberSizeDecoration>(src, size_);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/ast/struct_member_size_decoration.h b/src/ast/struct_member_size_decoration.h
new file mode 100644
index 0000000..f99fd50
--- /dev/null
+++ b/src/ast/struct_member_size_decoration.h
@@ -0,0 +1,59 @@
+// 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.
+
+#ifndef SRC_AST_STRUCT_MEMBER_SIZE_DECORATION_H_
+#define SRC_AST_STRUCT_MEMBER_SIZE_DECORATION_H_
+
+#include <stddef.h>
+
+#include "src/ast/decoration.h"
+
+namespace tint {
+namespace ast {
+
+/// A struct member size decoration
+class StructMemberSizeDecoration
+    : public Castable<StructMemberSizeDecoration, Decoration> {
+ public:
+  /// constructor
+  /// @param source the source of this decoration
+  /// @param size the size value
+  StructMemberSizeDecoration(const Source& source, uint32_t size);
+  ~StructMemberSizeDecoration() override;
+
+  /// @returns the size value
+  uint32_t size() const { return size_; }
+
+  /// Outputs the decoration to the given stream
+  /// @param sem the semantic info for the program
+  /// @param out the stream to write to
+  /// @param indent number of spaces to indent the node when writing
+  void to_str(const semantic::Info& sem,
+              std::ostream& out,
+              size_t indent) const override;
+
+  /// Clones this node and all transitive child nodes using the `CloneContext`
+  /// `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned node
+  StructMemberSizeDecoration* Clone(CloneContext* ctx) const override;
+
+ private:
+  uint32_t const size_;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_AST_STRUCT_MEMBER_SIZE_DECORATION_H_
diff --git a/src/ast/struct_member_size_decoration_test.cc b/src/ast/struct_member_size_decoration_test.cc
new file mode 100644
index 0000000..61427b7
--- /dev/null
+++ b/src/ast/struct_member_size_decoration_test.cc
@@ -0,0 +1,37 @@
+// 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/ast/struct_member_size_decoration.h"
+
+#include "src/ast/test_helper.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using StructMemberOffsetDecorationTest = TestHelper;
+
+TEST_F(StructMemberOffsetDecorationTest, Creation) {
+  auto* d = create<StructMemberSizeDecoration>(2);
+  EXPECT_EQ(2u, d->size());
+}
+
+TEST_F(StructMemberOffsetDecorationTest, Is) {
+  auto* d = create<StructMemberSizeDecoration>(2);
+  EXPECT_TRUE(d->Is<StructMemberSizeDecoration>());
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/ast/struct_member_test.cc b/src/ast/struct_member_test.cc
index 17c4425..c760c85 100644
--- a/src/ast/struct_member_test.cc
+++ b/src/ast/struct_member_test.cc
@@ -22,11 +22,11 @@
 using StructMemberTest = TestHelper;
 
 TEST_F(StructMemberTest, Creation) {
-  auto* st = Member("a", ty.i32(), {MemberOffset(4)});
+  auto* st = Member("a", ty.i32(), {MemberSize(4)});
   EXPECT_EQ(st->symbol(), Symbol(1));
   EXPECT_EQ(st->type(), ty.i32());
   EXPECT_EQ(st->decorations().size(), 1u);
-  EXPECT_TRUE(st->decorations()[0]->Is<StructMemberOffsetDecoration>());
+  EXPECT_TRUE(st->decorations()[0]->Is<StructMemberSizeDecoration>());
   EXPECT_EQ(st->source().range.begin.line, 0u);
   EXPECT_EQ(st->source().range.begin.column, 0u);
   EXPECT_EQ(st->source().range.end.line, 0u);
@@ -68,14 +68,14 @@
   EXPECT_FATAL_FAILURE(
       {
         ProgramBuilder b;
-        b.Member("a", b.ty.i32(), {b.MemberOffset(4), nullptr});
+        b.Member("a", b.ty.i32(), {b.MemberSize(4), nullptr});
       },
       "internal compiler error");
 }
 
 TEST_F(StructMemberTest, ToStr) {
-  auto* st = Member("a", ty.i32(), {MemberOffset(4)});
-  EXPECT_EQ(str(st), "StructMember{[[ offset 4 ]] a: __i32}\n");
+  auto* st = Member("a", ty.i32(), {MemberSize(4)});
+  EXPECT_EQ(str(st), "StructMember{[[ size 4 ]] a: __i32}\n");
 }
 
 TEST_F(StructMemberTest, ToStrNoDecorations) {
diff --git a/src/inspector/inspector.cc b/src/inspector/inspector.cc
index b049940..b2e96f5 100644
--- a/src/inspector/inspector.cc
+++ b/src/inspector/inspector.cc
@@ -23,6 +23,7 @@
 #include "src/ast/sint_literal.h"
 #include "src/ast/uint_literal.h"
 #include "src/semantic/function.h"
+#include "src/semantic/struct.h"
 #include "src/semantic/variable.h"
 #include "src/type/access_control_type.h"
 #include "src/type/array_type.h"
@@ -379,12 +380,18 @@
       continue;
     }
 
+    auto* sem = program_->Sem().Get(str);
+    if (!sem) {
+      error_ = "Missing semantic information for structure " +
+               program_->Symbols().NameFor(str->symbol());
+      continue;
+    }
+
     ResourceBinding entry;
     entry.resource_type = ResourceBinding::ResourceType::kUniformBuffer;
     entry.bind_group = binding_info.group->value();
     entry.binding = binding_info.binding->value();
-    entry.min_buffer_binding_size =
-        decl->type()->MinBufferBindingSize(type::MemoryLayout::kUniformBuffer);
+    entry.size = sem->Size();
 
     result.push_back(entry);
   }
@@ -541,7 +548,15 @@
       continue;
     }
 
-    if (!decl->type()->UnwrapIfNeeded()->Is<type::Struct>()) {
+    auto* str = decl->type()->UnwrapIfNeeded()->As<type::Struct>();
+    if (!str) {
+      continue;
+    }
+
+    auto* sem = program_->Sem().Get(str);
+    if (!sem) {
+      error_ = "Missing semantic information for structure " +
+               program_->Symbols().NameFor(str->symbol());
       continue;
     }
 
@@ -551,8 +566,7 @@
                   : ResourceBinding::ResourceType::kStorageBuffer;
     entry.bind_group = binding_info.group->value();
     entry.binding = binding_info.binding->value();
-    entry.min_buffer_binding_size =
-        decl->type()->MinBufferBindingSize(type::MemoryLayout::kStorageBuffer);
+    entry.size = sem->Size();
 
     result.push_back(entry);
   }
diff --git a/src/inspector/inspector.h b/src/inspector/inspector.h
index d1c047a..ec91efb 100644
--- a/src/inspector/inspector.h
+++ b/src/inspector/inspector.h
@@ -112,8 +112,8 @@
   uint32_t bind_group;
   /// Identifier to identify this binding within the bind group
   uint32_t binding;
-  /// Minimum size required for this binding, in bytes, if defined.
-  uint64_t min_buffer_binding_size;
+  /// Size for this binding, in bytes, if defined.
+  uint64_t size;
   /// Dimensionality of this binding, if defined.
   TextureDimension dim;
   /// Kind of data being sampled, if defined.
diff --git a/src/inspector/inspector_test.cc b/src/inspector/inspector_test.cc
index 5e7b95e..a48af7a 100644
--- a/src/inspector/inspector_test.cc
+++ b/src/inspector/inspector_test.cc
@@ -190,22 +190,15 @@
 
   /// Generates a struct type
   /// @param name name for the type
-  /// @param members_info a vector of {type, offset} where each entry is the
-  ///                     type and offset of a member of the struct
+  /// @param member_types a vector of member types
   /// @param is_block whether or not to decorate as a Block
   /// @returns a struct type
-  type::Struct* MakeStructType(
-      const std::string& name,
-      std::vector<std::tuple<type::Type*, uint32_t>> members_info,
-      bool is_block) {
+  type::Struct* MakeStructType(const std::string& name,
+                               std::vector<type::Type*> member_types,
+                               bool is_block) {
     ast::StructMemberList members;
-    for (auto& member_info : members_info) {
-      type::Type* type;
-      uint32_t offset;
-      std::tie(type, offset) = member_info;
-
-      members.push_back(Member(StructMemberName(members.size(), type), type,
-                               {MemberOffset(offset)}));
+    for (auto* type : member_types) {
+      members.push_back(Member(StructMemberName(members.size(), type), type));
     }
 
     ast::DecorationList decos;
@@ -214,20 +207,21 @@
     }
 
     auto* str = create<ast::Struct>(members, decos);
-    return ty.struct_(name, str);
+    auto* str_ty = ty.struct_(name, str);
+    AST().AddConstructedType(str_ty);
+    return str_ty;
   }
 
   /// Generates types appropriate for using in an uniform buffer
   /// @param name name for the type
-  /// @param members_info a vector of {type, offset} where each entry is the
-  ///                     type and offset of a member of the struct
+  /// @param member_types a vector of member types
   /// @returns a tuple {struct type, access control type}, where the struct has
   ///          the layout for an uniform buffer, and the control type wraps the
   ///          struct.
   std::tuple<type::Struct*, type::AccessControl*> MakeUniformBufferTypes(
       const std::string& name,
-      std::vector<std::tuple<type::Type*, uint32_t>> members_info) {
-    auto* struct_type = MakeStructType(name, members_info, true);
+      std::vector<type::Type*> member_types) {
+    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)};
@@ -235,15 +229,14 @@
 
   /// Generates types appropriate for using in a storage buffer
   /// @param name name for the type
-  /// @param members_info a vector of {type, offset} where each entry is the
-  ///                     type and offset of a member of the struct
+  /// @param member_types a vector of member types
   /// @returns a tuple {struct type, access control type}, where the struct has
   ///          the layout for a storage buffer, and the control type wraps the
   ///          struct.
   std::tuple<type::Struct*, type::AccessControl*> MakeStorageBufferTypes(
       const std::string& name,
-      std::vector<std::tuple<type::Type*, uint32_t>> members_info) {
-    auto* struct_type = MakeStructType(name, members_info, false);
+      std::vector<type::Type*> member_types) {
+    auto* struct_type = MakeStructType(name, member_types, false);
     auto* access_type = create<type::AccessControl>(
         ast::AccessControl::kReadWrite, struct_type);
     return {struct_type, std::move(access_type)};
@@ -251,16 +244,14 @@
 
   /// Generates types appropriate for using in a read-only storage buffer
   /// @param name name for the type
-  /// @param members_info a vector of {type, offset} where each entry is the
-  ///                     type and offset of a member of the struct
+  /// @param member_types a vector of member types
   /// @returns a tuple {struct type, access control type}, where the struct has
   ///          the layout for a read-only storage buffer, and the control type
   ///          wraps the struct.
   std::tuple<type::Struct*, type::AccessControl*>
-  MakeReadOnlyStorageBufferTypes(
-      const std::string& name,
-      std::vector<std::tuple<type::Type*, uint32_t>> members_info) {
-    auto* struct_type = MakeStructType(name, members_info, false);
+  MakeReadOnlyStorageBufferTypes(const std::string& name,
+                                 std::vector<type::Type*> member_types) {
+    auto* struct_type = MakeStructType(name, member_types, false);
     auto* access_type =
         create<type::AccessControl>(ast::AccessControl::kReadOnly, struct_type);
     return {struct_type, std::move(access_type)};
@@ -1404,21 +1395,21 @@
   type::Struct* ub_struct_type;
   type::AccessControl* ub_control_type;
   std::tie(ub_struct_type, ub_control_type) =
-      MakeUniformBufferTypes("ub_type", {{ty.i32(), 0}});
+      MakeUniformBufferTypes("ub_type", {ty.i32()});
   AddUniformBuffer("ub_var", ub_control_type, 0, 0);
   MakeStructVariableReferenceBodyFunction("ub_func", "ub_var", {{0, ty.i32()}});
 
   type::Struct* sb_struct_type;
   type::AccessControl* sb_control_type;
   std::tie(sb_struct_type, sb_control_type) =
-      MakeStorageBufferTypes("sb_type", {{ty.i32(), 0}});
+      MakeStorageBufferTypes("sb_type", {ty.i32()});
   AddStorageBuffer("sb_var", sb_control_type, 1, 0);
   MakeStructVariableReferenceBodyFunction("sb_func", "sb_var", {{0, ty.i32()}});
 
   type::Struct* ro_struct_type;
   type::AccessControl* ro_control_type;
   std::tie(ro_struct_type, ro_control_type) =
-      MakeReadOnlyStorageBufferTypes("ro_type", {{ty.i32(), 0}});
+      MakeReadOnlyStorageBufferTypes("ro_type", {ty.i32()});
   AddStorageBuffer("ro_var", ro_control_type, 1, 1);
   MakeStructVariableReferenceBodyFunction("ro_func", "ro_var", {{0, ty.i32()}});
 
@@ -1499,7 +1490,7 @@
   type::Struct* foo_struct_type;
   type::AccessControl* foo_control_type;
   std::tie(foo_struct_type, foo_control_type) =
-      MakeUniformBufferTypes("foo_type", {{ty.i32(), 0}});
+      MakeUniformBufferTypes("foo_type", {ty.i32()});
   AddUniformBuffer("foo_ub", foo_control_type, 0, 0);
 
   MakeStructVariableReferenceBodyFunction("ub_func", "foo_ub", {{0, ty.i32()}});
@@ -1520,8 +1511,7 @@
 TEST_F(InspectorGetUniformBufferResourceBindingsTest, MissingBlockDeco) {
   ast::DecorationList decos;
   auto* str = create<ast::Struct>(
-      ast::StructMemberList{
-          Member(StructMemberName(0, ty.i32()), ty.i32(), {MemberOffset(0)})},
+      ast::StructMemberList{Member(StructMemberName(0, ty.i32()), ty.i32())},
       decos);
 
   auto* foo_type = ty.struct_("foo_type", str);
@@ -1546,7 +1536,7 @@
   type::Struct* foo_struct_type;
   type::AccessControl* foo_control_type;
   std::tie(foo_struct_type, foo_control_type) =
-      MakeUniformBufferTypes("foo_type", {{ty.i32(), 0}});
+      MakeUniformBufferTypes("foo_type", {ty.i32()});
   AddUniformBuffer("foo_ub", foo_control_type, 0, 0);
 
   MakeStructVariableReferenceBodyFunction("ub_func", "foo_ub", {{0, ty.i32()}});
@@ -1567,14 +1557,14 @@
             result[0].resource_type);
   EXPECT_EQ(0u, result[0].bind_group);
   EXPECT_EQ(0u, result[0].binding);
-  EXPECT_EQ(16u, result[0].min_buffer_binding_size);
+  EXPECT_EQ(4u, result[0].size);
 }
 
 TEST_F(InspectorGetUniformBufferResourceBindingsTest, MultipleMembers) {
   type::Struct* foo_struct_type;
   type::AccessControl* foo_control_type;
-  std::tie(foo_struct_type, foo_control_type) = MakeUniformBufferTypes(
-      "foo_type", {{ty.i32(), 0}, {ty.u32(), 4}, {ty.f32(), 8}});
+  std::tie(foo_struct_type, foo_control_type) =
+      MakeUniformBufferTypes("foo_type", {ty.i32(), ty.u32(), ty.f32()});
   AddUniformBuffer("foo_ub", foo_control_type, 0, 0);
 
   MakeStructVariableReferenceBodyFunction(
@@ -1596,14 +1586,14 @@
             result[0].resource_type);
   EXPECT_EQ(0u, result[0].bind_group);
   EXPECT_EQ(0u, result[0].binding);
-  EXPECT_EQ(16u, result[0].min_buffer_binding_size);
+  EXPECT_EQ(12u, result[0].size);
 }
 
 TEST_F(InspectorGetUniformBufferResourceBindingsTest, MultipleUniformBuffers) {
   type::Struct* ub_struct_type;
   type::AccessControl* ub_control_type;
-  std::tie(ub_struct_type, ub_control_type) = MakeUniformBufferTypes(
-      "ub_type", {{ty.i32(), 0}, {ty.u32(), 4}, {ty.f32(), 8}});
+  std::tie(ub_struct_type, ub_control_type) =
+      MakeUniformBufferTypes("ub_type", {ty.i32(), ty.u32(), ty.f32()});
   AddUniformBuffer("ub_foo", ub_control_type, 0, 0);
   AddUniformBuffer("ub_bar", ub_control_type, 0, 1);
   AddUniformBuffer("ub_baz", ub_control_type, 2, 0);
@@ -1639,26 +1629,29 @@
             result[0].resource_type);
   EXPECT_EQ(0u, result[0].bind_group);
   EXPECT_EQ(0u, result[0].binding);
-  EXPECT_EQ(16u, result[0].min_buffer_binding_size);
+  EXPECT_EQ(12u, result[0].size);
 
   EXPECT_EQ(ResourceBinding::ResourceType::kUniformBuffer,
             result[1].resource_type);
   EXPECT_EQ(0u, result[1].bind_group);
   EXPECT_EQ(1u, result[1].binding);
-  EXPECT_EQ(16u, result[1].min_buffer_binding_size);
+  EXPECT_EQ(12u, result[1].size);
 
   EXPECT_EQ(ResourceBinding::ResourceType::kUniformBuffer,
             result[2].resource_type);
   EXPECT_EQ(2u, result[2].bind_group);
   EXPECT_EQ(0u, result[2].binding);
-  EXPECT_EQ(16u, result[2].min_buffer_binding_size);
+  EXPECT_EQ(12u, result[2].size);
 }
 
 TEST_F(InspectorGetUniformBufferResourceBindingsTest, ContainingArray) {
   type::Struct* foo_struct_type;
   type::AccessControl* foo_control_type;
-  std::tie(foo_struct_type, foo_control_type) = MakeUniformBufferTypes(
-      "foo_type", {{ty.i32(), 0}, {u32_array_type(4), 4}});
+  // TODO(bclayton) - This is not a legal structure layout for uniform buffer
+  // usage. Once crbug.com/tint/628 is implemented, this will fail validation
+  // and will need to be fixed.
+  std::tie(foo_struct_type, foo_control_type) =
+      MakeUniformBufferTypes("foo_type", {ty.i32(), u32_array_type(4)});
   AddUniformBuffer("foo_ub", foo_control_type, 0, 0);
 
   MakeStructVariableReferenceBodyFunction("ub_func", "foo_ub", {{0, ty.i32()}});
@@ -1679,14 +1672,14 @@
             result[0].resource_type);
   EXPECT_EQ(0u, result[0].bind_group);
   EXPECT_EQ(0u, result[0].binding);
-  EXPECT_EQ(32u, result[0].min_buffer_binding_size);
+  EXPECT_EQ(20u, result[0].size);
 }
 
 TEST_F(InspectorGetStorageBufferResourceBindingsTest, Simple) {
   type::Struct* foo_struct_type;
   type::AccessControl* foo_control_type;
   std::tie(foo_struct_type, foo_control_type) =
-      MakeStorageBufferTypes("foo_type", {{ty.i32(), 0}});
+      MakeStorageBufferTypes("foo_type", {ty.i32()});
   AddStorageBuffer("foo_sb", foo_control_type, 0, 0);
 
   MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", {{0, ty.i32()}});
@@ -1707,14 +1700,14 @@
             result[0].resource_type);
   EXPECT_EQ(0u, result[0].bind_group);
   EXPECT_EQ(0u, result[0].binding);
-  EXPECT_EQ(4u, result[0].min_buffer_binding_size);
+  EXPECT_EQ(4u, result[0].size);
 }
 
 TEST_F(InspectorGetStorageBufferResourceBindingsTest, MultipleMembers) {
   type::Struct* foo_struct_type;
   type::AccessControl* foo_control_type;
-  std::tie(foo_struct_type, foo_control_type) = MakeStorageBufferTypes(
-      "foo_type", {{ty.i32(), 0}, {ty.u32(), 4}, {ty.f32(), 8}});
+  std::tie(foo_struct_type, foo_control_type) =
+      MakeStorageBufferTypes("foo_type", {ty.i32(), ty.u32(), ty.f32()});
   AddStorageBuffer("foo_sb", foo_control_type, 0, 0);
 
   MakeStructVariableReferenceBodyFunction(
@@ -1736,14 +1729,14 @@
             result[0].resource_type);
   EXPECT_EQ(0u, result[0].bind_group);
   EXPECT_EQ(0u, result[0].binding);
-  EXPECT_EQ(12u, result[0].min_buffer_binding_size);
+  EXPECT_EQ(12u, result[0].size);
 }
 
 TEST_F(InspectorGetStorageBufferResourceBindingsTest, MultipleStorageBuffers) {
   type::Struct* sb_struct_type;
   type::AccessControl* sb_control_type;
-  std::tie(sb_struct_type, sb_control_type) = MakeStorageBufferTypes(
-      "sb_type", {{ty.i32(), 0}, {ty.u32(), 4}, {ty.f32(), 8}});
+  std::tie(sb_struct_type, sb_control_type) =
+      MakeStorageBufferTypes("sb_type", {ty.i32(), ty.u32(), ty.f32()});
   AddStorageBuffer("sb_foo", sb_control_type, 0, 0);
   AddStorageBuffer("sb_bar", sb_control_type, 0, 1);
   AddStorageBuffer("sb_baz", sb_control_type, 2, 0);
@@ -1782,26 +1775,26 @@
             result[0].resource_type);
   EXPECT_EQ(0u, result[0].bind_group);
   EXPECT_EQ(0u, result[0].binding);
-  EXPECT_EQ(12u, result[0].min_buffer_binding_size);
+  EXPECT_EQ(12u, result[0].size);
 
   EXPECT_EQ(ResourceBinding::ResourceType::kStorageBuffer,
             result[1].resource_type);
   EXPECT_EQ(0u, result[1].bind_group);
   EXPECT_EQ(1u, result[1].binding);
-  EXPECT_EQ(12u, result[1].min_buffer_binding_size);
+  EXPECT_EQ(12u, result[1].size);
 
   EXPECT_EQ(ResourceBinding::ResourceType::kStorageBuffer,
             result[2].resource_type);
   EXPECT_EQ(2u, result[2].bind_group);
   EXPECT_EQ(0u, result[2].binding);
-  EXPECT_EQ(12u, result[2].min_buffer_binding_size);
+  EXPECT_EQ(12u, result[2].size);
 }
 
 TEST_F(InspectorGetStorageBufferResourceBindingsTest, ContainingArray) {
   type::Struct* foo_struct_type;
   type::AccessControl* foo_control_type;
-  std::tie(foo_struct_type, foo_control_type) = MakeStorageBufferTypes(
-      "foo_type", {{ty.i32(), 0}, {u32_array_type(4), 4}});
+  std::tie(foo_struct_type, foo_control_type) =
+      MakeStorageBufferTypes("foo_type", {ty.i32(), u32_array_type(4)});
   AddStorageBuffer("foo_sb", foo_control_type, 0, 0);
 
   MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", {{0, ty.i32()}});
@@ -1822,14 +1815,14 @@
             result[0].resource_type);
   EXPECT_EQ(0u, result[0].bind_group);
   EXPECT_EQ(0u, result[0].binding);
-  EXPECT_EQ(20u, result[0].min_buffer_binding_size);
+  EXPECT_EQ(20u, result[0].size);
 }
 
 TEST_F(InspectorGetStorageBufferResourceBindingsTest, ContainingRuntimeArray) {
   type::Struct* foo_struct_type;
   type::AccessControl* foo_control_type;
-  std::tie(foo_struct_type, foo_control_type) = MakeStorageBufferTypes(
-      "foo_type", {{ty.i32(), 0}, {u32_array_type(0), 4}});
+  std::tie(foo_struct_type, foo_control_type) =
+      MakeStorageBufferTypes("foo_type", {ty.i32(), u32_array_type(0)});
   AddStorageBuffer("foo_sb", foo_control_type, 0, 0);
 
   MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", {{0, ty.i32()}});
@@ -1850,14 +1843,14 @@
             result[0].resource_type);
   EXPECT_EQ(0u, result[0].bind_group);
   EXPECT_EQ(0u, result[0].binding);
-  EXPECT_EQ(8u, result[0].min_buffer_binding_size);
+  EXPECT_EQ(8u, result[0].size);
 }
 
 TEST_F(InspectorGetStorageBufferResourceBindingsTest, SkipReadOnly) {
   type::Struct* foo_struct_type;
   type::AccessControl* foo_control_type;
   std::tie(foo_struct_type, foo_control_type) =
-      MakeReadOnlyStorageBufferTypes("foo_type", {{ty.i32(), 0}});
+      MakeReadOnlyStorageBufferTypes("foo_type", {ty.i32()});
   AddStorageBuffer("foo_sb", foo_control_type, 0, 0);
 
   MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", {{0, ty.i32()}});
@@ -1879,7 +1872,7 @@
   type::Struct* foo_struct_type;
   type::AccessControl* foo_control_type;
   std::tie(foo_struct_type, foo_control_type) =
-      MakeReadOnlyStorageBufferTypes("foo_type", {{ty.i32(), 0}});
+      MakeReadOnlyStorageBufferTypes("foo_type", {ty.i32()});
   AddStorageBuffer("foo_sb", foo_control_type, 0, 0);
 
   MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", {{0, ty.i32()}});
@@ -1900,15 +1893,15 @@
             result[0].resource_type);
   EXPECT_EQ(0u, result[0].bind_group);
   EXPECT_EQ(0u, result[0].binding);
-  EXPECT_EQ(4u, result[0].min_buffer_binding_size);
+  EXPECT_EQ(4u, result[0].size);
 }
 
 TEST_F(InspectorGetReadOnlyStorageBufferResourceBindingsTest,
        MultipleStorageBuffers) {
   type::Struct* sb_struct_type;
   type::AccessControl* sb_control_type;
-  std::tie(sb_struct_type, sb_control_type) = MakeReadOnlyStorageBufferTypes(
-      "sb_type", {{ty.i32(), 0}, {ty.u32(), 4}, {ty.f32(), 8}});
+  std::tie(sb_struct_type, sb_control_type) =
+      MakeReadOnlyStorageBufferTypes("sb_type", {ty.i32(), ty.u32(), ty.f32()});
   AddStorageBuffer("sb_foo", sb_control_type, 0, 0);
   AddStorageBuffer("sb_bar", sb_control_type, 0, 1);
   AddStorageBuffer("sb_baz", sb_control_type, 2, 0);
@@ -1947,26 +1940,26 @@
             result[0].resource_type);
   EXPECT_EQ(0u, result[0].bind_group);
   EXPECT_EQ(0u, result[0].binding);
-  EXPECT_EQ(12u, result[0].min_buffer_binding_size);
+  EXPECT_EQ(12u, result[0].size);
 
   EXPECT_EQ(ResourceBinding::ResourceType::kReadOnlyStorageBuffer,
             result[1].resource_type);
   EXPECT_EQ(0u, result[1].bind_group);
   EXPECT_EQ(1u, result[1].binding);
-  EXPECT_EQ(12u, result[1].min_buffer_binding_size);
+  EXPECT_EQ(12u, result[1].size);
 
   EXPECT_EQ(ResourceBinding::ResourceType::kReadOnlyStorageBuffer,
             result[2].resource_type);
   EXPECT_EQ(2u, result[2].bind_group);
   EXPECT_EQ(0u, result[2].binding);
-  EXPECT_EQ(12u, result[2].min_buffer_binding_size);
+  EXPECT_EQ(12u, result[2].size);
 }
 
 TEST_F(InspectorGetReadOnlyStorageBufferResourceBindingsTest, ContainingArray) {
   type::Struct* foo_struct_type;
   type::AccessControl* foo_control_type;
-  std::tie(foo_struct_type, foo_control_type) = MakeReadOnlyStorageBufferTypes(
-      "foo_type", {{ty.i32(), 0}, {u32_array_type(4), 4}});
+  std::tie(foo_struct_type, foo_control_type) =
+      MakeReadOnlyStorageBufferTypes("foo_type", {ty.i32(), u32_array_type(4)});
   AddStorageBuffer("foo_sb", foo_control_type, 0, 0);
 
   MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", {{0, ty.i32()}});
@@ -1987,15 +1980,15 @@
             result[0].resource_type);
   EXPECT_EQ(0u, result[0].bind_group);
   EXPECT_EQ(0u, result[0].binding);
-  EXPECT_EQ(20u, result[0].min_buffer_binding_size);
+  EXPECT_EQ(20u, result[0].size);
 }
 
 TEST_F(InspectorGetReadOnlyStorageBufferResourceBindingsTest,
        ContainingRuntimeArray) {
   type::Struct* foo_struct_type;
   type::AccessControl* foo_control_type;
-  std::tie(foo_struct_type, foo_control_type) = MakeReadOnlyStorageBufferTypes(
-      "foo_type", {{ty.i32(), 0}, {u32_array_type(0), 4}});
+  std::tie(foo_struct_type, foo_control_type) =
+      MakeReadOnlyStorageBufferTypes("foo_type", {ty.i32(), u32_array_type(0)});
   AddStorageBuffer("foo_sb", foo_control_type, 0, 0);
 
   MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", {{0, ty.i32()}});
@@ -2016,14 +2009,14 @@
             result[0].resource_type);
   EXPECT_EQ(0u, result[0].bind_group);
   EXPECT_EQ(0u, result[0].binding);
-  EXPECT_EQ(8u, result[0].min_buffer_binding_size);
+  EXPECT_EQ(8u, result[0].size);
 }
 
 TEST_F(InspectorGetReadOnlyStorageBufferResourceBindingsTest, SkipNonReadOnly) {
   type::Struct* foo_struct_type;
   type::AccessControl* foo_control_type;
   std::tie(foo_struct_type, foo_control_type) =
-      MakeStorageBufferTypes("foo_type", {{ty.i32(), 0}});
+      MakeStorageBufferTypes("foo_type", {ty.i32()});
   AddStorageBuffer("foo_sb", foo_control_type, 0, 0);
 
   MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", {{0, ty.i32()}});
diff --git a/src/program_builder.h b/src/program_builder.h
index 941f93e..39832a2 100644
--- a/src/program_builder.h
+++ b/src/program_builder.h
@@ -31,7 +31,9 @@
 #include "src/ast/scalar_constructor_expression.h"
 #include "src/ast/sint_literal.h"
 #include "src/ast/stride_decoration.h"
+#include "src/ast/struct_member_align_decoration.h"
 #include "src/ast/struct_member_offset_decoration.h"
+#include "src/ast/struct_member_size_decoration.h"
 #include "src/ast/type_constructor_expression.h"
 #include "src/ast/uint_literal.h"
 #include "src/ast/variable_decl_statement.h"
@@ -955,6 +957,36 @@
     return create<ast::StructMemberOffsetDecoration>(source_, val);
   }
 
+  /// Creates a ast::StructMemberSizeDecoration
+  /// @param source the source information
+  /// @param val the size value
+  /// @returns the size decoration pointer
+  ast::StructMemberSizeDecoration* MemberSize(Source source, uint32_t val) {
+    return create<ast::StructMemberSizeDecoration>(source, val);
+  }
+
+  /// Creates a ast::StructMemberSizeDecoration
+  /// @param val the size value
+  /// @returns the size decoration pointer
+  ast::StructMemberSizeDecoration* MemberSize(uint32_t val) {
+    return create<ast::StructMemberSizeDecoration>(source_, val);
+  }
+
+  /// Creates a ast::StructMemberAlignDecoration
+  /// @param source the source information
+  /// @param val the align value
+  /// @returns the align decoration pointer
+  ast::StructMemberAlignDecoration* MemberAlign(Source source, uint32_t val) {
+    return create<ast::StructMemberAlignDecoration>(source, val);
+  }
+
+  /// Creates a ast::StructMemberAlignDecoration
+  /// @param val the align value
+  /// @returns the align decoration pointer
+  ast::StructMemberAlignDecoration* MemberAlign(uint32_t val) {
+    return create<ast::StructMemberAlignDecoration>(source_, val);
+  }
+
   /// Creates an ast::Function and registers it with the ast::Module.
   /// @param source the source information
   /// @param name the function name
@@ -995,37 +1027,64 @@
     return func;
   }
 
+  /// Creates a ast::Struct and type::Struct, registering the type::Struct with
+  /// the AST().ConstructedTypes().
+  /// @param source the source information
+  /// @param name the struct name
+  /// @param members the struct members
+  /// @param decorations the optional struct decorations
+  /// @returns the struct type
+  type::Struct* Structure(const Source& source,
+                          const std::string& name,
+                          ast::StructMemberList members,
+                          ast::DecorationList decorations = {}) {
+    auto* impl =
+        create<ast::Struct>(source, std::move(members), std::move(decorations));
+    auto* type = ty.struct_(name, impl);
+    AST().AddConstructedType(type);
+    return type;
+  }
+
+  /// Creates a ast::Struct and type::Struct, registering the type::Struct with
+  /// the AST().ConstructedTypes().
+  /// @param name the struct name
+  /// @param members the struct members
+  /// @param decorations the optional struct decorations
+  /// @returns the struct type
+  type::Struct* Structure(const std::string& name,
+                          ast::StructMemberList members,
+                          ast::DecorationList decorations = {}) {
+    auto* impl =
+        create<ast::Struct>(std::move(members), std::move(decorations));
+    auto* type = ty.struct_(name, impl);
+    AST().AddConstructedType(type);
+    return type;
+  }
+
   /// Creates a ast::StructMember
   /// @param source the source information
   /// @param name the struct member name
   /// @param type the struct member type
+  /// @param decorations the optional struct member decorations
   /// @returns the struct member pointer
   ast::StructMember* Member(const Source& source,
                             const std::string& name,
-                            type::Type* type) {
+                            type::Type* type,
+                            ast::DecorationList decorations = {}) {
     return create<ast::StructMember>(source, Symbols().Register(name), type,
-                                     ast::DecorationList{});
+                                     std::move(decorations));
   }
 
   /// Creates a ast::StructMember
   /// @param name the struct member name
   /// @param type the struct member type
-  /// @returns the struct member pointer
-  ast::StructMember* Member(const std::string& name, type::Type* type) {
-    return create<ast::StructMember>(source_, Symbols().Register(name), type,
-                                     ast::DecorationList{});
-  }
-
-  /// Creates a ast::StructMember
-  /// @param name the struct member name
-  /// @param type the struct member type
-  /// @param decorations the struct member decorations
+  /// @param decorations the optional struct member decorations
   /// @returns the struct member pointer
   ast::StructMember* Member(const std::string& name,
                             type::Type* type,
-                            ast::DecorationList decorations) {
+                            ast::DecorationList decorations = {}) {
     return create<ast::StructMember>(source_, Symbols().Register(name), type,
-                                     decorations);
+                                     std::move(decorations));
   }
 
   /// Creates a ast::StructMember with the given byte offset
diff --git a/src/reader/spirv/parser_impl_convert_type_test.cc b/src/reader/spirv/parser_impl_convert_type_test.cc
index e4d2fba..ab21b4d 100644
--- a/src/reader/spirv/parser_impl_convert_type_test.cc
+++ b/src/reader/spirv/parser_impl_convert_type_test.cc
@@ -331,8 +331,7 @@
   EXPECT_TRUE(arr_type->IsRuntimeArray());
   ASSERT_NE(arr_type, nullptr);
   EXPECT_EQ(arr_type->size(), 0u);
-  EXPECT_EQ(arr_type->array_stride(), 0u);
-  EXPECT_FALSE(arr_type->has_array_stride());
+  EXPECT_EQ(arr_type->decorations().size(), 0u);
   auto* elem_type = arr_type->type();
   ASSERT_NE(elem_type, nullptr);
   EXPECT_TRUE(elem_type->Is<type::U32>());
@@ -365,8 +364,10 @@
   auto* arr_type = type->As<type::Array>();
   EXPECT_TRUE(arr_type->IsRuntimeArray());
   ASSERT_NE(arr_type, nullptr);
-  EXPECT_EQ(arr_type->array_stride(), 64u);
-  EXPECT_TRUE(arr_type->has_array_stride());
+  ASSERT_EQ(arr_type->decorations().size(), 1u);
+  auto* stride = arr_type->decorations()[0];
+  ASSERT_TRUE(stride->Is<ast::StrideDecoration>());
+  ASSERT_EQ(stride->As<ast::StrideDecoration>()->stride(), 64u);
   EXPECT_TRUE(p->error().empty());
 }
 
@@ -413,8 +414,7 @@
   EXPECT_FALSE(arr_type->IsRuntimeArray());
   ASSERT_NE(arr_type, nullptr);
   EXPECT_EQ(arr_type->size(), 42u);
-  EXPECT_EQ(arr_type->array_stride(), 0u);
-  EXPECT_FALSE(arr_type->has_array_stride());
+  EXPECT_EQ(arr_type->decorations().size(), 0u);
   auto* elem_type = arr_type->type();
   ASSERT_NE(elem_type, nullptr);
   EXPECT_TRUE(elem_type->Is<type::U32>());
@@ -499,8 +499,12 @@
   EXPECT_TRUE(type->Is<type::Array>());
   auto* arr_type = type->As<type::Array>();
   ASSERT_NE(arr_type, nullptr);
-  ASSERT_EQ(arr_type->array_stride(), 8u);
-  EXPECT_TRUE(arr_type->has_array_stride());
+
+  ASSERT_EQ(arr_type->decorations().size(), 1u);
+  auto* stride = arr_type->decorations()[0];
+  ASSERT_TRUE(stride->Is<ast::StrideDecoration>());
+  ASSERT_EQ(stride->As<ast::StrideDecoration>()->stride(), 8u);
+
   EXPECT_TRUE(p->error().empty());
 }
 
diff --git a/src/reader/wgsl/parser_impl.cc b/src/reader/wgsl/parser_impl.cc
index b025f44..34fc690 100644
--- a/src/reader/wgsl/parser_impl.cc
+++ b/src/reader/wgsl/parser_impl.cc
@@ -104,7 +104,9 @@
 const char kConstantIdDecoration[] = "constant_id";
 const char kGroupDecoration[] = "group";
 const char kLocationDecoration[] = "location";
-const char kOffsetDecoration[] = "offset";
+const char kOffsetDecoration[] = "offset";  // DEPRECATED
+const char kSizeDecoration[] = "size";
+const char kAlignDecoration[] = "align";
 const char kSetDecoration[] = "set";
 const char kStageDecoration[] = "stage";
 const char kStrideDecoration[] = "stride";
@@ -115,11 +117,12 @@
     return false;
 
   auto s = t.to_str();
-  return s == kAccessDecoration || s == kBindingDecoration ||
-         s == kBlockDecoration || s == kBuiltinDecoration ||
-         s == kConstantIdDecoration || s == kLocationDecoration ||
+  return s == kAccessDecoration || s == kAlignDecoration ||
+         s == kBindingDecoration || s == kBlockDecoration ||
+         s == kBuiltinDecoration || s == kConstantIdDecoration ||
+         s == kGroupDecoration || s == kLocationDecoration ||
          s == kOffsetDecoration || s == kSetDecoration ||
-         s == kGroupDecoration || s == kStageDecoration ||
+         s == kSizeDecoration || s == kStageDecoration ||
          s == kStrideDecoration || s == kWorkgroupSizeDecoration;
 }
 
@@ -2919,6 +2922,28 @@
     });
   }
 
+  if (s == kSizeDecoration) {
+    const char* use = "size decoration";
+    return expect_paren_block(use, [&]() -> Result {
+      auto val = expect_positive_sint(use);
+      if (val.errored)
+        return Failure::kErrored;
+
+      return create<ast::StructMemberSizeDecoration>(t.source(), val.value);
+    });
+  }
+
+  if (s == kAlignDecoration) {
+    const char* use = "align decoration";
+    return expect_paren_block(use, [&]() -> Result {
+      auto val = expect_positive_sint(use);
+      if (val.errored)
+        return Failure::kErrored;
+
+      return create<ast::StructMemberAlignDecoration>(t.source(), val.value);
+    });
+  }
+
   if (s == kConstantIdDecoration) {
     const char* use = "constant_id decoration";
     return expect_paren_block(use, [&]() -> Result {
diff --git a/src/reader/wgsl/parser_impl_error_msg_test.cc b/src/reader/wgsl/parser_impl_error_msg_test.cc
index 3fae7f6..00cd58f 100644
--- a/src/reader/wgsl/parser_impl_error_msg_test.cc
+++ b/src/reader/wgsl/parser_impl_error_msg_test.cc
@@ -704,35 +704,64 @@
          "                   ^\n");
 }
 
-TEST_F(ParserImplErrorTest, GlobalDeclStructMemberOffsetMissingLParen) {
-  EXPECT("struct S { [[offset 1)]] i : i32; };",
-         "test.wgsl:1:21 error: expected '(' for offset decoration\n"
-         "struct S { [[offset 1)]] i : i32; };\n"
-         "                    ^\n");
+TEST_F(ParserImplErrorTest, GlobalDeclStructMemberAlignMissingLParen) {
+  EXPECT("struct S { [[align 1)]] i : i32; };",
+         "test.wgsl:1:20 error: expected '(' for align decoration\n"
+         "struct S { [[align 1)]] i : i32; };\n"
+         "                   ^\n");
 }
 
-TEST_F(ParserImplErrorTest, GlobalDeclStructMemberOffsetMissingRParen) {
-  EXPECT("struct S { [[offset(1]] i : i32; };",
-         "test.wgsl:1:22 error: expected ')' for offset decoration\n"
-         "struct S { [[offset(1]] i : i32; };\n"
-         "                     ^^\n");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclStructMemberOffsetInvaldValue) {
-  EXPECT("struct S { [[offset(x)]] i : i32; };",
-         "test.wgsl:1:21 error: expected signed integer literal for offset "
-         "decoration\n"
-         "struct S { [[offset(x)]] i : i32; };\n"
-         "                    ^\n");
-}
-
-TEST_F(ParserImplErrorTest, GlobalDeclStructMemberOffsetNegativeValue) {
-  EXPECT("struct S { [[offset(-2)]] i : i32; };",
-         "test.wgsl:1:21 error: offset decoration must be positive\n"
-         "struct S { [[offset(-2)]] i : i32; };\n"
+TEST_F(ParserImplErrorTest, GlobalDeclStructMemberAlignMissingRParen) {
+  EXPECT("struct S { [[align(1]] i : i32; };",
+         "test.wgsl:1:21 error: expected ')' for align decoration\n"
+         "struct S { [[align(1]] i : i32; };\n"
          "                    ^^\n");
 }
 
+TEST_F(ParserImplErrorTest, GlobalDeclStructMemberAlignInvaldValue) {
+  EXPECT("struct S { [[align(x)]] i : i32; };",
+         "test.wgsl:1:20 error: expected signed integer literal for align "
+         "decoration\n"
+         "struct S { [[align(x)]] i : i32; };\n"
+         "                   ^\n");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclStructMemberAlignNegativeValue) {
+  EXPECT("struct S { [[align(-2)]] i : i32; };",
+         "test.wgsl:1:20 error: align decoration must be positive\n"
+         "struct S { [[align(-2)]] i : i32; };\n"
+         "                   ^^\n");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclStructMemberSizeMissingLParen) {
+  EXPECT("struct S { [[size 1)]] i : i32; };",
+         "test.wgsl:1:19 error: expected '(' for size decoration\n"
+         "struct S { [[size 1)]] i : i32; };\n"
+         "                  ^\n");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclStructMemberSizeMissingRParen) {
+  EXPECT("struct S { [[size(1]] i : i32; };",
+         "test.wgsl:1:20 error: expected ')' for size decoration\n"
+         "struct S { [[size(1]] i : i32; };\n"
+         "                   ^^\n");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclStructMemberSizeInvaldValue) {
+  EXPECT("struct S { [[size(x)]] i : i32; };",
+         "test.wgsl:1:19 error: expected signed integer literal for size "
+         "decoration\n"
+         "struct S { [[size(x)]] i : i32; };\n"
+         "                  ^\n");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclStructMemberSizeNegativeValue) {
+  EXPECT("struct S { [[size(-2)]] i : i32; };",
+         "test.wgsl:1:19 error: size decoration must be positive\n"
+         "struct S { [[size(-2)]] i : i32; };\n"
+         "                  ^^\n");
+}
+
 TEST_F(ParserImplErrorTest, GlobalDeclTypeAliasMissingIdentifier) {
   EXPECT("type 1 = f32;",
          "test.wgsl:1:6 error: expected identifier for type alias\n"
diff --git a/src/reader/wgsl/parser_impl_global_decl_test.cc b/src/reader/wgsl/parser_impl_global_decl_test.cc
index 08a7a50..de3d4716 100644
--- a/src/reader/wgsl/parser_impl_global_decl_test.cc
+++ b/src/reader/wgsl/parser_impl_global_decl_test.cc
@@ -173,8 +173,7 @@
 }
 
 TEST_F(ParserImplTest, GlobalDecl_Struct_WithStride) {
-  auto p =
-      parser("struct A { [[offset(0)]] data: [[stride(4)]] array<f32>; };");
+  auto p = parser("struct A { data: [[stride(4)]] array<f32>; };");
 
   p->expect_global_decl();
   ASSERT_FALSE(p->has_error()) << p->error();
@@ -194,12 +193,15 @@
   const auto* ty = str->impl()->members()[0]->type();
   ASSERT_TRUE(ty->Is<type::Array>());
   const auto* arr = ty->As<type::Array>();
-  EXPECT_TRUE(arr->has_array_stride());
-  EXPECT_EQ(arr->array_stride(), 4u);
+
+  ASSERT_EQ(arr->decorations().size(), 1u);
+  auto* stride = arr->decorations()[0];
+  ASSERT_TRUE(stride->Is<ast::StrideDecoration>());
+  ASSERT_EQ(stride->As<ast::StrideDecoration>()->stride(), 4u);
 }
 
 TEST_F(ParserImplTest, GlobalDecl_Struct_WithDecoration) {
-  auto p = parser("[[block]] struct A { [[offset(0)]] data: f32; };");
+  auto p = parser("[[block]] struct A { data: f32; };");
   p->expect_global_decl();
   ASSERT_FALSE(p->has_error()) << p->error();
 
diff --git a/src/reader/wgsl/parser_impl_struct_body_decl_test.cc b/src/reader/wgsl/parser_impl_struct_body_decl_test.cc
index c7f588b..16bcf4b 100644
--- a/src/reader/wgsl/parser_impl_struct_body_decl_test.cc
+++ b/src/reader/wgsl/parser_impl_struct_body_decl_test.cc
@@ -44,16 +44,28 @@
   ASSERT_EQ(m.value.size(), 0u);
 }
 
-TEST_F(ParserImplTest, StructBodyDecl_InvalidMember) {
+TEST_F(ParserImplTest, StructBodyDecl_InvalidAlign) {
   auto p = parser(R"(
 {
-  [[offset(nan)]] a : i32;
+  [[align(nan)]] a : i32;
 })");
   auto m = p->expect_struct_body_decl();
   ASSERT_TRUE(p->has_error());
   ASSERT_TRUE(m.errored);
   EXPECT_EQ(p->error(),
-            "3:12: expected signed integer literal for offset decoration");
+            "3:11: expected signed integer literal for align decoration");
+}
+
+TEST_F(ParserImplTest, StructBodyDecl_InvalidSize) {
+  auto p = parser(R"(
+{
+  [[size(nan)]] a : i32;
+})");
+  auto m = p->expect_struct_body_decl();
+  ASSERT_TRUE(p->has_error());
+  ASSERT_TRUE(m.errored);
+  EXPECT_EQ(p->error(),
+            "3:10: expected signed integer literal for size decoration");
 }
 
 TEST_F(ParserImplTest, StructBodyDecl_MissingClosingBracket) {
diff --git a/src/reader/wgsl/parser_impl_struct_decl_test.cc b/src/reader/wgsl/parser_impl_struct_decl_test.cc
index 23a3dd0..869baa2 100644
--- a/src/reader/wgsl/parser_impl_struct_decl_test.cc
+++ b/src/reader/wgsl/parser_impl_struct_decl_test.cc
@@ -24,7 +24,7 @@
   auto p = parser(R"(
 struct S {
   a : i32;
-  [[offset(4)]] b : f32;
+  b : f32;
 })");
   auto decos = p->decoration_list();
   EXPECT_FALSE(decos.errored);
diff --git a/src/reader/wgsl/parser_impl_struct_member_decoration_decl_test.cc b/src/reader/wgsl/parser_impl_struct_member_decoration_decl_test.cc
index 533a5bb..107c358 100644
--- a/src/reader/wgsl/parser_impl_struct_member_decoration_decl_test.cc
+++ b/src/reader/wgsl/parser_impl_struct_member_decoration_decl_test.cc
@@ -39,7 +39,7 @@
 }
 
 TEST_F(ParserImplTest, DecorationDecl_Single) {
-  auto p = parser("[[offset(4)]]");
+  auto p = parser("[[size(4)]]");
   auto decos = p->decoration_list();
   EXPECT_FALSE(p->has_error());
   EXPECT_FALSE(decos.errored);
@@ -47,26 +47,35 @@
   ASSERT_EQ(decos.value.size(), 1u);
   auto* deco = decos.value[0]->As<ast::Decoration>();
   ASSERT_NE(deco, nullptr);
-  EXPECT_TRUE(deco->Is<ast::StructMemberOffsetDecoration>());
+  EXPECT_TRUE(deco->Is<ast::StructMemberSizeDecoration>());
 }
 
 TEST_F(ParserImplTest, DecorationDecl_InvalidDecoration) {
-  auto p = parser("[[offset(nan)]]");
+  auto p = parser("[[size(nan)]]");
   auto decos = p->decoration_list();
   EXPECT_TRUE(p->has_error()) << p->error();
   EXPECT_TRUE(decos.errored);
   EXPECT_FALSE(decos.matched);
   EXPECT_EQ(p->error(),
-            "1:10: expected signed integer literal for offset decoration");
+            "1:8: expected signed integer literal for size decoration");
 }
 
 TEST_F(ParserImplTest, DecorationDecl_MissingClose) {
-  auto p = parser("[[offset(4)");
+  auto p = parser("[[size(4)");
   auto decos = p->decoration_list();
   EXPECT_TRUE(p->has_error()) << p->error();
   EXPECT_TRUE(decos.errored);
   EXPECT_FALSE(decos.matched);
-  EXPECT_EQ(p->error(), "1:12: expected ']]' for decoration list");
+  EXPECT_EQ(p->error(), "1:10: expected ']]' for decoration list");
+}
+
+TEST_F(ParserImplTest, StructMemberDecorationDecl_SizeMissingClose) {
+  auto p = parser("[[size(4)");
+  auto decos = p->decoration_list();
+  EXPECT_TRUE(p->has_error()) << p->error();
+  EXPECT_TRUE(decos.errored);
+  EXPECT_FALSE(decos.matched);
+  EXPECT_EQ(p->error(), "1:10: expected ']]' for decoration list");
 }
 
 }  // namespace
diff --git a/src/reader/wgsl/parser_impl_struct_member_test.cc b/src/reader/wgsl/parser_impl_struct_member_test.cc
index 911c3be..68ce72a 100644
--- a/src/reader/wgsl/parser_impl_struct_member_test.cc
+++ b/src/reader/wgsl/parser_impl_struct_member_test.cc
@@ -23,7 +23,7 @@
   auto p = parser("a : i32;");
 
   auto& builder = p->builder();
-  auto* i32 = builder.create<type::I32>();
+  auto* i32 = builder.ty.i32();
 
   auto decos = p->decoration_list();
   EXPECT_FALSE(decos.errored);
@@ -45,11 +45,11 @@
   ASSERT_EQ(m->source().range.end.column, 2u);
 }
 
-TEST_F(ParserImplTest, StructMember_ParsesWithDecoration) {
+TEST_F(ParserImplTest, StructMember_ParsesWithOffsetDecoration_DEPRECATED) {
   auto p = parser("[[offset(2)]] a : i32;");
 
   auto& builder = p->builder();
-  auto* i32 = builder.create<type::I32>();
+  auto* i32 = builder.ty.i32();
 
   auto decos = p->decoration_list();
   EXPECT_FALSE(decos.errored);
@@ -75,12 +75,99 @@
   ASSERT_EQ(m->source().range.end.column, 16u);
 }
 
-TEST_F(ParserImplTest, StructMember_ParsesWithMultipleDecorations) {
-  auto p = parser(R"([[offset(2)]]
-[[offset(4)]] a : i32;)");
+TEST_F(ParserImplTest, StructMember_ParsesWithAlignDecoration) {
+  auto p = parser("[[align(2)]] a : i32;");
 
   auto& builder = p->builder();
-  auto* i32 = builder.create<type::I32>();
+  auto* i32 = builder.ty.i32();
+
+  auto decos = p->decoration_list();
+  EXPECT_FALSE(decos.errored);
+  EXPECT_TRUE(decos.matched);
+  EXPECT_EQ(decos.value.size(), 1u);
+
+  auto m = p->expect_struct_member(decos.value);
+  ASSERT_FALSE(p->has_error());
+  ASSERT_FALSE(m.errored);
+  ASSERT_NE(m.value, nullptr);
+
+  EXPECT_EQ(m->symbol(), builder.Symbols().Get("a"));
+  EXPECT_EQ(m->type(), i32);
+  EXPECT_EQ(m->decorations().size(), 1u);
+  EXPECT_TRUE(m->decorations()[0]->Is<ast::StructMemberAlignDecoration>());
+  EXPECT_EQ(
+      m->decorations()[0]->As<ast::StructMemberAlignDecoration>()->align(), 2u);
+
+  ASSERT_EQ(m->source().range.begin.line, 1u);
+  ASSERT_EQ(m->source().range.begin.column, 14u);
+  ASSERT_EQ(m->source().range.end.line, 1u);
+  ASSERT_EQ(m->source().range.end.column, 15u);
+}
+
+TEST_F(ParserImplTest, StructMember_ParsesWithSizeDecoration) {
+  auto p = parser("[[size(2)]] a : i32;");
+
+  auto& builder = p->builder();
+  auto* i32 = builder.ty.i32();
+
+  auto decos = p->decoration_list();
+  EXPECT_FALSE(decos.errored);
+  EXPECT_TRUE(decos.matched);
+  EXPECT_EQ(decos.value.size(), 1u);
+
+  auto m = p->expect_struct_member(decos.value);
+  ASSERT_FALSE(p->has_error());
+  ASSERT_FALSE(m.errored);
+  ASSERT_NE(m.value, nullptr);
+
+  EXPECT_EQ(m->symbol(), builder.Symbols().Get("a"));
+  EXPECT_EQ(m->type(), i32);
+  EXPECT_EQ(m->decorations().size(), 1u);
+  EXPECT_TRUE(m->decorations()[0]->Is<ast::StructMemberSizeDecoration>());
+  EXPECT_EQ(m->decorations()[0]->As<ast::StructMemberSizeDecoration>()->size(),
+            2u);
+
+  ASSERT_EQ(m->source().range.begin.line, 1u);
+  ASSERT_EQ(m->source().range.begin.column, 13u);
+  ASSERT_EQ(m->source().range.end.line, 1u);
+  ASSERT_EQ(m->source().range.end.column, 14u);
+}
+
+TEST_F(ParserImplTest, StructMember_ParsesWithDecoration) {
+  auto p = parser("[[size(2)]] a : i32;");
+
+  auto& builder = p->builder();
+  auto* i32 = builder.ty.i32();
+
+  auto decos = p->decoration_list();
+  EXPECT_FALSE(decos.errored);
+  EXPECT_TRUE(decos.matched);
+  EXPECT_EQ(decos.value.size(), 1u);
+
+  auto m = p->expect_struct_member(decos.value);
+  ASSERT_FALSE(p->has_error());
+  ASSERT_FALSE(m.errored);
+  ASSERT_NE(m.value, nullptr);
+
+  EXPECT_EQ(m->symbol(), builder.Symbols().Get("a"));
+  EXPECT_EQ(m->type(), i32);
+  EXPECT_EQ(m->decorations().size(), 1u);
+  EXPECT_TRUE(m->decorations()[0]->Is<ast::StructMemberSizeDecoration>());
+  EXPECT_EQ(m->decorations()[0]->As<ast::StructMemberSizeDecoration>()->size(),
+            2u);
+
+  ASSERT_EQ(m->source().range.begin.line, 1u);
+  ASSERT_EQ(m->source().range.begin.column, 13u);
+  ASSERT_EQ(m->source().range.end.line, 1u);
+  ASSERT_EQ(m->source().range.end.column, 14u);
+}
+
+TEST_F(ParserImplTest, StructMember_ParsesWithMultipleDecorations) {
+  auto p = parser(R"([[size(2)]]
+[[align(4)]] a : i32;)");
+
+  auto& builder = p->builder();
+  auto* i32 = builder.ty.i32();
 
   auto decos = p->decoration_list();
   EXPECT_FALSE(decos.errored);
@@ -95,23 +182,21 @@
   EXPECT_EQ(m->symbol(), builder.Symbols().Get("a"));
   EXPECT_EQ(m->type(), i32);
   EXPECT_EQ(m->decorations().size(), 2u);
-  EXPECT_TRUE(m->decorations()[0]->Is<ast::StructMemberOffsetDecoration>());
+  EXPECT_TRUE(m->decorations()[0]->Is<ast::StructMemberSizeDecoration>());
+  EXPECT_EQ(m->decorations()[0]->As<ast::StructMemberSizeDecoration>()->size(),
+            2u);
+  EXPECT_TRUE(m->decorations()[1]->Is<ast::StructMemberAlignDecoration>());
   EXPECT_EQ(
-      m->decorations()[0]->As<ast::StructMemberOffsetDecoration>()->offset(),
-      2u);
-  EXPECT_TRUE(m->decorations()[1]->Is<ast::StructMemberOffsetDecoration>());
-  EXPECT_EQ(
-      m->decorations()[1]->As<ast::StructMemberOffsetDecoration>()->offset(),
-      4u);
+      m->decorations()[1]->As<ast::StructMemberAlignDecoration>()->align(), 4u);
 
   ASSERT_EQ(m->source().range.begin.line, 2u);
-  ASSERT_EQ(m->source().range.begin.column, 15u);
+  ASSERT_EQ(m->source().range.begin.column, 14u);
   ASSERT_EQ(m->source().range.end.line, 2u);
-  ASSERT_EQ(m->source().range.end.column, 16u);
+  ASSERT_EQ(m->source().range.end.column, 15u);
 }
 
 TEST_F(ParserImplTest, StructMember_InvalidDecoration) {
-  auto p = parser("[[offset(nan)]] a : i32;");
+  auto p = parser("[[size(nan)]] a : i32;");
   auto decos = p->decoration_list();
   EXPECT_TRUE(decos.errored);
   EXPECT_FALSE(decos.matched);
@@ -122,11 +207,11 @@
 
   ASSERT_TRUE(p->has_error());
   EXPECT_EQ(p->error(),
-            "1:10: expected signed integer literal for offset decoration");
+            "1:8: expected signed integer literal for size decoration");
 }
 
 TEST_F(ParserImplTest, StructMember_InvalidVariable) {
-  auto p = parser("[[offset(4)]] a : B;");
+  auto p = parser("[[size(4)]] a : B;");
   auto decos = p->decoration_list();
   EXPECT_FALSE(decos.errored);
   EXPECT_TRUE(decos.matched);
@@ -135,7 +220,7 @@
   ASSERT_TRUE(p->has_error());
   ASSERT_TRUE(m.errored);
   ASSERT_EQ(m.value, nullptr);
-  EXPECT_EQ(p->error(), "1:19: unknown constructed type 'B'");
+  EXPECT_EQ(p->error(), "1:17: unknown constructed type 'B'");
 }
 
 TEST_F(ParserImplTest, StructMember_MissingSemicolon) {
diff --git a/src/reader/wgsl/parser_impl_type_decl_test.cc b/src/reader/wgsl/parser_impl_type_decl_test.cc
index ab4d9f2..995c411 100644
--- a/src/reader/wgsl/parser_impl_type_decl_test.cc
+++ b/src/reader/wgsl/parser_impl_type_decl_test.cc
@@ -344,7 +344,7 @@
   ASSERT_FALSE(a->IsRuntimeArray());
   ASSERT_EQ(a->size(), 5u);
   ASSERT_TRUE(a->type()->Is<type::F32>());
-  ASSERT_FALSE(a->has_array_stride());
+  EXPECT_EQ(a->decorations().size(), 0u);
 }
 
 TEST_F(ParserImplTest, TypeDecl_Array_Stride) {
@@ -360,8 +360,11 @@
   ASSERT_FALSE(a->IsRuntimeArray());
   ASSERT_EQ(a->size(), 5u);
   ASSERT_TRUE(a->type()->Is<type::F32>());
-  ASSERT_TRUE(a->has_array_stride());
-  EXPECT_EQ(a->array_stride(), 16u);
+
+  ASSERT_EQ(a->decorations().size(), 1u);
+  auto* stride = a->decorations()[0];
+  ASSERT_TRUE(stride->Is<ast::StrideDecoration>());
+  ASSERT_EQ(stride->As<ast::StrideDecoration>()->stride(), 16u);
 }
 
 TEST_F(ParserImplTest, TypeDecl_Array_Runtime_Stride) {
@@ -376,8 +379,11 @@
   auto* a = t->As<type::Array>();
   ASSERT_TRUE(a->IsRuntimeArray());
   ASSERT_TRUE(a->type()->Is<type::F32>());
-  ASSERT_TRUE(a->has_array_stride());
-  EXPECT_EQ(a->array_stride(), 16u);
+
+  ASSERT_EQ(a->decorations().size(), 1u);
+  auto* stride = a->decorations()[0];
+  ASSERT_TRUE(stride->Is<ast::StrideDecoration>());
+  ASSERT_EQ(stride->As<ast::StrideDecoration>()->stride(), 16u);
 }
 
 TEST_F(ParserImplTest, TypeDecl_Array_MultipleDecorations_OneBlock) {
diff --git a/src/resolver/is_storeable_test.cc b/src/resolver/is_storeable_test.cc
new file mode 100644
index 0000000..e11d539
--- /dev/null
+++ b/src/resolver/is_storeable_test.cc
@@ -0,0 +1,137 @@
+// 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"
+
+namespace tint {
+namespace resolver {
+namespace {
+
+using ResolverIsStorableTest = ResolverTest;
+
+TEST_F(ResolverIsStorableTest, Void) {
+  auto* void_ty = ty.void_();
+
+  EXPECT_FALSE(r()->IsStorable(void_ty));
+}
+
+TEST_F(ResolverIsStorableTest, Scalar) {
+  auto* bool_ = ty.bool_();
+  auto* i32 = ty.i32();
+  auto* u32 = ty.u32();
+  auto* f32 = ty.f32();
+
+  EXPECT_TRUE(r()->IsStorable(bool_));
+  EXPECT_TRUE(r()->IsStorable(i32));
+  EXPECT_TRUE(r()->IsStorable(u32));
+  EXPECT_TRUE(r()->IsStorable(f32));
+}
+
+TEST_F(ResolverIsStorableTest, Vector) {
+  auto* vec2_i32 = ty.vec2<int>();
+  auto* vec3_i32 = ty.vec3<int>();
+  auto* vec4_i32 = ty.vec4<int>();
+  auto* vec2_u32 = ty.vec2<unsigned>();
+  auto* vec3_u32 = ty.vec3<unsigned>();
+  auto* vec4_u32 = ty.vec4<unsigned>();
+  auto* vec2_f32 = ty.vec2<float>();
+  auto* vec3_f32 = ty.vec3<float>();
+  auto* vec4_f32 = ty.vec4<float>();
+
+  EXPECT_TRUE(r()->IsStorable(vec2_i32));
+  EXPECT_TRUE(r()->IsStorable(vec3_i32));
+  EXPECT_TRUE(r()->IsStorable(vec4_i32));
+  EXPECT_TRUE(r()->IsStorable(vec2_u32));
+  EXPECT_TRUE(r()->IsStorable(vec3_u32));
+  EXPECT_TRUE(r()->IsStorable(vec4_u32));
+  EXPECT_TRUE(r()->IsStorable(vec2_f32));
+  EXPECT_TRUE(r()->IsStorable(vec3_f32));
+  EXPECT_TRUE(r()->IsStorable(vec4_f32));
+}
+
+TEST_F(ResolverIsStorableTest, Matrix) {
+  auto* mat2x2 = ty.mat2x2<float>();
+  auto* mat2x3 = ty.mat2x3<float>();
+  auto* mat2x4 = ty.mat2x4<float>();
+  auto* mat3x2 = ty.mat3x2<float>();
+  auto* mat3x3 = ty.mat3x3<float>();
+  auto* mat3x4 = ty.mat3x4<float>();
+  auto* mat4x2 = ty.mat4x2<float>();
+  auto* mat4x3 = ty.mat4x3<float>();
+  auto* mat4x4 = ty.mat4x4<float>();
+
+  EXPECT_TRUE(r()->IsStorable(mat2x2));
+  EXPECT_TRUE(r()->IsStorable(mat2x3));
+  EXPECT_TRUE(r()->IsStorable(mat2x4));
+  EXPECT_TRUE(r()->IsStorable(mat3x2));
+  EXPECT_TRUE(r()->IsStorable(mat3x3));
+  EXPECT_TRUE(r()->IsStorable(mat3x4));
+  EXPECT_TRUE(r()->IsStorable(mat4x2));
+  EXPECT_TRUE(r()->IsStorable(mat4x3));
+  EXPECT_TRUE(r()->IsStorable(mat4x4));
+}
+
+TEST_F(ResolverIsStorableTest, Pointer) {
+  auto* ptr_ty = ty.pointer<int>(ast::StorageClass::kPrivate);
+
+  EXPECT_FALSE(r()->IsStorable(ptr_ty));
+}
+
+TEST_F(ResolverIsStorableTest, AliasVoid) {
+  auto* alias = ty.alias("myalias", ty.void_());
+
+  EXPECT_FALSE(r()->IsStorable(alias));
+}
+
+TEST_F(ResolverIsStorableTest, AliasI32) {
+  auto* alias = ty.alias("myalias", ty.i32());
+
+  EXPECT_TRUE(r()->IsStorable(alias));
+}
+
+TEST_F(ResolverIsStorableTest, ArraySizedOfStorable) {
+  auto* arr = ty.array(ty.i32(), 5);
+
+  EXPECT_TRUE(r()->IsStorable(arr));
+}
+
+TEST_F(ResolverIsStorableTest, ArrayUnsizedOfStorable) {
+  auto* arr = ty.array<int>();
+
+  EXPECT_TRUE(r()->IsStorable(arr));
+}
+
+TEST_F(ResolverIsStorableTest, Struct_AllMembersStorable) {
+  ast::StructMemberList members{Member("a", ty.i32()), Member("b", ty.f32())};
+  auto* s = create<ast::Struct>(Source{}, members, ast::DecorationList{});
+  auto* s_ty = ty.struct_("mystruct", s);
+
+  EXPECT_TRUE(r()->IsStorable(s_ty));
+}
+
+TEST_F(ResolverIsStorableTest, Struct_SomeMembersNonStorable) {
+  auto* ptr_ty = ty.pointer<int>(ast::StorageClass::kPrivate);
+  ast::StructMemberList members{Member("a", ty.i32()), Member("b", ptr_ty)};
+  auto* s = create<ast::Struct>(Source{}, members, ast::DecorationList{});
+  auto* s_ty = ty.struct_("mystruct", s);
+
+  EXPECT_FALSE(r()->IsStorable(s_ty));
+}
+
+}  // namespace
+}  // namespace resolver
+}  // namespace tint
diff --git a/src/resolver/resolver.cc b/src/resolver/resolver.cc
index 24ea098..ddec567 100644
--- a/src/resolver/resolver.cc
+++ b/src/resolver/resolver.cc
@@ -30,11 +30,14 @@
 #include "src/ast/switch_statement.h"
 #include "src/ast/unary_op_expression.h"
 #include "src/ast/variable_decl_statement.h"
+#include "src/semantic/array.h"
 #include "src/semantic/call.h"
 #include "src/semantic/function.h"
 #include "src/semantic/member_accessor_expression.h"
 #include "src/semantic/statement.h"
+#include "src/semantic/struct.h"
 #include "src/semantic/variable.h"
+#include "src/type/access_control_type.h"
 
 namespace tint {
 namespace resolver {
@@ -59,6 +62,20 @@
   T old_value_;
 };
 
+/// Rounds `value` to the next multiple of `alignment`
+/// Assumes `alignment` is positive.
+template <typename T>
+T RoundUp(T alignment, T value) {
+  return ((value + alignment - 1) / alignment) * alignment;
+}
+
+/// Returns true if `value` is a power-of-two.
+/// Assumes `alignment` is positive.
+template <typename T>
+bool IsPowerOfTwo(T value) {
+  return (value & (value - 1)) == 0;
+}
+
 }  // namespace
 
 Resolver::Resolver(ProgramBuilder* builder)
@@ -98,7 +115,47 @@
   return result;
 }
 
+bool Resolver::IsStorable(type::Type* type) {
+  if (type == nullptr) {
+    return false;
+  }
+  if (type->is_scalar() || type->Is<type::Vector>() ||
+      type->Is<type::Matrix>()) {
+    return true;
+  }
+  if (type::Array* array_type = type->As<type::Array>()) {
+    return IsStorable(array_type->type());
+  }
+  if (type::Struct* struct_type = type->As<type::Struct>()) {
+    for (const auto* member : struct_type->impl()->members()) {
+      if (!IsStorable(member->type())) {
+        return false;
+      }
+    }
+    return true;
+  }
+  if (type::Alias* alias_type = type->As<type::Alias>()) {
+    return IsStorable(alias_type->type());
+  }
+  return false;
+}
+
 bool Resolver::ResolveInternal() {
+  for (auto* ty : builder_->Types()) {
+    if (auto* str = ty->As<type::Struct>()) {
+      if (!Structure(str)) {
+        return false;
+      }
+      continue;
+    }
+    if (auto* arr = ty->As<type::Array>()) {
+      if (!Array(arr)) {
+        return false;
+      }
+      continue;
+    }
+  }
+
   for (auto* var : builder_->AST().GlobalVariables()) {
     variable_stack_.set_global(var->symbol(), CreateVariableInfo(var));
 
@@ -962,6 +1019,204 @@
   }
 }
 
+bool Resolver::DefaultAlignAndSize(type::Type* ty,
+                                   uint32_t& align,
+                                   uint32_t& size) {
+  static constexpr uint32_t vector_size[] = {
+      /* padding */ 0,
+      /* padding */ 0,
+      /*vec2*/ 8,
+      /*vec3*/ 12,
+      /*vec4*/ 16,
+  };
+  static constexpr uint32_t vector_align[] = {
+      /* padding */ 0,
+      /* padding */ 0,
+      /*vec2*/ 8,
+      /*vec3*/ 16,
+      /*vec4*/ 16,
+  };
+
+  ty = ty->UnwrapAliasIfNeeded();
+  if (ty->is_scalar()) {
+    // Note: Also captures booleans, but these are not host-sharable.
+    align = 4;
+    size = 4;
+    return true;
+  } else if (auto* vec = ty->As<type::Vector>()) {
+    if (vec->size() < 2 || vec->size() > 4) {
+      TINT_UNREACHABLE(diagnostics_)
+          << "Invalid vector size: vec" << vec->size();
+      return false;
+    }
+    align = vector_align[vec->size()];
+    size = vector_size[vec->size()];
+    return true;
+  } else if (auto* mat = ty->As<type::Matrix>()) {
+    if (mat->columns() < 2 || mat->columns() > 4 || mat->rows() < 2 ||
+        mat->rows() > 4) {
+      TINT_UNREACHABLE(diagnostics_)
+          << "Invalid matrix size: mat" << mat->columns() << "x" << mat->rows();
+      return false;
+    }
+    align = vector_align[mat->rows()];
+    size = vector_align[mat->rows()] * mat->columns();
+    return true;
+  } else if (auto* s = ty->As<type::Struct>()) {
+    if (auto* sem = Structure(s)) {
+      align = sem->Align();
+      size = sem->Size();
+      return true;
+    }
+    return false;
+  } else if (auto* arr = ty->As<type::Array>()) {
+    if (auto* sem = Array(arr)) {
+      align = sem->Align();
+      size = sem->Size();
+      return true;
+    }
+    return false;
+  }
+  TINT_UNREACHABLE(diagnostics_) << "Invalid type " << ty->TypeInfo().name;
+  return false;
+}
+
+const semantic::Array* Resolver::Array(type::Array* arr) {
+  if (auto* sem = builder_->Sem().Get(arr)) {
+    // Semantic info already constructed for this array type
+    return sem;
+  }
+
+  // First check the element type is legal
+  auto* el_ty = arr->type();
+  if (!IsStorable(el_ty)) {
+    builder_->Diagnostics().add_error(
+        std::string(el_ty->FriendlyName(builder_->Symbols())) +
+        " cannot be used as an element type of an array");
+    return nullptr;
+  }
+
+  auto create_semantic = [&](uint32_t stride) -> semantic::Array* {
+    uint32_t el_align = 0;
+    uint32_t el_size = 0;
+    if (!DefaultAlignAndSize(arr->type(), el_align, el_size)) {
+      return nullptr;
+    }
+
+    auto align = el_align;
+    // WebGPU requires runtime arrays have at least one element, but the AST
+    // records an element count of 0 for it.
+    auto size = std::max<uint32_t>(arr->size(), 1) * stride;
+    auto* sem = builder_->create<semantic::Array>(arr, align, size, stride);
+    builder_->Sem().Add(arr, sem);
+    return sem;
+  };
+
+  // Look for explicit stride via [[stride(n)]] decoration
+  for (auto* deco : arr->decorations()) {
+    if (auto* stride = deco->As<ast::StrideDecoration>()) {
+      return create_semantic(stride->stride());
+    }
+  }
+
+  // Calculate implicit stride
+  uint32_t el_align = 0;
+  uint32_t el_size = 0;
+  if (!DefaultAlignAndSize(el_ty, el_align, el_size)) {
+    return nullptr;
+  }
+
+  return create_semantic(RoundUp(el_align, el_size));
+}
+
+const semantic::Struct* Resolver::Structure(type::Struct* str) {
+  if (auto* sem = builder_->Sem().Get(str)) {
+    // Semantic info already constructed for this structure type
+    return sem;
+  }
+
+  semantic::StructMemberList sem_members;
+  sem_members.reserve(str->impl()->members().size());
+
+  // Calculate the effective size and alignment of each field, and the overall
+  // size of the structure.
+  // For size, use the size attribute if provided, otherwise use the default
+  // size for the type.
+  // For alignment, use the alignment attribute if provided, otherwise use the
+  // default alignment for the member type.
+  // Diagnostic errors are raised if a basic rule is violated.
+  // Validation of storage-class rules requires analysing the actual variable
+  // usage of the structure, and so is performed as part of the variable
+  // validation.
+  // TODO(crbug.com/tint/628): Actually implement storage-class validation.
+  uint32_t struct_size = 0;
+  uint32_t struct_align = 1;
+
+  for (auto* member : str->impl()->members()) {
+    // First check the member type is legal
+    if (!IsStorable(member->type())) {
+      builder_->Diagnostics().add_error(
+          std::string(member->type()->FriendlyName(builder_->Symbols())) +
+          " cannot be used as the type of a structure member");
+      return nullptr;
+    }
+
+    uint32_t offset = struct_size;
+    uint32_t align = 0;
+    uint32_t size = 0;
+    if (!DefaultAlignAndSize(member->type(), align, size)) {
+      return nullptr;
+    }
+
+    for (auto* deco : member->decorations()) {
+      if (auto* o = deco->As<ast::StructMemberOffsetDecoration>()) {
+        // [DEPRECATED]
+        if (o->offset() < struct_size) {
+          diagnostics_.add_error("offsets must be in ascending order",
+                                 o->source());
+          return nullptr;
+        }
+        offset = o->offset();
+        align = 1;
+      } else if (auto* a = deco->As<ast::StructMemberAlignDecoration>()) {
+        if (a->align() <= 0 || !IsPowerOfTwo(a->align())) {
+          diagnostics_.add_error(
+              "align value must be a positive, power-of-two integer",
+              a->source());
+          return nullptr;
+        }
+        align = a->align();
+      } else if (auto* s = deco->As<ast::StructMemberSizeDecoration>()) {
+        if (s->size() < size) {
+          diagnostics_.add_error(
+              "size must be at least as big as the type's size (" +
+                  std::to_string(size) + ")",
+              s->source());
+          return nullptr;
+        }
+        size = s->size();
+      }
+    }
+
+    offset = RoundUp(align, offset);
+
+    auto* sem_member =
+        builder_->create<semantic::StructMember>(member, offset, size);
+    builder_->Sem().Add(member, sem_member);
+    sem_members.emplace_back(sem_member);
+
+    struct_size = offset + size;
+    struct_align = std::max(struct_align, align);
+  }
+
+  struct_size = RoundUp(struct_align, struct_size);
+
+  auto* sem = builder_->create<semantic::Struct>(str, std::move(sem_members),
+                                                 struct_align, struct_size);
+  builder_->Sem().Add(str, sem);
+  return sem;
+}
+
 template <typename F>
 bool Resolver::BlockScope(BlockInfo::Type type, F&& callback) {
   BlockInfo block_info(type, current_block_);
diff --git a/src/resolver/resolver.h b/src/resolver/resolver.h
index b2e5bae..485bff8 100644
--- a/src/resolver/resolver.h
+++ b/src/resolver/resolver.h
@@ -42,8 +42,12 @@
 class Variable;
 }  // namespace ast
 namespace semantic {
+class Array;
 class Statement;
 }  // namespace semantic
+namespace type {
+class Struct;
+}  // namespace type
 
 namespace resolver {
 
@@ -63,6 +67,10 @@
   /// @returns true if the resolver was successful
   bool Resolve();
 
+  /// @param type the given type
+  /// @returns true if the given type is storable.
+  static bool IsStorable(type::Type* type);
+
  private:
   /// Structure holding semantic information about a variable.
   /// Used to build the semantic::Variable nodes at the end of resolving.
@@ -141,41 +149,6 @@
   /// @returns true on success, false on error
   bool ResolveInternal();
 
-  /// Resolves functions
-  /// @param funcs the functions to check
-  /// @returns true on success, false on error
-  bool Functions(const ast::FunctionList& funcs);
-  /// Resolves a function. Requires all dependency
-  /// (callee) functions to have DetermineFunction() called on them first.
-  /// @param func the function to check
-  /// @returns true on success, false on error
-  bool Function(ast::Function* func);
-  /// Resolves a block statement
-  /// @param stmt the block statement
-  /// @returns true if determination was successful
-  bool BlockStatement(const ast::BlockStatement* stmt);
-  /// Resolves the list of statements
-  /// @param stmts the statements to resolve
-  /// @returns true on success, false on error
-  bool Statements(const ast::StatementList& stmts);
-  /// Resolves a statement
-  /// @param stmt the statement to check
-  /// @returns true on success, false on error
-  bool Statement(ast::Statement* stmt);
-  /// Resolves an expression list
-  /// @param list the expression list to check
-  /// @returns true on success, false on error
-  bool Expressions(const ast::ExpressionList& list);
-  /// Resolves an expression
-  /// @param expr the expression to check
-  /// @returns true on success, false on error
-  bool Expression(ast::Expression* expr);
-  /// Resolves the storage class for variables. This assumes that it is only
-  /// called for things in function scope, not module scope.
-  /// @param stmt the statement to check
-  /// @returns false on error
-  bool VariableStorageClass(ast::Statement* stmt);
-
   /// Creates the nodes and adds them to the semantic::Info mappings of the
   /// ProgramBuilder.
   void CreateSemanticNodes() const;
@@ -195,20 +168,43 @@
 
   void set_referenced_from_function_if_needed(VariableInfo* var, bool local);
 
-  bool ArrayAccessor(ast::ArrayAccessorExpression* expr);
-  bool Binary(ast::BinaryExpression* expr);
-  bool Bitcast(ast::BitcastExpression* expr);
-  bool Call(ast::CallExpression* expr);
-  bool CaseStatement(ast::CaseStatement* stmt);
-  bool Constructor(ast::ConstructorExpression* expr);
-  bool Identifier(ast::IdentifierExpression* expr);
-  bool IfStatement(ast::IfStatement* stmt);
-  bool IntrinsicCall(ast::CallExpression* call,
-                     semantic::IntrinsicType intrinsic_type);
-  bool MemberAccessor(ast::MemberAccessorExpression* expr);
-  bool UnaryOp(ast::UnaryOpExpression* expr);
+  // AST and Type traversal methods
+  // Each return true on success, false on failure.
+  bool ArrayAccessor(ast::ArrayAccessorExpression*);
+  bool Binary(ast::BinaryExpression*);
+  bool Bitcast(ast::BitcastExpression*);
+  bool BlockStatement(const ast::BlockStatement*);
+  bool Call(ast::CallExpression*);
+  bool CaseStatement(ast::CaseStatement*);
+  bool Constructor(ast::ConstructorExpression*);
+  bool Expression(ast::Expression*);
+  bool Expressions(const ast::ExpressionList&);
+  bool Function(ast::Function*);
+  bool Functions(const ast::FunctionList&);
+  bool Identifier(ast::IdentifierExpression*);
+  bool IfStatement(ast::IfStatement*);
+  bool IntrinsicCall(ast::CallExpression*, semantic::IntrinsicType);
+  bool MemberAccessor(ast::MemberAccessorExpression*);
+  bool Statement(ast::Statement*);
+  bool Statements(const ast::StatementList&);
+  bool UnaryOp(ast::UnaryOpExpression*);
+  bool VariableDeclStatement(const ast::VariableDeclStatement*);
+  bool VariableStorageClass(ast::Statement*);
 
-  bool VariableDeclStatement(const ast::VariableDeclStatement* stmt);
+  /// @returns the semantic information for the array `arr`, building it if it
+  /// hasn't been constructed already. If an error is raised, nullptr is
+  /// returned.
+  const semantic::Array* Array(type::Array*);
+
+  /// @returns the semantic information for the structure `str`, building it if
+  /// it hasn't been constructed already. If an error is raised, nullptr is
+  /// returned.
+  const semantic::Struct* Structure(type::Struct* str);
+
+  /// @param align the output default alignment in bytes for the type `ty`
+  /// @param size the output default size in bytes for the type `ty`
+  /// @returns true on success, false on error
+  bool DefaultAlignAndSize(type::Type* ty, uint32_t& align, uint32_t& size);
 
   VariableInfo* CreateVariableInfo(ast::Variable*);
 
diff --git a/src/resolver/struct_layout_test.cc b/src/resolver/struct_layout_test.cc
new file mode 100644
index 0000000..53dd37b
--- /dev/null
+++ b/src/resolver/struct_layout_test.cc
@@ -0,0 +1,333 @@
+// 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"
+
+namespace tint {
+namespace resolver {
+namespace {
+
+using ResolverStructLayoutTest = ResolverTest;
+
+TEST_F(ResolverStructLayoutTest, Scalars) {
+  auto* s = Structure("S", {
+                               Member("a", ty.f32()),
+                               Member("b", ty.u32()),
+                               Member("c", ty.i32()),
+                           });
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(s);
+  ASSERT_NE(sem, nullptr);
+  EXPECT_EQ(sem->Size(), 12u);
+  EXPECT_EQ(sem->Align(), 4u);
+  ASSERT_EQ(sem->Members().size(), 3u);
+  EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
+  EXPECT_EQ(sem->Members()[0]->Size(), 4u);
+  EXPECT_EQ(sem->Members()[1]->Offset(), 4u);
+  EXPECT_EQ(sem->Members()[1]->Size(), 4u);
+  EXPECT_EQ(sem->Members()[2]->Offset(), 8u);
+  EXPECT_EQ(sem->Members()[2]->Size(), 4u);
+}
+
+TEST_F(ResolverStructLayoutTest, Alias) {
+  auto* s = Structure("S", {
+                               Member("a", ty.alias("a", ty.f32())),
+                               Member("b", ty.alias("b", ty.f32())),
+                           });
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(s);
+  ASSERT_NE(sem, nullptr);
+  EXPECT_EQ(sem->Size(), 8u);
+  EXPECT_EQ(sem->Align(), 4u);
+  ASSERT_EQ(sem->Members().size(), 2u);
+  EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
+  EXPECT_EQ(sem->Members()[0]->Size(), 4u);
+  EXPECT_EQ(sem->Members()[1]->Offset(), 4u);
+  EXPECT_EQ(sem->Members()[1]->Size(), 4u);
+}
+
+TEST_F(ResolverStructLayoutTest, ImplicitStrideArrayStaticSize) {
+  auto* s = Structure("S", {
+                               Member("a", ty.array<i32, 3>()),
+                               Member("b", ty.array<f32, 5>()),
+                               Member("c", ty.array<f32, 1>()),
+                           });
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(s);
+  ASSERT_NE(sem, nullptr);
+  EXPECT_EQ(sem->Size(), 36u);
+  EXPECT_EQ(sem->Align(), 4u);
+  ASSERT_EQ(sem->Members().size(), 3u);
+  EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
+  EXPECT_EQ(sem->Members()[0]->Size(), 12u);
+  EXPECT_EQ(sem->Members()[1]->Offset(), 12u);
+  EXPECT_EQ(sem->Members()[1]->Size(), 20u);
+  EXPECT_EQ(sem->Members()[2]->Offset(), 32u);
+  EXPECT_EQ(sem->Members()[2]->Size(), 4u);
+}
+
+TEST_F(ResolverStructLayoutTest, ExplicitStrideArrayStaticSize) {
+  auto* s = Structure("S", {
+                               Member("a", ty.array<i32, 3>(/*stride*/ 8)),
+                               Member("b", ty.array<f32, 5>(/*stride*/ 16)),
+                               Member("c", ty.array<f32, 1>(/*stride*/ 32)),
+                           });
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(s);
+  ASSERT_NE(sem, nullptr);
+  EXPECT_EQ(sem->Size(), 136u);
+  EXPECT_EQ(sem->Align(), 4u);
+  ASSERT_EQ(sem->Members().size(), 3u);
+  EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
+  EXPECT_EQ(sem->Members()[0]->Size(), 24u);
+  EXPECT_EQ(sem->Members()[1]->Offset(), 24u);
+  EXPECT_EQ(sem->Members()[1]->Size(), 80u);
+  EXPECT_EQ(sem->Members()[2]->Offset(), 104u);
+  EXPECT_EQ(sem->Members()[2]->Size(), 32u);
+}
+
+TEST_F(ResolverStructLayoutTest, ImplicitStrideArrayRuntimeSized) {
+  auto* s = Structure("S", {
+                               Member("c", ty.array<f32>()),
+                           });
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(s);
+  ASSERT_NE(sem, nullptr);
+  EXPECT_EQ(sem->Size(), 4u);
+  EXPECT_EQ(sem->Align(), 4u);
+  ASSERT_EQ(sem->Members().size(), 1u);
+  EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
+  EXPECT_EQ(sem->Members()[0]->Size(), 4u);
+}
+
+TEST_F(ResolverStructLayoutTest, ExplicitStrideArrayRuntimeSized) {
+  auto* s = Structure("S", {
+                               Member("c", ty.array<f32>(/*stride*/ 32)),
+                           });
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(s);
+  ASSERT_NE(sem, nullptr);
+  EXPECT_EQ(sem->Size(), 32u);
+  EXPECT_EQ(sem->Align(), 4u);
+  ASSERT_EQ(sem->Members().size(), 1u);
+  EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
+  EXPECT_EQ(sem->Members()[0]->Size(), 32u);
+}
+
+TEST_F(ResolverStructLayoutTest, ImplicitStrideArrayOfExplicitStrideArray) {
+  auto* inner = ty.array<i32, 2>(/*stride*/ 16);  // size: 32
+  auto* outer = ty.array(inner, 12);              // size: 12 * 32
+  auto* s = Structure("S", {
+                               Member("c", outer),
+                           });
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(s);
+  ASSERT_NE(sem, nullptr);
+  EXPECT_EQ(sem->Size(), 384u);
+  EXPECT_EQ(sem->Align(), 4u);
+  ASSERT_EQ(sem->Members().size(), 1u);
+  EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
+  EXPECT_EQ(sem->Members()[0]->Size(), 384u);
+}
+
+TEST_F(ResolverStructLayoutTest, ImplicitStrideArrayOfStructure) {
+  auto* inner = Structure("Inner", {
+                                       Member("a", ty.vec2<i32>()),
+                                       Member("b", ty.vec3<i32>()),
+                                       Member("c", ty.vec4<i32>()),
+                                   });  // size: 48
+  auto* outer = ty.array(inner, 12);    // size: 12 * 48
+  auto* s = Structure("S", {
+                               Member("c", outer),
+                           });
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(s);
+  ASSERT_NE(sem, nullptr);
+  EXPECT_EQ(sem->Size(), 576u);
+  EXPECT_EQ(sem->Align(), 16u);
+  ASSERT_EQ(sem->Members().size(), 1u);
+  EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
+  EXPECT_EQ(sem->Members()[0]->Size(), 576u);
+}
+
+TEST_F(ResolverStructLayoutTest, Vector) {
+  auto* s = Structure("S", {
+                               Member("a", ty.vec2<i32>()),
+                               Member("b", ty.vec3<i32>()),
+                               Member("c", ty.vec4<i32>()),
+                           });
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(s);
+  ASSERT_NE(sem, nullptr);
+  EXPECT_EQ(sem->Size(), 48u);
+  EXPECT_EQ(sem->Align(), 16u);
+  ASSERT_EQ(sem->Members().size(), 3u);
+  EXPECT_EQ(sem->Members()[0]->Offset(), 0u);  // vec2
+  EXPECT_EQ(sem->Members()[0]->Size(), 8u);
+  EXPECT_EQ(sem->Members()[1]->Offset(), 16u);  // vec3
+  EXPECT_EQ(sem->Members()[1]->Size(), 12u);
+  EXPECT_EQ(sem->Members()[2]->Offset(), 32u);  // vec4
+  EXPECT_EQ(sem->Members()[2]->Size(), 16u);
+}
+
+TEST_F(ResolverStructLayoutTest, Matrix) {
+  auto* s = Structure("S", {
+                               Member("a", ty.mat2x2<i32>()),
+                               Member("b", ty.mat2x3<i32>()),
+                               Member("c", ty.mat2x4<i32>()),
+                               Member("d", ty.mat3x2<i32>()),
+                               Member("e", ty.mat3x3<i32>()),
+                               Member("f", ty.mat3x4<i32>()),
+                               Member("g", ty.mat4x2<i32>()),
+                               Member("h", ty.mat4x3<i32>()),
+                               Member("i", ty.mat4x4<i32>()),
+                           });
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(s);
+  ASSERT_NE(sem, nullptr);
+  EXPECT_EQ(sem->Size(), 368u);
+  EXPECT_EQ(sem->Align(), 16u);
+  ASSERT_EQ(sem->Members().size(), 9u);
+  EXPECT_EQ(sem->Members()[0]->Offset(), 0u);  // mat2x2
+  EXPECT_EQ(sem->Members()[0]->Size(), 16u);
+  EXPECT_EQ(sem->Members()[1]->Offset(), 16u);  // mat2x3
+  EXPECT_EQ(sem->Members()[1]->Size(), 32u);
+  EXPECT_EQ(sem->Members()[2]->Offset(), 48u);  // mat2x4
+  EXPECT_EQ(sem->Members()[2]->Size(), 32u);
+  EXPECT_EQ(sem->Members()[3]->Offset(), 80u);  // mat3x2
+  EXPECT_EQ(sem->Members()[3]->Size(), 24u);
+  EXPECT_EQ(sem->Members()[4]->Offset(), 112u);  // mat3x3
+  EXPECT_EQ(sem->Members()[4]->Size(), 48u);
+  EXPECT_EQ(sem->Members()[5]->Offset(), 160u);  // mat3x4
+  EXPECT_EQ(sem->Members()[5]->Size(), 48u);
+  EXPECT_EQ(sem->Members()[6]->Offset(), 208u);  // mat4x2
+  EXPECT_EQ(sem->Members()[6]->Size(), 32u);
+  EXPECT_EQ(sem->Members()[7]->Offset(), 240u);  // mat4x3
+  EXPECT_EQ(sem->Members()[7]->Size(), 64u);
+  EXPECT_EQ(sem->Members()[8]->Offset(), 304u);  // mat4x4
+  EXPECT_EQ(sem->Members()[8]->Size(), 64u);
+}
+
+TEST_F(ResolverStructLayoutTest, NestedStruct) {
+  auto* inner = Structure("Inner", {
+                                       Member("a", ty.mat3x3<i32>()),
+                                   });
+  auto* s = Structure("S", {
+                               Member("a", ty.i32()),
+                               Member("b", inner),
+                               Member("c", ty.i32()),
+                           });
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(s);
+  ASSERT_NE(sem, nullptr);
+  EXPECT_EQ(sem->Size(), 80u);
+  EXPECT_EQ(sem->Align(), 16u);
+  ASSERT_EQ(sem->Members().size(), 3u);
+  EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
+  EXPECT_EQ(sem->Members()[0]->Size(), 4u);
+  EXPECT_EQ(sem->Members()[1]->Offset(), 16u);
+  EXPECT_EQ(sem->Members()[1]->Size(), 48u);
+  EXPECT_EQ(sem->Members()[2]->Offset(), 64u);
+  EXPECT_EQ(sem->Members()[2]->Size(), 4u);
+}
+
+TEST_F(ResolverStructLayoutTest, SizeDecorations) {
+  auto* inner = Structure("Inner", {
+                                       Member("a", ty.f32(), {MemberSize(8)}),
+                                       Member("b", ty.f32(), {MemberSize(16)}),
+                                       Member("c", ty.f32(), {MemberSize(8)}),
+                                   });
+  auto* s = Structure("S", {
+                               Member("a", ty.f32(), {MemberSize(4)}),
+                               Member("b", ty.u32(), {MemberSize(8)}),
+                               Member("c", inner),
+                               Member("d", ty.i32(), {MemberSize(32)}),
+                           });
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(s);
+  ASSERT_NE(sem, nullptr);
+  EXPECT_EQ(sem->Size(), 76u);
+  EXPECT_EQ(sem->Align(), 4u);
+  ASSERT_EQ(sem->Members().size(), 4u);
+  EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
+  EXPECT_EQ(sem->Members()[0]->Size(), 4u);
+  EXPECT_EQ(sem->Members()[1]->Offset(), 4u);
+  EXPECT_EQ(sem->Members()[1]->Size(), 8u);
+  EXPECT_EQ(sem->Members()[2]->Offset(), 12u);
+  EXPECT_EQ(sem->Members()[2]->Size(), 32u);
+  EXPECT_EQ(sem->Members()[3]->Offset(), 44u);
+  EXPECT_EQ(sem->Members()[3]->Size(), 32u);
+}
+
+TEST_F(ResolverStructLayoutTest, AlignDecorations) {
+  auto* inner = Structure("Inner", {
+                                       Member("a", ty.f32(), {MemberAlign(8)}),
+                                       Member("b", ty.f32(), {MemberAlign(16)}),
+                                       Member("c", ty.f32(), {MemberAlign(4)}),
+                                   });
+  auto* s = Structure("S", {
+                               Member("a", ty.f32(), {MemberAlign(4)}),
+                               Member("b", ty.u32(), {MemberAlign(8)}),
+                               Member("c", inner),
+                               Member("d", ty.i32(), {MemberAlign(32)}),
+                           });
+
+  ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+  auto* sem = Sem().Get(s);
+  ASSERT_NE(sem, nullptr);
+  EXPECT_EQ(sem->Size(), 96u);
+  EXPECT_EQ(sem->Align(), 32u);
+  ASSERT_EQ(sem->Members().size(), 4u);
+  EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
+  EXPECT_EQ(sem->Members()[0]->Size(), 4u);
+  EXPECT_EQ(sem->Members()[1]->Offset(), 8u);
+  EXPECT_EQ(sem->Members()[1]->Size(), 4u);
+  EXPECT_EQ(sem->Members()[2]->Offset(), 16u);
+  EXPECT_EQ(sem->Members()[2]->Size(), 32u);
+  EXPECT_EQ(sem->Members()[3]->Offset(), 64u);
+  EXPECT_EQ(sem->Members()[3]->Size(), 4u);
+}
+
+}  // namespace
+}  // namespace resolver
+}  // namespace tint
diff --git a/src/resolver/validation_test.cc b/src/resolver/validation_test.cc
index 87c111e..f3d3cbd 100644
--- a/src/resolver/validation_test.cc
+++ b/src/resolver/validation_test.cc
@@ -556,6 +556,38 @@
             "12:34 error: break statement must be in a loop or switch case");
 }
 
+TEST_F(ResolverValidationTest, NonPOTStructMemberAlignDecoration) {
+  Structure("S", {
+                     Member("a", ty.f32(), {MemberAlign(Source{{12, 34}}, 3)}),
+                 });
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: align value must be a positive, power-of-two integer");
+}
+
+TEST_F(ResolverValidationTest, ZeroStructMemberAlignDecoration) {
+  Structure("S", {
+                     Member("a", ty.f32(), {MemberAlign(Source{{12, 34}}, 0)}),
+                 });
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: align value must be a positive, power-of-two integer");
+}
+
+TEST_F(ResolverValidationTest, ZeroStructMemberSizeDecoration) {
+  Structure("S", {
+                     Member("a", ty.f32(), {MemberSize(Source{{12, 34}}, 0)}),
+                 });
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: size must be at least as big as the type's size (4)");
+}
+
 }  // namespace
 }  // namespace resolver
 }  // namespace tint
diff --git a/src/semantic/array.h b/src/semantic/array.h
new file mode 100644
index 0000000..c620d49
--- /dev/null
+++ b/src/semantic/array.h
@@ -0,0 +1,69 @@
+// 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.
+
+#ifndef SRC_SEMANTIC_ARRAY_H_
+#define SRC_SEMANTIC_ARRAY_H_
+
+#include <stdint.h>
+
+#include "src/semantic/node.h"
+
+namespace tint {
+
+// Forward declarations
+namespace type {
+class Array;
+}  // namespace type
+
+namespace semantic {
+
+/// Array holds the semantic information for Array nodes.
+class Array : public Castable<Array, Node> {
+ public:
+  /// Constructor
+  /// @param type the Array type
+  /// @param align the byte alignment of the structure
+  /// @param size the byte size of the structure
+  /// @param stride the number of bytes from the start of one element of the
+  /// array to the start of the next element
+  Array(type::Array* type, uint32_t align, uint32_t size, uint32_t stride);
+
+  /// @return the resolved type of the Array
+  type::Array* Type() const { return type_; }
+
+  /// @returns the byte alignment of the array
+  /// @note this may differ from the alignment of a structure member of this
+  /// array type, if the member is annotated with the `[[align(n)]]` decoration.
+  uint32_t Align() const { return align_; }
+
+  /// @returns the byte size of the array
+  /// @note this may differ from the size of a structure member of this array
+  /// type, if the member is annotated with the `[[size(n)]]` decoration.
+  uint32_t Size() const { return size_; }
+
+  /// @returns the number of bytes from the start of one element of the
+  /// array to the start of the next element
+  uint32_t Stride() const { return stride_; }
+
+ private:
+  type::Array* const type_;
+  uint32_t const align_;
+  uint32_t const size_;
+  uint32_t const stride_;
+};
+
+}  // namespace semantic
+}  // namespace tint
+
+#endif  // SRC_SEMANTIC_ARRAY_H_
diff --git a/src/semantic/call.h b/src/semantic/call.h
index 954585c..8ea4dc1 100644
--- a/src/semantic/call.h
+++ b/src/semantic/call.h
@@ -29,9 +29,9 @@
   /// @param declaration the AST node
   /// @param target the call target
   /// @param statement the statement that owns this expression
-  explicit Call(ast::Expression* declaration,
-                const CallTarget* target,
-                Statement* statement);
+  Call(ast::Expression* declaration,
+       const CallTarget* target,
+       Statement* statement);
 
   /// Destructor
   ~Call() override;
diff --git a/src/semantic/info.h b/src/semantic/info.h
index 6e478ff..ec14cee 100644
--- a/src/semantic/info.h
+++ b/src/semantic/info.h
@@ -23,12 +23,6 @@
 #include "src/semantic/type_mappings.h"
 
 namespace tint {
-
-// Forward declarations
-namespace ast {
-class Node;
-}  // namespace ast
-
 namespace semantic {
 
 /// Info holds all the resolved semantic information for a Program.
@@ -48,26 +42,29 @@
   /// @return this Program
   Info& operator=(Info&& rhs);
 
-  /// Get looks up the semantic information for the AST node `ast_node`.
-  /// @param ast_node the AST node
+  /// Get looks up the semantic information for the AST or type node `node`.
+  /// @param node the AST or type node
   /// @returns a pointer to the semantic node if found, otherwise nullptr
-  template <typename AST, typename SEM = SemanticNodeTypeFor<AST>>
-  const SEM* Get(const AST* ast_node) const {
-    auto it = ast_to_sem_.find(ast_node);
-    if (it == ast_to_sem_.end()) {
+  template <typename AST_OR_TYPE,
+            typename SEM = SemanticNodeTypeFor<AST_OR_TYPE>>
+  const SEM* Get(const AST_OR_TYPE* node) const {
+    auto it = map.find(node);
+    if (it == map.end()) {
       return nullptr;
     }
     return it->second->template As<SEM>();
   }
 
-  /// Add registers the semantic node `sem_node` for the AST node `ast_node`.
-  /// @param ast_node the AST node
+  /// Add registers the semantic node `sem_node` for the AST or type node
+  /// `node`.
+  /// @param node the AST or type node
   /// @param sem_node the semantic node
-  template <typename AST>
-  void Add(const AST* ast_node, const SemanticNodeTypeFor<AST>* sem_node) {
+  template <typename AST_OR_TYPE>
+  void Add(const AST_OR_TYPE* node,
+           const SemanticNodeTypeFor<AST_OR_TYPE>* sem_node) {
     // Check there's no semantic info already existing for the node
-    assert(Get(ast_node) == nullptr);
-    ast_to_sem_.emplace(ast_node, sem_node);
+    assert(Get(node) == nullptr);
+    map.emplace(node, sem_node);
   }
 
   /// Wrap returns a new Info created with the contents of `inner`.
@@ -79,12 +76,12 @@
   /// @return the Info that wraps `inner`
   static Info Wrap(const Info& inner) {
     Info out;
-    out.ast_to_sem_ = inner.ast_to_sem_;
+    out.map = inner.map;
     return out;
   }
 
  private:
-  std::unordered_map<const ast::Node*, const semantic::Node*> ast_to_sem_;
+  std::unordered_map<const CastableBase*, const semantic::Node*> map;
 };
 
 }  // namespace semantic
diff --git a/src/semantic/sem_array.cc b/src/semantic/sem_array.cc
new file mode 100644
index 0000000..c54a3fc
--- /dev/null
+++ b/src/semantic/sem_array.cc
@@ -0,0 +1,26 @@
+// 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/semantic/array.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::semantic::Array);
+
+namespace tint {
+namespace semantic {
+
+Array::Array(type::Array* type, uint32_t align, uint32_t size, uint32_t stride)
+    : type_(type), align_(align), size_(size), stride_(stride) {}
+
+}  // namespace semantic
+}  // namespace tint
diff --git a/src/semantic/sem_struct.cc b/src/semantic/sem_struct.cc
new file mode 100644
index 0000000..0d3b971
--- /dev/null
+++ b/src/semantic/sem_struct.cc
@@ -0,0 +1,39 @@
+// 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/semantic/struct.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::semantic::Struct);
+TINT_INSTANTIATE_TYPEINFO(tint::semantic::StructMember);
+
+namespace tint {
+namespace semantic {
+
+Struct::Struct(type::Struct* type,
+               StructMemberList members,
+               uint32_t align,
+               uint32_t size)
+    : type_(type), members_(std::move(members)), align_(align), size_(size) {}
+
+Struct::~Struct() = default;
+
+StructMember::StructMember(ast::StructMember* declaration,
+                           uint32_t offset,
+                           uint32_t size)
+    : declaration_(declaration), offset_(offset), size_(size) {}
+
+StructMember::~StructMember() = default;
+
+}  // namespace semantic
+}  // namespace tint
diff --git a/src/semantic/struct.h b/src/semantic/struct.h
new file mode 100644
index 0000000..3d99655
--- /dev/null
+++ b/src/semantic/struct.h
@@ -0,0 +1,112 @@
+// 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.
+
+#ifndef SRC_SEMANTIC_STRUCT_H_
+#define SRC_SEMANTIC_STRUCT_H_
+
+#include <stdint.h>
+
+#include <vector>
+
+#include "src/semantic/node.h"
+
+namespace tint {
+
+// Forward declarations
+namespace ast {
+class StructMember;
+}  // namespace ast
+namespace type {
+class Struct;
+}  // namespace type
+
+namespace semantic {
+
+class StructMember;
+
+/// A vector of StructMember pointers.
+using StructMemberList = std::vector<StructMember*>;
+
+/// Struct holds the semantic information for structures.
+class Struct : public Castable<Struct, Node> {
+ public:
+  /// Constructor
+  /// @param type the structure type
+  /// @param members the structure members
+  /// @param align the byte alignment of the structure
+  /// @param size the byte size of the structure
+  Struct(type::Struct* type,
+         StructMemberList members,
+         uint32_t align,
+         uint32_t size);
+
+  /// Destructor
+  ~Struct() override;
+
+  /// @returns the structure type
+  type::Struct* Type() const { return type_; }
+
+  /// @returns the members of the structure
+  const StructMemberList& Members() const { return members_; }
+
+  /// @returns the byte alignment of the structure
+  /// @note this may differ from the alignment of a structure member of this
+  /// structure type, if the member is annotated with the `[[align(n)]]`
+  /// decoration.
+  uint32_t Align() const { return align_; }
+
+  /// @returns the byte size of the structure
+  /// @note this may differ from the size of a structure member of this
+  /// structure type, if the member is annotated with the `[[size(n)]]`
+  /// decoration.
+  uint32_t Size() const { return size_; }
+
+ private:
+  type::Struct* const type_;
+  StructMemberList const members_;
+  uint32_t const align_;
+  uint32_t const size_;
+};
+
+/// StructMember holds the semantic information for structure members.
+class StructMember : public Castable<StructMember, Node> {
+ public:
+  /// Constructor
+  /// @param declaration the AST declaration node
+  /// @param offset the byte offset from the base of the structure
+  /// @param size the byte size
+  StructMember(ast::StructMember* declaration, uint32_t offset, uint32_t size);
+
+  /// Destructor
+  ~StructMember() override;
+
+  /// @returns the AST declaration node
+  ast::StructMember* Declaration() const { return declaration_; }
+
+  /// @returns byte offset from base of structure
+  uint32_t Offset() const { return offset_; }
+
+  /// @returns byte size
+  uint32_t Size() const { return size_; }
+
+ private:
+  ast::StructMember* const declaration_;
+  uint32_t const offset_;  // Byte offset from base of structure
+  uint32_t const size_;    // Byte size
+};
+
+}  // namespace semantic
+}  // namespace tint
+
+#endif  // SRC_SEMANTIC_STRUCT_H_
diff --git a/src/semantic/type_mappings.h b/src/semantic/type_mappings.h
index 6b1c331..d0b48d8 100644
--- a/src/semantic/type_mappings.h
+++ b/src/semantic/type_mappings.h
@@ -19,44 +19,52 @@
 
 // Forward declarations
 namespace ast {
-
 class CallExpression;
 class Expression;
 class Function;
 class MemberAccessorExpression;
+class StructMember;
 class Variable;
-
 }  // namespace ast
+namespace type {
+class Array;
+class Struct;
+}  // namespace type
 
 namespace semantic {
 
+// Forward declarations
+class Array;
 class Call;
 class Expression;
 class Function;
 class MemberAccessorExpression;
+class Struct;
+class StructMember;
 class Variable;
 
 /// TypeMappings is a struct that holds dummy `operator()` methods that's used
-/// by SemanticNodeTypeFor to map AST node types to their corresponding semantic
-/// node types.
-/// The standard operator overload resolving rules will be used to infer the
-/// return type based on the argument type.
+/// by SemanticNodeTypeFor to map AST / type node types to their corresponding
+/// semantic node types. The standard operator overload resolving rules will be
+/// used to infer the return type based on the argument type.
 struct TypeMappings {
   //! @cond Doxygen_Suppress
-  semantic::Expression* operator()(ast::Expression*);
-  semantic::Function* operator()(ast::Function*);
-  semantic::Variable* operator()(ast::Variable*);
-  semantic::Call* operator()(ast::CallExpression*);
-  semantic::MemberAccessorExpression* operator()(
-      ast::MemberAccessorExpression*);
+  Array* operator()(type::Array*);
+  Call* operator()(ast::CallExpression*);
+  Expression* operator()(ast::Expression*);
+  Function* operator()(ast::Function*);
+  MemberAccessorExpression* operator()(ast::MemberAccessorExpression*);
+  Struct* operator()(type::Struct*);
+  StructMember* operator()(ast::StructMember*);
+  Variable* operator()(ast::Variable*);
   //! @endcond
 };
 
 /// SemanticNodeTypeFor resolves to the appropriate semantic::Node type for the
-/// AST node type `AST`.
-template <typename AST>
+/// AST or type node `AST_OR_TYPE`.
+template <typename AST_OR_TYPE>
 using SemanticNodeTypeFor = typename std::remove_pointer<decltype(
-    TypeMappings()(std::declval<AST*>()))>::type;
+    TypeMappings()(std::declval<AST_OR_TYPE*>()))>::type;
 
 }  // namespace semantic
 }  // namespace tint
diff --git a/src/transform/first_index_offset.cc b/src/transform/first_index_offset.cc
index 61ab725..6acabe0 100644
--- a/src/transform/first_index_offset.cc
+++ b/src/transform/first_index_offset.cc
@@ -154,26 +154,20 @@
 
 ast::Variable* FirstIndexOffset::State::AddUniformBuffer() {
   auto* u32_type = dst->create<type::U32>();
-  ast::StructMemberList members;
   uint32_t offset = 0;
+  ast::StructMemberList members;
   if (has_vertex_index) {
-    ast::DecorationList member_dec;
-    member_dec.push_back(
-        dst->create<ast::StructMemberOffsetDecoration>(Source{}, offset));
     members.push_back(dst->create<ast::StructMember>(
         Source{}, dst->Symbols().Register(kFirstVertexName), u32_type,
-        std::move(member_dec)));
+        ast::DecorationList{}));
     vertex_index_offset = offset;
     offset += 4;
   }
 
   if (has_instance_index) {
-    ast::DecorationList member_dec;
-    member_dec.push_back(
-        dst->create<ast::StructMemberOffsetDecoration>(Source{}, offset));
     members.push_back(dst->create<ast::StructMember>(
         Source{}, dst->Symbols().Register(kFirstInstanceName), u32_type,
-        std::move(member_dec)));
+        ast::DecorationList{}));
     instance_index_offset = offset;
     offset += 4;
   }
diff --git a/src/transform/first_index_offset.h b/src/transform/first_index_offset.h
index f2d04ec..dd187d2 100644
--- a/src/transform/first_index_offset.h
+++ b/src/transform/first_index_offset.h
@@ -46,8 +46,8 @@
 /// After:
 ///   [[block]]
 ///   struct TintFirstIndexOffsetData {
-///     [[offset(0)]] tint_first_vertex_index : u32;
-///     [[offset(4)]] tint_first_instance_index : u32;
+///     tint_first_vertex_index : u32;
+///     tint_first_instance_index : u32;
 ///   };
 ///   [[builtin(vertex_index)]] var<in> tint_first_index_offset_vert_idx : u32;
 ///   [[binding(N), group(M)]] var<uniform> tint_first_index_data :
diff --git a/src/transform/first_index_offset_test.cc b/src/transform/first_index_offset_test.cc
index 4618915..3370cf4 100644
--- a/src/transform/first_index_offset_test.cc
+++ b/src/transform/first_index_offset_test.cc
@@ -98,7 +98,6 @@
 
 [[block]]
 struct TintFirstIndexOffsetData {
-  [[offset(0)]]
   tint_first_vertex_index : u32;
 };
 
@@ -147,7 +146,6 @@
 
 [[block]]
 struct TintFirstIndexOffsetData {
-  [[offset(0)]]
   tint_first_instance_index : u32;
 };
 
@@ -199,9 +197,7 @@
 
 [[block]]
 struct TintFirstIndexOffsetData {
-  [[offset(0)]]
   tint_first_vertex_index : u32;
-  [[offset(4)]]
   tint_first_instance_index : u32;
 };
 
@@ -255,7 +251,6 @@
 
 [[block]]
 struct TintFirstIndexOffsetData {
-  [[offset(0)]]
   tint_first_vertex_index : u32;
 };
 
diff --git a/src/transform/hlsl_test.cc b/src/transform/hlsl_test.cc
index 57340f1..a24f68b 100644
--- a/src/transform/hlsl_test.cc
+++ b/src/transform/hlsl_test.cc
@@ -96,7 +96,6 @@
   auto* src = R"(
 [[block]]
 struct Uniforms {
-  [[offset(0)]]
   transform : mat2x2<f32>;
 };
 
@@ -121,7 +120,6 @@
   auto* expect = R"(
 [[block]]
 struct Uniforms {
-  [[offset(0)]]
   transform : mat2x2<f32>;
 };
 
diff --git a/src/transform/msl.h b/src/transform/msl.h
index 7b5e16c..38a2cd1 100644
--- a/src/transform/msl.h
+++ b/src/transform/msl.h
@@ -34,6 +34,7 @@
   /// @returns the transformation result
   Output Run(const Program* program) override;
 
+ private:
   /// Hoist location-decorated entry point parameters out to struct members.
   void HandleEntryPointIOTypes(CloneContext& ctx) const;
 };
diff --git a/src/transform/vertex_pulling.cc b/src/transform/vertex_pulling.cc
index b9d6525..b82a4d5 100644
--- a/src/transform/vertex_pulling.cc
+++ b/src/transform/vertex_pulling.cc
@@ -230,13 +230,9 @@
 
   // Creating the struct type
   ast::StructMemberList members;
-  ast::DecorationList member_dec;
-  member_dec.push_back(
-      ctx.dst->create<ast::StructMemberOffsetDecoration>(Source{}, 0u));
-
   members.push_back(ctx.dst->create<ast::StructMember>(
       Source{}, ctx.dst->Symbols().Register(kStructBufferName),
-      internal_array_type, std::move(member_dec)));
+      internal_array_type, ast::DecorationList{}));
 
   ast::DecorationList decos;
   decos.push_back(ctx.dst->create<ast::StructBlockDecoration>(Source{}));
diff --git a/src/transform/vertex_pulling_test.cc b/src/transform/vertex_pulling_test.cc
index 6c80e16..b4e1080 100644
--- a/src/transform/vertex_pulling_test.cc
+++ b/src/transform/vertex_pulling_test.cc
@@ -83,7 +83,6 @@
   auto* expect = R"(
 [[block]]
 struct TintVertexData {
-  [[offset(0)]]
   _tint_vertex_data : [[stride(4)]] array<u32>;
 };
 
@@ -120,7 +119,6 @@
 
 [[block]]
 struct TintVertexData {
-  [[offset(0)]]
   _tint_vertex_data : [[stride(4)]] array<u32>;
 };
 
@@ -163,7 +161,6 @@
 
 [[block]]
 struct TintVertexData {
-  [[offset(0)]]
   _tint_vertex_data : [[stride(4)]] array<u32>;
 };
 
@@ -206,7 +203,6 @@
 
 [[block]]
 struct TintVertexData {
-  [[offset(0)]]
   _tint_vertex_data : [[stride(4)]] array<u32>;
 };
 
@@ -254,7 +250,6 @@
 
 [[block]]
 struct TintVertexData {
-  [[offset(0)]]
   _tint_vertex_data : [[stride(4)]] array<u32>;
 };
 
@@ -316,7 +311,6 @@
 
 [[block]]
 struct TintVertexData {
-  [[offset(0)]]
   _tint_vertex_data : [[stride(4)]] array<u32>;
 };
 
@@ -371,7 +365,6 @@
 
 [[block]]
 struct TintVertexData {
-  [[offset(0)]]
   _tint_vertex_data : [[stride(4)]] array<u32>;
 };
 
diff --git a/src/type/access_control_type.cc b/src/type/access_control_type.cc
index 43c7924..4ecefe3 100644
--- a/src/type/access_control_type.cc
+++ b/src/type/access_control_type.cc
@@ -65,14 +65,6 @@
   return out.str();
 }
 
-uint64_t AccessControl::MinBufferBindingSize(MemoryLayout mem_layout) const {
-  return subtype_->MinBufferBindingSize(mem_layout);
-}
-
-uint64_t AccessControl::BaseAlignment(MemoryLayout mem_layout) const {
-  return subtype_->BaseAlignment(mem_layout);
-}
-
 AccessControl* AccessControl::Clone(CloneContext* ctx) const {
   // Clone arguments outside of create() call to have deterministic ordering
   auto* ty = ctx->Clone(type());
diff --git a/src/type/access_control_type.h b/src/type/access_control_type.h
index 7dce8bd..7002ea4 100644
--- a/src/type/access_control_type.h
+++ b/src/type/access_control_type.h
@@ -54,16 +54,6 @@
   /// declared in WGSL.
   std::string FriendlyName(const SymbolTable& symbols) const override;
 
-  /// @param mem_layout type of memory layout to use in calculation.
-  /// @returns minimum size required for this type, in bytes.
-  ///          0 for non-host shareable types.
-  uint64_t MinBufferBindingSize(MemoryLayout mem_layout) const override;
-
-  /// @param mem_layout type of memory layout to use in calculation.
-  /// @returns base alignment for the type, in bytes.
-  ///          0 for non-host shareable types.
-  uint64_t BaseAlignment(MemoryLayout mem_layout) const override;
-
   /// Clones this type and all transitive types using the `CloneContext` `ctx`.
   /// @param ctx the clone context
   /// @return the newly cloned type
diff --git a/src/type/access_control_type_test.cc b/src/type/access_control_type_test.cc
index 2256345..e390400 100644
--- a/src/type/access_control_type_test.cc
+++ b/src/type/access_control_type_test.cc
@@ -95,70 +95,6 @@
   EXPECT_EQ(at.FriendlyName(Symbols()), "[[access(read_write)]] i32");
 }
 
-TEST_F(AccessControlTest, MinBufferBindingSizeU32) {
-  U32 u32;
-  AccessControl at{ast::AccessControl::kReadOnly, &u32};
-  EXPECT_EQ(4u, at.MinBufferBindingSize(MemoryLayout::kUniformBuffer));
-}
-
-TEST_F(AccessControlTest, MinBufferBindingSizeArray) {
-  U32 u32;
-  Array array(&u32, 4, ast::DecorationList{create<ast::StrideDecoration>(4)});
-  AccessControl at{ast::AccessControl::kReadOnly, &array};
-  EXPECT_EQ(16u, at.MinBufferBindingSize(MemoryLayout::kUniformBuffer));
-}
-
-TEST_F(AccessControlTest, MinBufferBindingSizeRuntimeArray) {
-  U32 u32;
-  Array array(&u32, 0, ast::DecorationList{create<ast::StrideDecoration>(4)});
-  AccessControl at{ast::AccessControl::kReadOnly, &array};
-  EXPECT_EQ(4u, at.MinBufferBindingSize(MemoryLayout::kUniformBuffer));
-}
-
-TEST_F(AccessControlTest, MinBufferBindingSizeStruct) {
-  auto* str = create<ast::Struct>(
-      ast::StructMemberList{Member("foo", ty.u32(), {MemberOffset(0)}),
-                            Member("bar", ty.u32(), {MemberOffset(4)})},
-      ast::DecorationList{});
-
-  auto* struct_type = ty.struct_("struct_type", str);
-  AccessControl at{ast::AccessControl::kReadOnly, struct_type};
-  EXPECT_EQ(16u, at.MinBufferBindingSize(MemoryLayout::kUniformBuffer));
-  EXPECT_EQ(8u, at.MinBufferBindingSize(MemoryLayout::kStorageBuffer));
-}
-
-TEST_F(AccessControlTest, BaseAlignmentU32) {
-  U32 u32;
-  AccessControl at{ast::AccessControl::kReadOnly, &u32};
-  EXPECT_EQ(4u, at.BaseAlignment(MemoryLayout::kUniformBuffer));
-}
-
-TEST_F(AccessControlTest, BaseAlignmentArray) {
-  U32 u32;
-  Array array(&u32, 4, ast::DecorationList{create<ast::StrideDecoration>(4)});
-  AccessControl at{ast::AccessControl::kReadOnly, &array};
-  EXPECT_EQ(16u, at.BaseAlignment(MemoryLayout::kUniformBuffer));
-}
-
-TEST_F(AccessControlTest, BaseAlignmentRuntimeArray) {
-  U32 u32;
-  Array array(&u32, 0, ast::DecorationList{create<ast::StrideDecoration>(4)});
-  AccessControl at{ast::AccessControl::kReadOnly, &array};
-  EXPECT_EQ(16u, at.BaseAlignment(MemoryLayout::kUniformBuffer));
-}
-
-TEST_F(AccessControlTest, BaseAlignmentStruct) {
-  auto* str = create<ast::Struct>(
-      ast::StructMemberList{Member("foo", ty.u32(), {MemberOffset(0)}),
-                            Member("bar", ty.u32(), {MemberOffset(4)})},
-      ast::DecorationList{});
-  auto* struct_type = ty.struct_("struct_type", str);
-
-  AccessControl at{ast::AccessControl::kReadOnly, struct_type};
-  EXPECT_EQ(16u, at.BaseAlignment(MemoryLayout::kUniformBuffer));
-  EXPECT_EQ(4u, at.BaseAlignment(MemoryLayout::kStorageBuffer));
-}
-
 }  // namespace
 }  // namespace type
 }  // namespace tint
diff --git a/src/type/alias_type.cc b/src/type/alias_type.cc
index f5e2d2a..b155cc2 100644
--- a/src/type/alias_type.cc
+++ b/src/type/alias_type.cc
@@ -38,14 +38,6 @@
   return symbols.NameFor(symbol_);
 }
 
-uint64_t Alias::MinBufferBindingSize(MemoryLayout mem_layout) const {
-  return subtype_->MinBufferBindingSize(mem_layout);
-}
-
-uint64_t Alias::BaseAlignment(MemoryLayout mem_layout) const {
-  return subtype_->BaseAlignment(mem_layout);
-}
-
 Alias* Alias::Clone(CloneContext* ctx) const {
   // Clone arguments outside of create() call to have deterministic ordering
   auto sym = ctx->Clone(symbol());
diff --git a/src/type/alias_type.h b/src/type/alias_type.h
index 5361950..ede6218 100644
--- a/src/type/alias_type.h
+++ b/src/type/alias_type.h
@@ -47,16 +47,6 @@
   /// declared in WGSL.
   std::string FriendlyName(const SymbolTable& symbols) const override;
 
-  /// @param mem_layout type of memory layout to use in calculation.
-  /// @returns minimum size required for this type, in bytes.
-  ///          0 for non-host shareable types.
-  uint64_t MinBufferBindingSize(MemoryLayout mem_layout) const override;
-
-  /// @param mem_layout type of memory layout to use in calculation.
-  /// @returns base alignment for the type, in bytes.
-  ///          0 for non-host shareable types.
-  uint64_t BaseAlignment(MemoryLayout mem_layout) const override;
-
   /// Clones this type and all transitive types using the `CloneContext` `ctx`.
   /// @param ctx the clone context
   /// @return the newly cloned type
diff --git a/src/type/alias_type_test.cc b/src/type/alias_type_test.cc
index 278ea6a..741d04a 100644
--- a/src/type/alias_type_test.cc
+++ b/src/type/alias_type_test.cc
@@ -137,76 +137,6 @@
   EXPECT_EQ(a.UnwrapAll(), ty.u32());
 }
 
-TEST_F(AliasTest, MinBufferBindingSizeU32) {
-  auto* alias = ty.alias("alias", ty.u32());
-  EXPECT_EQ(4u, alias->MinBufferBindingSize(MemoryLayout::kUniformBuffer));
-}
-
-TEST_F(AliasTest, MinBufferBindingSizeArray) {
-  Array array(ty.u32(), 4,
-              ast::DecorationList{
-                  create<ast::StrideDecoration>(4),
-              });
-  auto* alias = ty.alias("alias", &array);
-  EXPECT_EQ(16u, alias->MinBufferBindingSize(MemoryLayout::kUniformBuffer));
-}
-
-TEST_F(AliasTest, MinBufferBindingSizeRuntimeArray) {
-  Array array(ty.u32(), 0,
-              ast::DecorationList{
-                  create<ast::StrideDecoration>(4),
-              });
-  auto* alias = ty.alias("alias", &array);
-  EXPECT_EQ(4u, alias->MinBufferBindingSize(MemoryLayout::kUniformBuffer));
-}
-
-TEST_F(AliasTest, MinBufferBindingSizeStruct) {
-  auto* str = create<ast::Struct>(
-      ast::StructMemberList{Member("foo", ty.u32(), {MemberOffset(0)}),
-                            Member("bar", ty.u32(), {MemberOffset(4)})},
-      ast::DecorationList{});
-  auto* struct_type = ty.struct_("struct_type", str);
-  auto* alias = ty.alias("alias", struct_type);
-
-  EXPECT_EQ(16u, alias->MinBufferBindingSize(MemoryLayout::kUniformBuffer));
-  EXPECT_EQ(8u, alias->MinBufferBindingSize(MemoryLayout::kStorageBuffer));
-}
-
-TEST_F(AliasTest, BaseAlignmentU32) {
-  auto* alias = ty.alias("alias", ty.u32());
-  EXPECT_EQ(4u, alias->BaseAlignment(MemoryLayout::kUniformBuffer));
-}
-
-TEST_F(AliasTest, BaseAlignmentArray) {
-  Array array(ty.u32(), 4,
-              ast::DecorationList{
-                  create<ast::StrideDecoration>(4),
-              });
-  auto* alias = ty.alias("alias", &array);
-  EXPECT_EQ(16u, alias->BaseAlignment(MemoryLayout::kUniformBuffer));
-}
-
-TEST_F(AliasTest, BaseAlignmentRuntimeArray) {
-  Array array(ty.u32(), 0,
-              ast::DecorationList{
-                  create<ast::StrideDecoration>(4),
-              });
-  auto* alias = ty.alias("alias", &array);
-  EXPECT_EQ(16u, alias->BaseAlignment(MemoryLayout::kUniformBuffer));
-}
-
-TEST_F(AliasTest, BaseAlignmentStruct) {
-  auto* str = create<ast::Struct>(
-      ast::StructMemberList{Member("foo", ty.u32(), {MemberOffset(0)}),
-                            Member("bar", ty.u32(), {MemberOffset(4)})},
-      ast::DecorationList{});
-  auto* struct_type = ty.struct_("struct_type", str);
-  auto* alias = ty.alias("alias", struct_type);
-
-  EXPECT_EQ(16u, alias->BaseAlignment(MemoryLayout::kUniformBuffer));
-  EXPECT_EQ(4u, alias->BaseAlignment(MemoryLayout::kStorageBuffer));
-}
-
 TEST_F(AliasTest, UnwrapAliasIfNeeded) {
   auto* alias1 = ty.alias("alias1", ty.f32());
   auto* alias2 = ty.alias("alias2", alias1);
diff --git a/src/type/array_type.cc b/src/type/array_type.cc
index 29ead9b..46fe3ed 100644
--- a/src/type/array_type.cc
+++ b/src/type/array_type.cc
@@ -30,71 +30,28 @@
 
 Array::~Array() = default;
 
-uint64_t Array::MinBufferBindingSize(MemoryLayout mem_layout) const {
-  if (!has_array_stride()) {
-    // Arrays in buffers are required to have a stride.
-    return 0;
-  }
-
-  if (IsRuntimeArray()) {
-    // WebGPU spec 10.1.2:
-    // If the last field of the corresponding structure defined in the shader
-    // has an unbounded array type, then the value of minBufferBindingSize must
-    // be greater than or equal to the byte offset of that field plus the stride
-    // of the unbounded array
-    return array_stride();
-  } else {
-    // Not including the padding for the last element
-    return (size_ - 1) * array_stride() +
-           subtype_->MinBufferBindingSize(mem_layout);
-  }
-}
-
-uint64_t Array::BaseAlignment(MemoryLayout mem_layout) const {
-  if (mem_layout == MemoryLayout::kUniformBuffer) {
-    float aligment = 16;  // for a vec4
-    float unaligned = static_cast<float>(subtype_->BaseAlignment(mem_layout));
-    return static_cast<uint64_t>(aligment * std::ceil(unaligned / aligment));
-  } else if (mem_layout == MemoryLayout::kStorageBuffer) {
-    return subtype_->BaseAlignment(mem_layout);
-  }
-  return 0;
-}
-
-uint32_t Array::array_stride() const {
-  for (auto* deco : decos_) {
-    if (auto* stride = deco->As<ast::StrideDecoration>()) {
-      return stride->stride();
-    }
-  }
-  return 0;
-}
-
-bool Array::has_array_stride() const {
-  for (auto* deco : decos_) {
-    if (deco->Is<ast::StrideDecoration>()) {
-      return true;
-    }
-  }
-  return false;
-}
-
 std::string Array::type_name() const {
   assert(subtype_);
 
   std::string type_name = "__array" + subtype_->type_name();
-  if (!IsRuntimeArray())
+  if (!IsRuntimeArray()) {
     type_name += "_" + std::to_string(size_);
-  if (has_array_stride())
-    type_name += "_stride_" + std::to_string(array_stride());
+  }
+  for (auto* deco : decos_) {
+    if (auto* stride = deco->As<ast::StrideDecoration>()) {
+      type_name += "_stride_" + std::to_string(stride->stride());
+    }
+  }
 
   return type_name;
 }
 
 std::string Array::FriendlyName(const SymbolTable& symbols) const {
   std::ostringstream out;
-  if (has_array_stride()) {
-    out << "[[stride(" << array_stride() << ")]] ";
+  for (auto* deco : decos_) {
+    if (auto* stride = deco->As<ast::StrideDecoration>()) {
+      out << "[[stride(" << stride->stride() << ")]] ";
+    }
   }
   out << "array<" << subtype_->FriendlyName(symbols);
   if (!IsRuntimeArray()) {
diff --git a/src/type/array_type.h b/src/type/array_type.h
index c0f74aa..73456bc 100644
--- a/src/type/array_type.h
+++ b/src/type/array_type.h
@@ -40,24 +40,9 @@
   /// i.e. the size is determined at runtime
   bool IsRuntimeArray() const { return size_ == 0; }
 
-  /// @param mem_layout type of memory layout to use in calculation.
-  /// @returns minimum size required for this type, in bytes.
-  ///          0 for non-host shareable types.
-  uint64_t MinBufferBindingSize(MemoryLayout mem_layout) const override;
-
-  /// @param mem_layout type of memory layout to use in calculation.
-  /// @returns base alignment for the type, in bytes.
-  ///          0 for non-host shareable types.
-  uint64_t BaseAlignment(MemoryLayout mem_layout) const override;
-
   /// @returns the array decorations
   const ast::DecorationList& decorations() const { return decos_; }
 
-  /// @returns the array stride or 0 if none set.
-  uint32_t array_stride() const;
-  /// @returns true if the array has a stride set
-  bool has_array_stride() const;
-
   /// @returns the array type
   Type* type() const { return subtype_; }
   /// @returns the array size. Size is 0 for a runtime array
diff --git a/src/type/array_type_test.cc b/src/type/array_type_test.cc
index 30bf850..062addd 100644
--- a/src/type/array_type_test.cc
+++ b/src/type/array_type_test.cc
@@ -94,38 +94,6 @@
   EXPECT_EQ(arr.type_name(), "__array__i32_3_stride_16");
 }
 
-TEST_F(ArrayTest, MinBufferBindingSizeNoStride) {
-  U32 u32;
-  Array arr(&u32, 4, ast::DecorationList{});
-  EXPECT_EQ(0u, arr.MinBufferBindingSize(MemoryLayout::kUniformBuffer));
-}
-
-TEST_F(ArrayTest, MinBufferBindingSizeArray) {
-  U32 u32;
-  Array arr(&u32, 4, ast::DecorationList{create<ast::StrideDecoration>(4)});
-  EXPECT_EQ(16u, arr.MinBufferBindingSize(MemoryLayout::kUniformBuffer));
-}
-
-TEST_F(ArrayTest, MinBufferBindingSizeRuntimeArray) {
-  U32 u32;
-  Array arr(&u32, 0, ast::DecorationList{create<ast::StrideDecoration>(4)});
-  EXPECT_EQ(4u, arr.MinBufferBindingSize(MemoryLayout::kUniformBuffer));
-}
-
-TEST_F(ArrayTest, BaseAlignmentArray) {
-  U32 u32;
-  Array arr(&u32, 4, ast::DecorationList{create<ast::StrideDecoration>(4)});
-  EXPECT_EQ(16u, arr.BaseAlignment(MemoryLayout::kUniformBuffer));
-  EXPECT_EQ(4u, arr.BaseAlignment(MemoryLayout::kStorageBuffer));
-}
-
-TEST_F(ArrayTest, BaseAlignmentRuntimeArray) {
-  U32 u32;
-  Array arr(&u32, 0, ast::DecorationList{create<ast::StrideDecoration>(4)});
-  EXPECT_EQ(16u, arr.BaseAlignment(MemoryLayout::kUniformBuffer));
-  EXPECT_EQ(4u, arr.BaseAlignment(MemoryLayout::kStorageBuffer));
-}
-
 }  // namespace
 }  // namespace type
 }  // namespace tint
diff --git a/src/type/bool_type_test.cc b/src/type/bool_type_test.cc
index 3a9fd2c..7751c53 100644
--- a/src/type/bool_type_test.cc
+++ b/src/type/bool_type_test.cc
@@ -50,11 +50,6 @@
   EXPECT_EQ(b.FriendlyName(Symbols()), "bool");
 }
 
-TEST_F(BoolTest, MinBufferBindingSize) {
-  Bool b;
-  EXPECT_EQ(0u, b.MinBufferBindingSize(MemoryLayout::kUniformBuffer));
-}
-
 }  // namespace
 }  // namespace type
 }  // namespace tint
diff --git a/src/type/depth_texture_type_test.cc b/src/type/depth_texture_type_test.cc
index ebb20da..62cb05b 100644
--- a/src/type/depth_texture_type_test.cc
+++ b/src/type/depth_texture_type_test.cc
@@ -67,11 +67,6 @@
   EXPECT_EQ(d.FriendlyName(Symbols()), "texture_depth_cube");
 }
 
-TEST_F(DepthTextureTest, MinBufferBindingSize) {
-  DepthTexture d(TextureDimension::kCube);
-  EXPECT_EQ(0u, d.MinBufferBindingSize(MemoryLayout::kUniformBuffer));
-}
-
 }  // namespace
 }  // namespace type
 }  // namespace tint
diff --git a/src/type/f32_type.cc b/src/type/f32_type.cc
index 8bd02ab..5e7e2a5 100644
--- a/src/type/f32_type.cc
+++ b/src/type/f32_type.cc
@@ -35,14 +35,6 @@
   return "f32";
 }
 
-uint64_t F32::MinBufferBindingSize(MemoryLayout) const {
-  return 4;
-}
-
-uint64_t F32::BaseAlignment(MemoryLayout) const {
-  return 4;
-}
-
 F32* F32::Clone(CloneContext* ctx) const {
   return ctx->dst->create<F32>();
 }
diff --git a/src/type/f32_type.h b/src/type/f32_type.h
index 50e990d..156ddd1 100644
--- a/src/type/f32_type.h
+++ b/src/type/f32_type.h
@@ -39,16 +39,6 @@
   /// declared in WGSL.
   std::string FriendlyName(const SymbolTable& symbols) const override;
 
-  /// @param mem_layout type of memory layout to use in calculation.
-  /// @returns minimum size required for this type, in bytes.
-  ///          0 for non-host shareable types.
-  uint64_t MinBufferBindingSize(MemoryLayout mem_layout) const override;
-
-  /// @param mem_layout type of memory layout to use in calculation.
-  /// @returns base alignment for the type, in bytes.
-  ///          0 for non-host shareable types.
-  uint64_t BaseAlignment(MemoryLayout mem_layout) const override;
-
   /// Clones this type and all transitive types using the `CloneContext` `ctx`.
   /// @param ctx the clone context
   /// @return the newly cloned type
diff --git a/src/type/f32_type_test.cc b/src/type/f32_type_test.cc
index d02d1b9..2985597 100644
--- a/src/type/f32_type_test.cc
+++ b/src/type/f32_type_test.cc
@@ -50,16 +50,6 @@
   EXPECT_EQ(f.FriendlyName(Symbols()), "f32");
 }
 
-TEST_F(F32Test, MinBufferBindingSize) {
-  F32 f;
-  EXPECT_EQ(4u, f.MinBufferBindingSize(MemoryLayout::kUniformBuffer));
-}
-
-TEST_F(F32Test, BaseAlignment) {
-  F32 f;
-  EXPECT_EQ(4u, f.BaseAlignment(MemoryLayout::kUniformBuffer));
-}
-
 }  // namespace
 }  // namespace type
 }  // namespace tint
diff --git a/src/type/i32_type.cc b/src/type/i32_type.cc
index 78a7a7d..7807385 100644
--- a/src/type/i32_type.cc
+++ b/src/type/i32_type.cc
@@ -35,14 +35,6 @@
   return "i32";
 }
 
-uint64_t I32::MinBufferBindingSize(MemoryLayout) const {
-  return 4;
-}
-
-uint64_t I32::BaseAlignment(MemoryLayout) const {
-  return 4;
-}
-
 I32* I32::Clone(CloneContext* ctx) const {
   return ctx->dst->create<I32>();
 }
diff --git a/src/type/i32_type.h b/src/type/i32_type.h
index e688a0c..a376974 100644
--- a/src/type/i32_type.h
+++ b/src/type/i32_type.h
@@ -39,16 +39,6 @@
   /// declared in WGSL.
   std::string FriendlyName(const SymbolTable& symbols) const override;
 
-  /// @param mem_layout type of memory layout to use in calculation.
-  /// @returns minimum size required for this type, in bytes.
-  ///          0 for non-host shareable types.
-  uint64_t MinBufferBindingSize(MemoryLayout mem_layout) const override;
-
-  /// @param mem_layout type of memory layout to use in calculation.
-  /// @returns base alignment for the type, in bytes.
-  ///          0 for non-host shareable types.
-  uint64_t BaseAlignment(MemoryLayout mem_layout) const override;
-
   /// Clones this type and all transitive types using the `CloneContext` `ctx`.
   /// @param ctx the clone context
   /// @return the newly cloned type
diff --git a/src/type/i32_type_test.cc b/src/type/i32_type_test.cc
index 788ea58..38aca92 100644
--- a/src/type/i32_type_test.cc
+++ b/src/type/i32_type_test.cc
@@ -50,16 +50,6 @@
   EXPECT_EQ(i.FriendlyName(Symbols()), "i32");
 }
 
-TEST_F(I32Test, MinBufferBindingSize) {
-  I32 i;
-  EXPECT_EQ(4u, i.MinBufferBindingSize(MemoryLayout::kUniformBuffer));
-}
-
-TEST_F(I32Test, BaseAlignment) {
-  I32 i;
-  EXPECT_EQ(4u, i.BaseAlignment(MemoryLayout::kUniformBuffer));
-}
-
 }  // namespace
 }  // namespace type
 }  // namespace tint
diff --git a/src/type/matrix_type.cc b/src/type/matrix_type.cc
index d98c669..02e225b 100644
--- a/src/type/matrix_type.cc
+++ b/src/type/matrix_type.cc
@@ -45,18 +45,6 @@
   return out.str();
 }
 
-uint64_t Matrix::MinBufferBindingSize(MemoryLayout mem_layout) const {
-  Vector vec(subtype_, rows_);
-  return (columns_ - 1) * vec.BaseAlignment(mem_layout) +
-         vec.MinBufferBindingSize(mem_layout);
-}
-
-uint64_t Matrix::BaseAlignment(MemoryLayout mem_layout) const {
-  Vector vec(subtype_, rows_);
-  Array arr(&vec, columns_, ast::DecorationList{});
-  return arr.BaseAlignment(mem_layout);
-}
-
 Matrix* Matrix::Clone(CloneContext* ctx) const {
   // Clone arguments outside of create() call to have deterministic ordering
   auto* ty = ctx->Clone(type());
diff --git a/src/type/matrix_type.h b/src/type/matrix_type.h
index f76d578..59c878c 100644
--- a/src/type/matrix_type.h
+++ b/src/type/matrix_type.h
@@ -49,16 +49,6 @@
   /// declared in WGSL.
   std::string FriendlyName(const SymbolTable& symbols) const override;
 
-  /// @param mem_layout type of memory layout to use in calculation.
-  /// @returns minimum size required for this type, in bytes.
-  ///          0 for non-host shareable types.
-  uint64_t MinBufferBindingSize(MemoryLayout mem_layout) const override;
-
-  /// @param mem_layout type of memory layout to use in calculation.
-  /// @returns base alignment for the type, in bytes.
-  ///          0 for non-host shareable types.
-  uint64_t BaseAlignment(MemoryLayout mem_layout) const override;
-
   /// Clones this type and all transitive types using the `CloneContext` `ctx`.
   /// @param ctx the clone context
   /// @return the newly cloned type
diff --git a/src/type/matrix_type_test.cc b/src/type/matrix_type_test.cc
index 73a7d0f..09dc8f0 100644
--- a/src/type/matrix_type_test.cc
+++ b/src/type/matrix_type_test.cc
@@ -60,62 +60,6 @@
   EXPECT_EQ(m.FriendlyName(Symbols()), "mat2x3<i32>");
 }
 
-TEST_F(MatrixTest, MinBufferBindingSize4x2) {
-  I32 i32;
-  Matrix m{&i32, 4, 2};
-  EXPECT_EQ(32u, m.MinBufferBindingSize(MemoryLayout::kUniformBuffer));
-  EXPECT_EQ(32u, m.MinBufferBindingSize(MemoryLayout::kStorageBuffer));
-}
-
-TEST_F(MatrixTest, MinBufferBindingSize3x2) {
-  I32 i32;
-  Matrix m{&i32, 3, 2};
-  EXPECT_EQ(28u, m.MinBufferBindingSize(MemoryLayout::kUniformBuffer));
-  EXPECT_EQ(28u, m.MinBufferBindingSize(MemoryLayout::kStorageBuffer));
-}
-
-TEST_F(MatrixTest, MinBufferBindingSize2x3) {
-  I32 i32;
-  Matrix m{&i32, 2, 3};
-  EXPECT_EQ(24u, m.MinBufferBindingSize(MemoryLayout::kUniformBuffer));
-  EXPECT_EQ(24u, m.MinBufferBindingSize(MemoryLayout::kStorageBuffer));
-}
-
-TEST_F(MatrixTest, MinBufferBindingSize2x2) {
-  I32 i32;
-  Matrix m{&i32, 2, 2};
-  EXPECT_EQ(16u, m.MinBufferBindingSize(MemoryLayout::kUniformBuffer));
-  EXPECT_EQ(16u, m.MinBufferBindingSize(MemoryLayout::kStorageBuffer));
-}
-
-TEST_F(MatrixTest, BaseAlignment4x2) {
-  I32 i32;
-  Matrix m{&i32, 4, 2};
-  EXPECT_EQ(16u, m.BaseAlignment(MemoryLayout::kUniformBuffer));
-  EXPECT_EQ(16u, m.BaseAlignment(MemoryLayout::kStorageBuffer));
-}
-
-TEST_F(MatrixTest, BaseAlignment3x2) {
-  I32 i32;
-  Matrix m{&i32, 3, 2};
-  EXPECT_EQ(16u, m.BaseAlignment(MemoryLayout::kUniformBuffer));
-  EXPECT_EQ(16u, m.BaseAlignment(MemoryLayout::kStorageBuffer));
-}
-
-TEST_F(MatrixTest, BaseAlignment2x3) {
-  I32 i32;
-  Matrix m{&i32, 2, 3};
-  EXPECT_EQ(16u, m.BaseAlignment(MemoryLayout::kUniformBuffer));
-  EXPECT_EQ(8u, m.BaseAlignment(MemoryLayout::kStorageBuffer));
-}
-
-TEST_F(MatrixTest, BaseAlignment2x2) {
-  I32 i32;
-  Matrix m{&i32, 2, 2};
-  EXPECT_EQ(16u, m.BaseAlignment(MemoryLayout::kUniformBuffer));
-  EXPECT_EQ(8u, m.BaseAlignment(MemoryLayout::kStorageBuffer));
-}
-
 }  // namespace
 }  // namespace type
 }  // namespace tint
diff --git a/src/type/multisampled_texture_type_test.cc b/src/type/multisampled_texture_type_test.cc
index caeb3fa..d0f025e 100644
--- a/src/type/multisampled_texture_type_test.cc
+++ b/src/type/multisampled_texture_type_test.cc
@@ -78,12 +78,6 @@
   EXPECT_EQ(s.FriendlyName(Symbols()), "texture_multisampled_3d<f32>");
 }
 
-TEST_F(MultisampledTextureTest, MinBufferBindingSize) {
-  F32 f32;
-  MultisampledTexture s(TextureDimension::k3d, &f32);
-  EXPECT_EQ(0u, s.MinBufferBindingSize(MemoryLayout::kUniformBuffer));
-}
-
 }  // namespace
 }  // namespace type
 }  // namespace tint
diff --git a/src/type/sampled_texture_type_test.cc b/src/type/sampled_texture_type_test.cc
index 5034fb8..2587c2e 100644
--- a/src/type/sampled_texture_type_test.cc
+++ b/src/type/sampled_texture_type_test.cc
@@ -76,12 +76,6 @@
   EXPECT_EQ(s.FriendlyName(Symbols()), "texture_3d<f32>");
 }
 
-TEST_F(SampledTextureTest, MinBufferBindingSize) {
-  F32 f32;
-  SampledTexture s(TextureDimension::kCube, &f32);
-  EXPECT_EQ(0u, s.MinBufferBindingSize(MemoryLayout::kUniformBuffer));
-}
-
 }  // namespace
 }  // namespace type
 }  // namespace tint
diff --git a/src/type/sampler_type_test.cc b/src/type/sampler_type_test.cc
index 1912857..a3ea06d 100644
--- a/src/type/sampler_type_test.cc
+++ b/src/type/sampler_type_test.cc
@@ -71,11 +71,6 @@
   EXPECT_EQ(s.FriendlyName(Symbols()), "sampler_comparison");
 }
 
-TEST_F(SamplerTest, MinBufferBindingSize) {
-  Sampler s{SamplerKind::kSampler};
-  EXPECT_EQ(0u, s.MinBufferBindingSize(MemoryLayout::kUniformBuffer));
-}
-
 }  // namespace
 }  // namespace type
 }  // namespace tint
diff --git a/src/type/storage_texture_type_test.cc b/src/type/storage_texture_type_test.cc
index d7af801..976ea72 100644
--- a/src/type/storage_texture_type_test.cc
+++ b/src/type/storage_texture_type_test.cc
@@ -132,13 +132,6 @@
   EXPECT_TRUE(s->As<StorageTexture>()->type()->Is<I32>());
 }
 
-TEST_F(StorageTextureTest, MinBufferBindingSize) {
-  auto* subtype = StorageTexture::SubtypeFor(ImageFormat::kRgba32Sint, Types());
-  auto* s = create<StorageTexture>(TextureDimension::k2dArray,
-                                   ImageFormat::kRgba32Sint, subtype);
-  EXPECT_EQ(0u, s->MinBufferBindingSize(MemoryLayout::kUniformBuffer));
-}
-
 }  // namespace
 }  // namespace type
 }  // namespace tint
diff --git a/src/type/struct_type.cc b/src/type/struct_type.cc
index c1bf774..37e7885 100644
--- a/src/type/struct_type.cc
+++ b/src/type/struct_type.cc
@@ -38,48 +38,6 @@
   return symbols.NameFor(symbol_);
 }
 
-uint64_t Struct::MinBufferBindingSize(MemoryLayout mem_layout) const {
-  if (!struct_->members().size()) {
-    return 0;
-  }
-
-  auto* last_member = struct_->members().back();
-
-  // If there is no offset, then this is not a host-shareable struct, returning
-  // 0 indicates this to the caller.
-  if (!last_member->has_offset_decoration()) {
-    return 0;
-  }
-
-  uint64_t size = last_member->type()->MinBufferBindingSize(mem_layout);
-  if (!size) {
-    return 0;
-  }
-
-  float unaligned = static_cast<float>(last_member->offset() + size);
-  float alignment = static_cast<float>(BaseAlignment(mem_layout));
-
-  return static_cast<uint64_t>(alignment * std::ceil(unaligned / alignment));
-}
-
-uint64_t Struct::BaseAlignment(MemoryLayout mem_layout) const {
-  uint64_t max = 0;
-  for (auto* member : struct_->members()) {
-    if (member->type()->BaseAlignment(mem_layout) > max) {
-      max = member->type()->BaseAlignment(mem_layout);
-    }
-  }
-
-  if (mem_layout == MemoryLayout::kUniformBuffer) {
-    // Round up to a vec4.
-    return static_cast<uint64_t>(16 *
-                                 std::ceil(static_cast<float>(max) / 16.0f));
-  } else if (mem_layout == MemoryLayout::kStorageBuffer) {
-    return max;
-  }
-  return 0;
-}
-
 Struct* Struct::Clone(CloneContext* ctx) const {
   // Clone arguments outside of create() call to have deterministic ordering
   auto sym = ctx->Clone(symbol());
diff --git a/src/type/struct_type.h b/src/type/struct_type.h
index bec6782..9a94877 100644
--- a/src/type/struct_type.h
+++ b/src/type/struct_type.h
@@ -51,16 +51,6 @@
   /// declared in WGSL.
   std::string FriendlyName(const SymbolTable& symbols) const override;
 
-  /// @param mem_layout type of memory layout to use in calculation.
-  /// @returns minimum size required for this type, in bytes.
-  ///          0 for non-host shareable types.
-  uint64_t MinBufferBindingSize(MemoryLayout mem_layout) const override;
-
-  /// @param mem_layout type of memory layout to use in calculation.
-  /// @returns base alignment for the type, in bytes.
-  ///          0 for non-host shareable types.
-  uint64_t BaseAlignment(MemoryLayout mem_layout) const override;
-
   /// Clones this type and all transitive types using the `CloneContext` `ctx`.
   /// @param ctx the clone context
   /// @return the newly cloned type
diff --git a/src/type/struct_type_test.cc b/src/type/struct_type_test.cc
index ec562bc..4099ed1 100644
--- a/src/type/struct_type_test.cc
+++ b/src/type/struct_type_test.cc
@@ -64,140 +64,6 @@
   EXPECT_EQ(s->FriendlyName(Symbols()), "my_struct");
 }
 
-TEST_F(StructTypeTest, MinBufferBindingSize) {
-  auto* str = create<ast::Struct>(
-      ast::StructMemberList{Member("foo", ty.u32(), {MemberOffset(0)}),
-                            Member("bar", ty.u32(), {MemberOffset(4)})},
-      ast::DecorationList{});
-  auto* s_ty = ty.struct_("s_ty", str);
-
-  EXPECT_EQ(16u, s_ty->MinBufferBindingSize(MemoryLayout::kUniformBuffer));
-  EXPECT_EQ(8u, s_ty->MinBufferBindingSize(MemoryLayout::kStorageBuffer));
-}
-
-TEST_F(StructTypeTest, MinBufferBindingSizeArray) {
-  Array arr(ty.u32(), 4, ast::DecorationList{create<ast::StrideDecoration>(4)});
-
-  auto* str = create<ast::Struct>(
-      ast::StructMemberList{Member("foo", ty.u32(), {MemberOffset(0)}),
-                            Member("bar", ty.u32(), {MemberOffset(4)}),
-                            Member("bar", &arr, {MemberOffset(8)})},
-      ast::DecorationList{});
-  auto* s_ty = ty.struct_("s_ty", str);
-
-  EXPECT_EQ(32u, s_ty->MinBufferBindingSize(MemoryLayout::kUniformBuffer));
-  EXPECT_EQ(24u, s_ty->MinBufferBindingSize(MemoryLayout::kStorageBuffer));
-}
-
-TEST_F(StructTypeTest, MinBufferBindingSizeRuntimeArray) {
-  Array arr(ty.u32(), 0, ast::DecorationList{create<ast::StrideDecoration>(4)});
-
-  auto* str = create<ast::Struct>(
-      ast::StructMemberList{Member("foo", ty.u32(), {MemberOffset(0)}),
-                            Member("bar", ty.u32(), {MemberOffset(4)}),
-                            Member("bar", ty.u32(), {MemberOffset(8)})},
-      ast::DecorationList{});
-  auto* s_ty = ty.struct_("s_ty", str);
-
-  EXPECT_EQ(12u, s_ty->MinBufferBindingSize(MemoryLayout::kStorageBuffer));
-}
-
-TEST_F(StructTypeTest, MinBufferBindingSizeVec2) {
-  auto* str = create<ast::Struct>(
-      ast::StructMemberList{Member("foo", ty.vec2<u32>(), {MemberOffset(0)})},
-      ast::DecorationList{});
-  auto* s_ty = ty.struct_("s_ty", str);
-
-  EXPECT_EQ(16u, s_ty->MinBufferBindingSize(MemoryLayout::kUniformBuffer));
-  EXPECT_EQ(8u, s_ty->MinBufferBindingSize(MemoryLayout::kStorageBuffer));
-}
-
-TEST_F(StructTypeTest, MinBufferBindingSizeVec3) {
-  auto* str = create<ast::Struct>(
-      ast::StructMemberList{Member("foo", ty.vec3<u32>(), {MemberOffset(0)})},
-      ast::DecorationList{});
-  auto* s_ty = ty.struct_("s_ty", str);
-
-  EXPECT_EQ(16u, s_ty->MinBufferBindingSize(MemoryLayout::kUniformBuffer));
-  EXPECT_EQ(16u, s_ty->MinBufferBindingSize(MemoryLayout::kStorageBuffer));
-}
-
-TEST_F(StructTypeTest, MinBufferBindingSizeVec4) {
-  auto* str = create<ast::Struct>(
-      ast::StructMemberList{Member("foo", ty.vec4<u32>(), {MemberOffset(0)})},
-      ast::DecorationList{});
-  auto* s_ty = ty.struct_("s_ty", str);
-
-  EXPECT_EQ(16u, s_ty->MinBufferBindingSize(MemoryLayout::kUniformBuffer));
-  EXPECT_EQ(16u, s_ty->MinBufferBindingSize(MemoryLayout::kStorageBuffer));
-}
-
-TEST_F(StructTypeTest, BaseAlignment) {
-  auto* str = create<ast::Struct>(
-      ast::StructMemberList{Member("foo", ty.u32(), {MemberOffset(0)}),
-                            Member("bar", ty.u32(), {MemberOffset(8)})},
-      ast::DecorationList{});
-  auto* s_ty = ty.struct_("s_ty", str);
-
-  EXPECT_EQ(16u, s_ty->BaseAlignment(MemoryLayout::kUniformBuffer));
-  EXPECT_EQ(4u, s_ty->BaseAlignment(MemoryLayout::kStorageBuffer));
-}
-
-TEST_F(StructTypeTest, BaseAlignmentArray) {
-  Array arr(ty.u32(), 4, ast::DecorationList{create<ast::StrideDecoration>(4)});
-  auto* str = create<ast::Struct>(
-      ast::StructMemberList{Member("foo", ty.u32(), {MemberOffset(0)}),
-                            Member("bar", ty.u32(), {MemberOffset(4)}),
-                            Member("bar", &arr, {MemberOffset(8)})},
-      ast::DecorationList{});
-  auto* s_ty = ty.struct_("s_ty", str);
-
-  EXPECT_EQ(16u, s_ty->BaseAlignment(MemoryLayout::kUniformBuffer));
-  EXPECT_EQ(4u, s_ty->BaseAlignment(MemoryLayout::kStorageBuffer));
-}
-
-TEST_F(StructTypeTest, BaseAlignmentRuntimeArray) {
-  Array arr(ty.u32(), 0, ast::DecorationList{create<ast::StrideDecoration>(4)});
-  auto* str = create<ast::Struct>(
-      ast::StructMemberList{Member("foo", ty.u32(), {MemberOffset(0)}),
-                            Member("bar", ty.u32(), {MemberOffset(4)}),
-                            Member("bar", ty.u32(), {MemberOffset(8)})},
-      ast::DecorationList{});
-  auto* s_ty = ty.struct_("s_ty", str);
-
-  EXPECT_EQ(4u, s_ty->BaseAlignment(MemoryLayout::kStorageBuffer));
-}
-
-TEST_F(StructTypeTest, BaseAlignmentVec2) {
-  auto* str = create<ast::Struct>(
-      ast::StructMemberList{Member("foo", ty.vec2<u32>(), {MemberOffset(0)})},
-      ast::DecorationList{});
-  auto* s_ty = ty.struct_("s_ty", str);
-
-  EXPECT_EQ(16u, s_ty->BaseAlignment(MemoryLayout::kUniformBuffer));
-  EXPECT_EQ(8u, s_ty->BaseAlignment(MemoryLayout::kStorageBuffer));
-}
-
-TEST_F(StructTypeTest, BaseAlignmentVec3) {
-  auto* str = create<ast::Struct>(
-      ast::StructMemberList{Member("foo", ty.vec3<u32>(), {MemberOffset(0)})},
-      ast::DecorationList{});
-  auto* s_ty = ty.struct_("s_ty", str);
-
-  EXPECT_EQ(16u, s_ty->BaseAlignment(MemoryLayout::kUniformBuffer));
-  EXPECT_EQ(16u, s_ty->BaseAlignment(MemoryLayout::kStorageBuffer));
-}
-
-TEST_F(StructTypeTest, BaseAlignmentVec4) {
-  auto* str = create<ast::Struct>(
-      ast::StructMemberList{Member("foo", ty.vec4<u32>(), {MemberOffset(0)})},
-      ast::DecorationList{});
-  auto* s_ty = ty.struct_("s_ty", str);
-
-  EXPECT_EQ(16u, s_ty->BaseAlignment(MemoryLayout::kUniformBuffer));
-  EXPECT_EQ(16u, s_ty->BaseAlignment(MemoryLayout::kStorageBuffer));
-}
-
 }  // namespace
 }  // namespace type
 }  // namespace tint
diff --git a/src/type/type.cc b/src/type/type.cc
index 2f6e0dc..39f309c 100644
--- a/src/type/type.cc
+++ b/src/type/type.cc
@@ -70,14 +70,6 @@
   return UnwrapIfNeeded()->UnwrapPtrIfNeeded()->UnwrapIfNeeded();
 }
 
-uint64_t Type::MinBufferBindingSize(MemoryLayout) const {
-  return 0;
-}
-
-uint64_t Type::BaseAlignment(MemoryLayout) const {
-  return 0;
-}
-
 bool Type::is_scalar() const {
   return is_float_scalar() || is_integer_scalar() || Is<Bool>();
 }
diff --git a/src/type/type.h b/src/type/type.h
index c6ef50e..c4e7a36 100644
--- a/src/type/type.h
+++ b/src/type/type.h
@@ -45,16 +45,6 @@
   /// declared in WGSL.
   virtual std::string FriendlyName(const SymbolTable& symbols) const = 0;
 
-  /// @param mem_layout type of memory layout to use in calculation.
-  /// @returns minimum size required for this type, in bytes.
-  ///          0 for non-host shareable types.
-  virtual uint64_t MinBufferBindingSize(MemoryLayout mem_layout) const;
-
-  /// @param mem_layout type of memory layout to use in calculation.
-  /// @returns base alignment for the type, in bytes.
-  ///          0 for non-host shareable types.
-  virtual uint64_t BaseAlignment(MemoryLayout mem_layout) const;
-
   /// @returns the pointee type if this is a pointer, `this` otherwise
   Type* UnwrapPtrIfNeeded();
 
diff --git a/src/type/u32_type.cc b/src/type/u32_type.cc
index c2f029c..775abc3 100644
--- a/src/type/u32_type.cc
+++ b/src/type/u32_type.cc
@@ -35,14 +35,6 @@
   return "u32";
 }
 
-uint64_t U32::MinBufferBindingSize(MemoryLayout) const {
-  return 4;
-}
-
-uint64_t U32::BaseAlignment(MemoryLayout) const {
-  return 4;
-}
-
 U32* U32::Clone(CloneContext* ctx) const {
   return ctx->dst->create<U32>();
 }
diff --git a/src/type/u32_type.h b/src/type/u32_type.h
index 7e92a3a..7aadc89 100644
--- a/src/type/u32_type.h
+++ b/src/type/u32_type.h
@@ -39,16 +39,6 @@
   /// declared in WGSL.
   std::string FriendlyName(const SymbolTable& symbols) const override;
 
-  /// @param mem_layout type of memory layout to use in calculation.
-  /// @returns minimum size required for this type, in bytes.
-  ///          0 for non-host shareable types.
-  uint64_t MinBufferBindingSize(MemoryLayout mem_layout) const override;
-
-  /// @param mem_layout type of memory layout to use in calculation.
-  /// @returns base alignment for the type, in bytes.
-  ///          0 for non-host shareable types.
-  uint64_t BaseAlignment(MemoryLayout mem_layout) const override;
-
   /// Clones this type and all transitive types using the `CloneContext` `ctx`.
   /// @param ctx the clone context
   /// @return the newly cloned type
diff --git a/src/type/u32_type_test.cc b/src/type/u32_type_test.cc
index 5b6db5a..c320cb5 100644
--- a/src/type/u32_type_test.cc
+++ b/src/type/u32_type_test.cc
@@ -50,16 +50,6 @@
   EXPECT_EQ(u.FriendlyName(Symbols()), "u32");
 }
 
-TEST_F(U32Test, MinBufferBindingSize) {
-  U32 u;
-  EXPECT_EQ(4u, u.MinBufferBindingSize(MemoryLayout::kUniformBuffer));
-}
-
-TEST_F(U32Test, BaseAlignment) {
-  U32 u;
-  EXPECT_EQ(4u, u.BaseAlignment(MemoryLayout::kUniformBuffer));
-}
-
 }  // namespace
 }  // namespace type
 }  // namespace tint
diff --git a/src/type/vector_type.cc b/src/type/vector_type.cc
index 3a8078f..ab12790 100644
--- a/src/type/vector_type.cc
+++ b/src/type/vector_type.cc
@@ -40,20 +40,6 @@
   return out.str();
 }
 
-uint64_t Vector::MinBufferBindingSize(MemoryLayout mem_layout) const {
-  return size_ * subtype_->MinBufferBindingSize(mem_layout);
-}
-
-uint64_t Vector::BaseAlignment(MemoryLayout mem_layout) const {
-  if (size_ == 2) {
-    return 2 * subtype_->BaseAlignment(mem_layout);
-  } else if (size_ == 3 || size_ == 4) {
-    return 4 * subtype_->BaseAlignment(mem_layout);
-  }
-
-  return 0;  // vectors are only supposed to have 2, 3, or 4 elements.
-}
-
 Vector* Vector::Clone(CloneContext* ctx) const {
   // Clone arguments outside of create() call to have deterministic ordering
   auto* ty = ctx->Clone(type());
diff --git a/src/type/vector_type.h b/src/type/vector_type.h
index 88e41f2..cbb9022 100644
--- a/src/type/vector_type.h
+++ b/src/type/vector_type.h
@@ -46,16 +46,6 @@
   /// declared in WGSL.
   std::string FriendlyName(const SymbolTable& symbols) const override;
 
-  /// @param mem_layout type of memory layout to use in calculation.
-  /// @returns minimum size required for this type, in bytes.
-  ///          0 for non-host shareable types.
-  uint64_t MinBufferBindingSize(MemoryLayout mem_layout) const override;
-
-  /// @param mem_layout type of memory layout to use in calculation.
-  /// @returns base alignment for the type, in bytes.
-  ///          0 for non-host shareable types.
-  uint64_t BaseAlignment(MemoryLayout mem_layout) const override;
-
   /// Clones this type and all transitive types using the `CloneContext` `ctx`.
   /// @param ctx the clone context
   /// @return the newly cloned type
diff --git a/src/type/vector_type_test.cc b/src/type/vector_type_test.cc
index 8f13b68..8c4a793 100644
--- a/src/type/vector_type_test.cc
+++ b/src/type/vector_type_test.cc
@@ -59,42 +59,6 @@
   EXPECT_EQ(v->FriendlyName(Symbols()), "vec3<f32>");
 }
 
-TEST_F(VectorTest, MinBufferBindingSizeVec2) {
-  I32 i32;
-  Vector v{&i32, 2};
-  EXPECT_EQ(8u, v.MinBufferBindingSize(MemoryLayout::kUniformBuffer));
-}
-
-TEST_F(VectorTest, MinBufferBindingSizeVec3) {
-  I32 i32;
-  Vector v{&i32, 3};
-  EXPECT_EQ(12u, v.MinBufferBindingSize(MemoryLayout::kUniformBuffer));
-}
-
-TEST_F(VectorTest, MinBufferBindingSizeVec4) {
-  I32 i32;
-  Vector v{&i32, 4};
-  EXPECT_EQ(16u, v.MinBufferBindingSize(MemoryLayout::kUniformBuffer));
-}
-
-TEST_F(VectorTest, BaseAlignmentVec2) {
-  I32 i32;
-  Vector v{&i32, 2};
-  EXPECT_EQ(8u, v.BaseAlignment(MemoryLayout::kUniformBuffer));
-}
-
-TEST_F(VectorTest, BaseAlignmentVec3) {
-  I32 i32;
-  Vector v{&i32, 3};
-  EXPECT_EQ(16u, v.BaseAlignment(MemoryLayout::kUniformBuffer));
-}
-
-TEST_F(VectorTest, BaseAlignmentVec4) {
-  I32 i32;
-  Vector v{&i32, 4};
-  EXPECT_EQ(16u, v.BaseAlignment(MemoryLayout::kUniformBuffer));
-}
-
 }  // namespace
 }  // namespace type
 }  // namespace tint
diff --git a/src/validator/validator_test.cc b/src/validator/validator_test.cc
index 491bed4..cc39b22 100644
--- a/src/validator/validator_test.cc
+++ b/src/validator/validator_test.cc
@@ -872,14 +872,6 @@
   EXPECT_TRUE(v.IsStorable(arr));
 }
 
-TEST_F(ValidatorTest, IsStorable_ArraySizedOfNonStorable) {
-  auto* arr = ty.array(ty.void_(), 5);
-
-  ValidatorImpl& v = Build();
-
-  EXPECT_FALSE(v.IsStorable(arr));
-}
-
 TEST_F(ValidatorTest, IsStorable_ArrayUnsizedOfStorable) {
   auto* arr = ty.array<int>();
 
@@ -888,14 +880,6 @@
   EXPECT_TRUE(v.IsStorable(arr));
 }
 
-TEST_F(ValidatorTest, IsStorable_ArrayUnsizedOfNonStorable) {
-  auto* arr = ty.array<void>();
-
-  ValidatorImpl& v = Build();
-
-  EXPECT_FALSE(v.IsStorable(arr));
-}
-
 TEST_F(ValidatorTest, IsStorable_Struct_AllMembersStorable) {
   ast::StructMemberList members{Member("a", ty.i32()), Member("b", ty.f32())};
   auto* s = create<ast::Struct>(Source{}, members, ast::DecorationList{});
@@ -906,16 +890,5 @@
   EXPECT_TRUE(v.IsStorable(s_ty));
 }
 
-TEST_F(ValidatorTest, IsStorable_Struct_SomeMembersNonStorable) {
-  auto* ptr_ty = ty.pointer<int>(ast::StorageClass::kPrivate);
-  ast::StructMemberList members{Member("a", ty.i32()), Member("b", ptr_ty)};
-  auto* s = create<ast::Struct>(Source{}, members, ast::DecorationList{});
-  auto* s_ty = ty.struct_("mystruct", s);
-
-  ValidatorImpl& v = Build();
-
-  EXPECT_FALSE(v.IsStorable(s_ty));
-}
-
 }  // namespace
 }  // namespace tint
diff --git a/src/writer/hlsl/generator_impl.cc b/src/writer/hlsl/generator_impl.cc
index cf5d62a..47fda58 100644
--- a/src/writer/hlsl/generator_impl.cc
+++ b/src/writer/hlsl/generator_impl.cc
@@ -21,9 +21,11 @@
 #include "src/ast/constant_id_decoration.h"
 #include "src/ast/fallthrough_statement.h"
 #include "src/ast/variable_decl_statement.h"
+#include "src/semantic/array.h"
 #include "src/semantic/call.h"
 #include "src/semantic/function.h"
 #include "src/semantic/member_accessor_expression.h"
+#include "src/semantic/struct.h"
 #include "src/semantic/variable.h"
 #include "src/type/access_control_type.h"
 #include "src/type/multisampled_texture_type.h"
@@ -2021,11 +2023,13 @@
         auto* str_type = str->impl();
         auto* str_member = str_type->get_member(mem->member()->symbol());
 
-        if (!str_member->has_offset_decoration()) {
-          diagnostics_.add_error("missing offset decoration for struct member");
+        auto* sem_mem = builder_.Sem().Get(str_member);
+        if (!sem_mem) {
+          TINT_ICE(diagnostics_) << "struct member missing semantic info";
           return "";
         }
-        out << str_member->offset();
+
+        out << sem_mem->Offset();
 
       } else if (res_type->Is<type::Vector>()) {
         auto swizzle = builder_.Sem().Get(mem)->Swizzle();
@@ -2035,9 +2039,9 @@
         // This must be a single element swizzle if we've got a vector at this
         // point.
         if (swizzle.size() != 1) {
-          diagnostics_.add_error(
-              "Encountered multi-element swizzle when should have only one "
-              "level");
+          TINT_ICE(diagnostics_)
+              << "Encountered multi-element swizzle when should have only one "
+                 "level";
           return "";
         }
 
@@ -2046,8 +2050,8 @@
         // f64 types.
         out << "(4 * " << swizzle[0] << ")";
       } else {
-        diagnostics_.add_error("Invalid result type for member accessor: " +
-                               res_type->type_name());
+        TINT_ICE(diagnostics_) << "Invalid result type for member accessor: "
+                               << res_type->type_name();
         return "";
       }
 
@@ -2057,7 +2061,12 @@
 
       out << "(";
       if (auto* arr = ary_type->As<type::Array>()) {
-        out << arr->array_stride();
+        auto* sem_arr = builder_.Sem().Get(arr);
+        if (!sem_arr) {
+          TINT_ICE(diagnostics_) << "array type missing semantic info";
+          return "";
+        }
+        out << sem_arr->Stride();
       } else if (ary_type->Is<type::Vector>()) {
         // TODO(dsinclair): This is a hack. Our vectors can only be f32, i32
         // or u32 which are all 4 bytes. When we get f16 or other types we'll
diff --git a/src/writer/hlsl/generator_impl_alias_type_test.cc b/src/writer/hlsl/generator_impl_alias_type_test.cc
index a11551d..ca1007b 100644
--- a/src/writer/hlsl/generator_impl_alias_type_test.cc
+++ b/src/writer/hlsl/generator_impl_alias_type_test.cc
@@ -43,8 +43,10 @@
 
 TEST_F(HlslGeneratorImplTest_Alias, EmitAlias_Struct) {
   auto* str = create<ast::Struct>(
-      ast::StructMemberList{Member("a", ty.f32()),
-                            Member("b", ty.i32(), {MemberOffset(4)})},
+      ast::StructMemberList{
+          Member("a", ty.f32()),
+          Member("b", ty.i32()),
+      },
       ast::DecorationList{});
 
   auto* s = ty.struct_("A", str);
diff --git a/src/writer/hlsl/generator_impl_function_test.cc b/src/writer/hlsl/generator_impl_function_test.cc
index c985858..e6ad1d5 100644
--- a/src/writer/hlsl/generator_impl_function_test.cc
+++ b/src/writer/hlsl/generator_impl_function_test.cc
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "gmock/gmock.h"
 #include "src/ast/stage_decoration.h"
 #include "src/ast/struct_block_decoration.h"
 #include "src/ast/variable_decl_statement.h"
@@ -19,6 +20,8 @@
 #include "src/type/access_control_type.h"
 #include "src/writer/hlsl/test_helper.h"
 
+using ::testing::HasSubstr;
+
 namespace tint {
 namespace writer {
 namespace hlsl {
@@ -264,11 +267,7 @@
 
 TEST_F(HlslGeneratorImplTest_Function,
        Emit_Decoration_EntryPoint_With_UniformStruct) {
-  auto* str = create<ast::Struct>(
-      ast::StructMemberList{Member("coord", ty.vec4<f32>())},
-      ast::DecorationList{});
-
-  auto* s = ty.struct_("Uniforms", str);
+  auto* s = Structure("Uniforms", {Member("coord", ty.vec4<f32>())});
 
   Global("uniforms", s, ast::StorageClass::kUniform, nullptr,
          ast::DecorationList{
@@ -276,8 +275,6 @@
              create<ast::GroupDecoration>(1),
          });
 
-  AST().AddConstructedType(s);
-
   auto* var = Var("v", ty.f32(), ast::StorageClass::kFunction,
                   create<ast::MemberAccessorExpression>(
                       MemberAccessor("uniforms", "coord"), Expr("x")));
@@ -310,12 +307,11 @@
 
 TEST_F(HlslGeneratorImplTest_Function,
        Emit_Decoration_EntryPoint_With_RW_StorageBuffer_Read) {
-  auto* str = create<ast::Struct>(
-      ast::StructMemberList{Member("a", ty.i32(), {MemberOffset(0)}),
-                            Member("b", ty.f32(), {MemberOffset(4)})},
-      ast::DecorationList{});
+  auto* s = Structure("Data", {
+                                  Member("a", ty.i32()),
+                                  Member("b", ty.f32()),
+                              });
 
-  auto* s = ty.struct_("Data", str);
   type::AccessControl ac(ast::AccessControl::kReadWrite, s);
 
   Global("coord", &ac, ast::StorageClass::kStorage, nullptr,
@@ -339,24 +335,21 @@
   GeneratorImpl& gen = Build();
 
   ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_EQ(result(), R"(RWByteAddressBuffer coord : register(u0);
+  EXPECT_THAT(result(), HasSubstr(R"(RWByteAddressBuffer coord : register(u0);
 
 void frag_main() {
   float v = asfloat(coord.Load(4));
   return;
-}
-
-)");
+})"));
 }
 
 TEST_F(HlslGeneratorImplTest_Function,
        Emit_Decoration_EntryPoint_With_RO_StorageBuffer_Read) {
-  auto* str = create<ast::Struct>(
-      ast::StructMemberList{Member("a", ty.i32(), {MemberOffset(0)}),
-                            Member("b", ty.f32(), {MemberOffset(4)})},
-      ast::DecorationList{});
+  auto* s = Structure("Data", {
+                                  Member("a", ty.i32()),
+                                  Member("b", ty.f32()),
+                              });
 
-  auto* s = ty.struct_("Data", str);
   type::AccessControl ac(ast::AccessControl::kReadOnly, s);
 
   Global("coord", &ac, ast::StorageClass::kStorage, nullptr,
@@ -380,24 +373,21 @@
   GeneratorImpl& gen = Build();
 
   ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_EQ(result(), R"(ByteAddressBuffer coord : register(t0);
+  EXPECT_THAT(result(), HasSubstr(R"(ByteAddressBuffer coord : register(t0);
 
 void frag_main() {
   float v = asfloat(coord.Load(4));
   return;
-}
-
-)");
+})"));
 }
 
 TEST_F(HlslGeneratorImplTest_Function,
        Emit_Decoration_EntryPoint_With_WO_StorageBuffer_Store) {
-  auto* str = create<ast::Struct>(
-      ast::StructMemberList{Member("a", ty.i32(), {MemberOffset(0)}),
-                            Member("b", ty.f32(), {MemberOffset(4)})},
-      ast::DecorationList{});
+  auto* s = Structure("Data", {
+                                  Member("a", ty.i32()),
+                                  Member("b", ty.f32()),
+                              });
 
-  auto* s = ty.struct_("Data", str);
   type::AccessControl ac(ast::AccessControl::kWriteOnly, s);
 
   Global("coord", &ac, ast::StorageClass::kStorage, nullptr,
@@ -419,24 +409,21 @@
   GeneratorImpl& gen = Build();
 
   ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_EQ(result(), R"(RWByteAddressBuffer coord : register(u0);
+  EXPECT_THAT(result(), HasSubstr(R"(RWByteAddressBuffer coord : register(u0);
 
 void frag_main() {
   coord.Store(4, asuint(2.0f));
   return;
-}
-
-)");
+})"));
 }
 
 TEST_F(HlslGeneratorImplTest_Function,
        Emit_Decoration_EntryPoint_With_StorageBuffer_Store) {
-  auto* str = create<ast::Struct>(
-      ast::StructMemberList{Member("a", ty.i32(), {MemberOffset(0)}),
-                            Member("b", ty.f32(), {MemberOffset(4)})},
-      ast::DecorationList{});
+  auto* s = Structure("Data", {
+                                  Member("a", ty.i32()),
+                                  Member("b", ty.f32()),
+                              });
 
-  auto* s = ty.struct_("Data", str);
   type::AccessControl ac(ast::AccessControl::kReadWrite, s);
 
   Global("coord", &ac, ast::StorageClass::kStorage, nullptr,
@@ -458,14 +445,12 @@
   GeneratorImpl& gen = Build();
 
   ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_EQ(result(), R"(RWByteAddressBuffer coord : register(u0);
+  EXPECT_THAT(result(), HasSubstr(R"(RWByteAddressBuffer coord : register(u0);
 
 void frag_main() {
   coord.Store(4, asuint(2.0f));
   return;
-}
-
-)");
+})"));
 }
 
 TEST_F(
@@ -715,7 +700,7 @@
   GeneratorImpl& gen = Build();
 
   ASSERT_TRUE(gen.Generate(out)) << gen.error();
-  EXPECT_EQ(result(), R"(RWByteAddressBuffer coord : register(u0);
+  EXPECT_THAT(result(), HasSubstr(R"(RWByteAddressBuffer coord : register(u0);
 
 float sub_func(float param) {
   return asfloat(coord.Load((4 * 0)));
@@ -724,9 +709,7 @@
 void frag_main() {
   float v = sub_func(1.0f);
   return;
-}
-
-)");
+})"));
 }
 
 TEST_F(HlslGeneratorImplTest_Function,
@@ -857,7 +840,7 @@
 TEST_F(HlslGeneratorImplTest_Function,
        Emit_Multiple_EntryPoint_With_Same_ModuleVar) {
   // [[block]] struct Data {
-  //   [[offset(0)]] d : f32;
+  //   d : f32;
   // };
   // [[binding(0), group(0)]] var<storage> data : Data;
   //
@@ -871,12 +854,9 @@
   //   return;
   // }
 
-  auto* str = create<ast::Struct>(
-      ast::StructMemberList{Member("d", ty.f32(), {MemberOffset(0)})},
-      ast::DecorationList{create<ast::StructBlockDecoration>()});
-
-  auto* s = ty.struct_("Data", str);
-  AST().AddConstructedType(s);
+  auto* s =
+      Structure("Data", {Member("d", ty.f32())},
+                ast::DecorationList{create<ast::StructBlockDecoration>()});
 
   type::AccessControl ac(ast::AccessControl::kReadWrite, s);
 
diff --git a/src/writer/hlsl/generator_impl_member_accessor_test.cc b/src/writer/hlsl/generator_impl_member_accessor_test.cc
index ffd588c..c8627d1 100644
--- a/src/writer/hlsl/generator_impl_member_accessor_test.cc
+++ b/src/writer/hlsl/generator_impl_member_accessor_test.cc
@@ -22,11 +22,7 @@
 using HlslGeneratorImplTest_MemberAccessor = TestHelper;
 
 TEST_F(HlslGeneratorImplTest_MemberAccessor, EmitExpression_MemberAccessor) {
-  auto* strct = create<ast::Struct>(
-      ast::StructMemberList{Member("mem", ty.f32(), {MemberOffset(0)})},
-      ast::DecorationList{});
-
-  auto* s = ty.struct_("Str", strct);
+  auto* s = Structure("Data", {Member("mem", ty.f32())});
   auto* str_var = Global("str", s, ast::StorageClass::kPrivate);
 
   auto* expr = MemberAccessor("str", "mem");
@@ -43,20 +39,19 @@
 TEST_F(HlslGeneratorImplTest_MemberAccessor,
        EmitExpression_MemberAccessor_StorageBuffer_Load) {
   // struct Data {
-  //   [[offset(0)]] a : i32;
-  //   [[offset(4)]] b : f32;
+  //   a : i32;
+  //   b : f32;
   // };
   // var<storage> data : Data;
   // data.b;
   //
   // -> asfloat(data.Load(4));
 
-  auto* str = create<ast::Struct>(
-      ast::StructMemberList{Member("a", ty.i32(), {MemberOffset(0)}),
-                            Member("b", ty.f32(), {MemberOffset(4)})},
-      ast::DecorationList{});
+  auto* s = Structure("data", {
+                                  Member("a", ty.i32()),
+                                  Member("b", ty.f32()),
+                              });
 
-  auto* s = ty.struct_("Data", str);
   auto* coord_var = Global("data", s, ast::StorageClass::kStorage);
 
   auto* expr = MemberAccessor("data", "b");
@@ -73,19 +68,19 @@
 TEST_F(HlslGeneratorImplTest_MemberAccessor,
        EmitExpression_MemberAccessor_StorageBuffer_Load_Int) {
   // struct Data {
-  //   [[offset(0)]] a : i32;
-  //   [[offset(4)]] b : f32;
+  //   a : i32;
+  //   b : f32;
   // };
   // var<storage> data : Data;
   // data.a;
   //
   // -> asint(data.Load(0));
 
-  auto* str = create<ast::Struct>(
-      ast::StructMemberList{Member("a", ty.i32(), {MemberOffset(0)}),
-                            Member("b", ty.f32(), {MemberOffset(4)})},
-      ast::DecorationList{});
-  auto* s = ty.struct_("Data", str);
+  auto* s = Structure("data", {
+                                  Member("a", ty.i32()),
+                                  Member("b", ty.f32()),
+                              });
+
   auto* coord_var = Global("data", s, ast::StorageClass::kStorage);
 
   auto* expr = MemberAccessor("data", "a");
@@ -102,8 +97,8 @@
 TEST_F(HlslGeneratorImplTest_MemberAccessor,
        EmitExpression_MemberAccessor_StorageBuffer_Store_Matrix) {
   // struct Data {
-  //   [[offset(0)]] z : f32;
-  //   [[offset(4)]] a : mat2x3<f32>;
+  //   z : f32;
+  //   a : mat2x3<f32>;
   // };
   // var<storage> data : Data;
   // mat2x3<f32> b;
@@ -113,12 +108,9 @@
   //    data.Store3(4 + 0, asuint(_tint_tmp[0]));
   //    data.Store3(4 + 16, asuint(_tint_tmp[1]));
 
-  auto* str = create<ast::Struct>(
-      ast::StructMemberList{Member("z", ty.i32(), {MemberOffset(0)}),
-                            Member("a", ty.mat2x3<f32>(), {MemberOffset(4)})},
-      ast::DecorationList{});
+  auto* s =
+      Structure("Data", {Member("z", ty.i32()), Member("a", ty.mat2x3<f32>())});
 
-  auto* s = ty.struct_("Data", str);
   auto* b_var = Global("b", ty.mat2x3<f32>(), ast::StorageClass::kPrivate);
   auto* coord_var = Global("data", s, ast::StorageClass::kStorage);
 
@@ -135,31 +127,28 @@
 
   ASSERT_TRUE(gen.EmitStatement(out, assign)) << gen.error();
   EXPECT_EQ(result(), R"(float3x2 _tint_tmp = b;
-data.Store3(4 + 0, asuint(_tint_tmp[0]));
-data.Store3(4 + 16, asuint(_tint_tmp[1]));
+data.Store3(16 + 0, asuint(_tint_tmp[0]));
+data.Store3(16 + 16, asuint(_tint_tmp[1]));
 )");
 }
 
 TEST_F(HlslGeneratorImplTest_MemberAccessor,
        EmitExpression_MemberAccessor_StorageBuffer_Store_Matrix_Empty) {
   // struct Data {
-  //   [[offset(0)]] z : f32;
-  //   [[offset(4)]] a : mat2x3<f32>;
+  //   z : f32;
+  //   a : mat2x3<f32>;
   // };
   // var<storage> data : Data;
   // data.a = mat2x3<f32>();
   //
   // -> float3x2 _tint_tmp = float3x2(0.0f, 0.0f, 0.0f,
   // 0.0f, 0.0f, 0.0f);
-  //    data.Store3(4 + 0, asuint(_tint_tmp[0]);
-  //    data.Store3(4 + 16, asuint(_tint_tmp[1]));
+  //    data.Store3(16 + 0, asuint(_tint_tmp[0]);
+  //    data.Store3(16 + 16, asuint(_tint_tmp[1]));
 
-  auto* str = create<ast::Struct>(
-      ast::StructMemberList{Member("z", ty.i32(), {MemberOffset(0)}),
-                            Member("a", ty.mat2x3<f32>(), {MemberOffset(4)})},
-      ast::DecorationList{});
+  auto* s =
+      Structure("Data", {Member("z", ty.i32()), Member("a", ty.mat2x3<f32>())});
 
-  auto* s = ty.struct_("Data", str);
   auto* coord_var = Global("data", s, ast::StorageClass::kStorage);
 
   auto* lhs = MemberAccessor("data", "a");
@@ -176,16 +165,16 @@
   EXPECT_EQ(
       result(),
       R"(float3x2 _tint_tmp = float3x2(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
-data.Store3(4 + 0, asuint(_tint_tmp[0]));
-data.Store3(4 + 16, asuint(_tint_tmp[1]));
+data.Store3(16 + 0, asuint(_tint_tmp[0]));
+data.Store3(16 + 16, asuint(_tint_tmp[1]));
 )");
 }
 
 TEST_F(HlslGeneratorImplTest_MemberAccessor,
        EmitExpression_MemberAccessor_StorageBuffer_Load_Matrix) {
   // struct Data {
-  //   [[offset(0)]] z : f32;
-  //   [[offset(4)]] a : mat3x2<f32>;
+  //   z : f32;
+  //   a : mat3x2<f32>;
   // };
   // var<storage> data : Data;
   // data.a;
@@ -193,12 +182,9 @@
   // -> asfloat(uint2x3(data.Load2(4 + 0), data.Load2(4 + 8),
   // data.Load2(4 + 16)));
 
-  auto* str = create<ast::Struct>(
-      ast::StructMemberList{Member("z", ty.i32(), {MemberOffset(0)}),
-                            Member("a", ty.mat3x2<f32>(), {MemberOffset(4)})},
-      ast::DecorationList{});
+  auto* s =
+      Structure("Data", {Member("z", ty.i32()), Member("a", ty.mat3x2<f32>())});
 
-  auto* s = ty.struct_("Data", str);
   auto* coord_var = Global("data", s, ast::StorageClass::kStorage);
 
   auto* expr = MemberAccessor("data", "a");
@@ -210,33 +196,26 @@
 
   ASSERT_TRUE(gen.EmitExpression(pre, out, expr)) << gen.error();
   EXPECT_EQ(result(),
-            "asfloat(uint2x3(data.Load2(4 + 0), data.Load2(4 + 8), "
-            "data.Load2(4 + 16)))");
+            "asfloat(uint2x3(data.Load2(8 + 0), data.Load2(8 + 8), "
+            "data.Load2(8 + 16)))");
 }
 
 TEST_F(HlslGeneratorImplTest_MemberAccessor,
        EmitExpression_MemberAccessor_StorageBuffer_Load_Matrix_Nested) {
   // struct Data {
-  //   [[offset(0)]] z : f32;
-  //   [[offset(4)]] a : mat2x3<f32;
-  // };
-  // struct Outer {
-  //   [[offset(0)]] c : f32;
-  //   [[offset(4)]] b : Data;
+  //   z : f32;
+  //   a : mat2x3<f32>
   // };
   // var<storage> data : Outer;
   // data.b.a;
   //
-  // -> asfloat(uint3x2(data.Load3(4 + 0), data.Load3(4 + 16)));
+  // -> asfloat(uint3x2(data.Load3(4 + 0), data.Load3(16 + 16)));
 
-  auto* str = create<ast::Struct>(
-      ast::StructMemberList{
-          Member("z", ty.i32(), {MemberOffset(0)}),
-          Member("a", ty.mat2x3<f32>(), {MemberOffset(4)}),
-      },
-      ast::DecorationList{});
+  auto* s = Structure("Data", {
+                                  Member("z", ty.i32()),
+                                  Member("a", ty.mat2x3<f32>()),
+                              });
 
-  auto* s = ty.struct_("Data", str);
   auto* coord_var = Global("data", s, ast::StorageClass::kStorage);
 
   auto* expr = MemberAccessor("data", "a");
@@ -248,14 +227,14 @@
 
   ASSERT_TRUE(gen.EmitExpression(pre, out, expr)) << gen.error();
   EXPECT_EQ(result(),
-            "asfloat(uint3x2(data.Load3(4 + 0), data.Load3(4 + 16)))");
+            "asfloat(uint3x2(data.Load3(16 + 0), data.Load3(16 + 16)))");
 }
 
 TEST_F(
     HlslGeneratorImplTest_MemberAccessor,
     EmitExpression_MemberAccessor_StorageBuffer_Load_Matrix_By3_Is_16_Bytes) {
   // struct Data {
-  //   [[offset(4)]] a : mat3x3<f32;
+  //   a : mat3x3<f32>
   // };
   // var<storage> data : Data;
   // data.a;
@@ -263,11 +242,10 @@
   // -> asfloat(uint3x3(data.Load3(0), data.Load3(16),
   // data.Load3(32)));
 
-  auto* str = create<ast::Struct>(
-      ast::StructMemberList{Member("a", ty.mat3x3<f32>(), {MemberOffset(0)})},
-      ast::DecorationList{});
+  auto* s = Structure("Data", {
+                                  Member("a", ty.mat3x3<f32>()),
+                              });
 
-  auto* s = ty.struct_("Data", str);
   auto* coord_var = Global("data", s, ast::StorageClass::kStorage);
 
   auto* expr = MemberAccessor("data", "a");
@@ -286,20 +264,19 @@
 TEST_F(HlslGeneratorImplTest_MemberAccessor,
        EmitExpression_MemberAccessor_StorageBuffer_Load_Matrix_Single_Element) {
   // struct Data {
-  //   [[offset(0)]] z : f32;
-  //   [[offset(16)]] a : mat4x3<f32>;
+  //   z : f32;
+  //   a : mat4x3<f32>;
   // };
   // var<storage> data : Data;
   // data.a[2][1];
   //
   // -> asfloat(data.Load((2 * 16) + (1 * 4) + 16)))
 
-  auto* str = create<ast::Struct>(
-      ast::StructMemberList{Member("z", ty.i32(), {MemberOffset(0)}),
-                            Member("a", ty.mat4x3<f32>(), {MemberOffset(16)})},
-      ast::DecorationList{});
+  auto* s = Structure("Data", {
+                                  Member("z", ty.i32()),
+                                  Member("a", ty.mat4x3<f32>()),
+                              });
 
-  auto* s = ty.struct_("Data", str);
   auto* coord_var = Global("data", s, ast::StorageClass::kStorage);
 
   auto* expr = IndexAccessor(
@@ -317,7 +294,7 @@
 TEST_F(HlslGeneratorImplTest_MemberAccessor,
        EmitExpression_ArrayAccessor_StorageBuffer_Load_Int_FromArray) {
   // struct Data {
-  //   [[offset(0)]] a : [[stride(4)]] array<i32, 5>;
+  //   a : [[stride(4)]] array<i32, 5>;
   // };
   // var<storage> data : Data;
   // data.a[2];
@@ -328,10 +305,8 @@
                       create<ast::StrideDecoration>(4),
                   });
 
-  auto* str = create<ast::Struct>(
-      ast::StructMemberList{Member("a", &ary, {MemberOffset(0)})},
-      ast::DecorationList{});
-  auto* s = ty.struct_("Data", str);
+  auto* s = Structure("Data", {Member("a", &ary)});
+
   auto* coord_var = Global("data", s, ast::StorageClass::kStorage);
 
   auto* expr = IndexAccessor(MemberAccessor("data", "a"), Expr(2));
@@ -348,7 +323,7 @@
 TEST_F(HlslGeneratorImplTest_MemberAccessor,
        EmitExpression_ArrayAccessor_StorageBuffer_Load_Int_FromArray_ExprIdx) {
   // struct Data {
-  //   [[offset(0)]] a : [[stride(4)]] array<i32, 5>;
+  //   a : [[stride(4)]] array<i32, 5>;
   // };
   // var<storage> data : Data;
   // data.a[(2 + 4) - 3];
@@ -359,10 +334,8 @@
                       create<ast::StrideDecoration>(4),
                   });
 
-  auto* str = create<ast::Struct>(
-      ast::StructMemberList{Member("a", &ary, {MemberOffset(0)})},
-      ast::DecorationList{});
-  auto* s = ty.struct_("Data", str);
+  auto* s = Structure("Data", {Member("a", &ary)});
+
   auto* coord_var = Global("data", s, ast::StorageClass::kStorage);
 
   auto* expr = IndexAccessor(MemberAccessor("data", "a"),
@@ -380,20 +353,19 @@
 TEST_F(HlslGeneratorImplTest_MemberAccessor,
        EmitExpression_MemberAccessor_StorageBuffer_Store) {
   // struct Data {
-  //   [[offset(0)]] a : i32;
-  //   [[offset(4)]] b : f32;
+  //   a : i32;
+  //   b : f32;
   // };
   // var<storage> data : Data;
   // data.b = 2.3f;
   //
   // -> data.Store(0, asuint(2.0f));
 
-  auto* str = create<ast::Struct>(
-      ast::StructMemberList{Member("a", ty.i32(), {MemberOffset(0)}),
-                            Member("b", ty.f32(), {MemberOffset(4)})},
-      ast::DecorationList{});
+  auto* s = Structure("data", {
+                                  Member("a", ty.i32()),
+                                  Member("b", ty.f32()),
+                              });
 
-  auto* s = ty.struct_("Data", str);
   auto* coord_var = Global("data", s, ast::StorageClass::kStorage);
 
   auto* lhs = MemberAccessor("data", "b");
@@ -413,7 +385,7 @@
 TEST_F(HlslGeneratorImplTest_MemberAccessor,
        EmitExpression_MemberAccessor_StorageBuffer_Store_ToArray) {
   // struct Data {
-  //   [[offset(0)]] a : [[stride(4)]] array<i32, 5>;
+  //   a : [[stride(4)]] array<i32, 5>;
   // };
   // var<storage> data : Data;
   // data.a[2] = 2;
@@ -425,11 +397,8 @@
                       create<ast::StrideDecoration>(4),
                   });
 
-  auto* str = create<ast::Struct>(
-      ast::StructMemberList{Member("a", &ary, {MemberOffset(0)})},
-      ast::DecorationList{});
+  auto* s = Structure("Data", {Member("a", &ary)});
 
-  auto* s = ty.struct_("Data", str);
   auto* coord_var = Global("data", s, ast::StorageClass::kStorage);
 
   auto* lhs = IndexAccessor(MemberAccessor("data", "a"), Expr(2));
@@ -449,20 +418,19 @@
 TEST_F(HlslGeneratorImplTest_MemberAccessor,
        EmitExpression_MemberAccessor_StorageBuffer_Store_Int) {
   // struct Data {
-  //   [[offset(0)]] a : i32;
-  //   [[offset(4)]] b : f32;
+  //   a : i32;
+  //   b : f32;
   // };
   // var<storage> data : Data;
   // data.a = 2;
   //
   // -> data.Store(0, asuint(2));
 
-  auto* str = create<ast::Struct>(
-      ast::StructMemberList{Member("a", ty.i32(), {MemberOffset(0)}),
-                            Member("b", ty.f32(), {MemberOffset(4)})},
-      ast::DecorationList{});
+  auto* s = Structure("data", {
+                                  Member("a", ty.i32()),
+                                  Member("b", ty.f32()),
+                              });
 
-  auto* s = ty.struct_("Data", str);
   auto* coord_var = Global("data", s, ast::StorageClass::kStorage);
 
   auto* lhs = MemberAccessor("data", "a");
@@ -482,20 +450,19 @@
 TEST_F(HlslGeneratorImplTest_MemberAccessor,
        EmitExpression_MemberAccessor_StorageBuffer_Load_Vec3) {
   // struct Data {
-  //   [[offset(0)]] a : vec3<i32>;
-  //   [[offset(16)]] b : vec3<f32>;
+  //   a : vec3<i32>;
+  //   b : vec3<f32>;
   // };
   // var<storage> data : Data;
   // data.b;
   //
   // -> asfloat(data.Load(16));
 
-  auto* str = create<ast::Struct>(
-      ast::StructMemberList{Member("a", ty.vec3<i32>(), {MemberOffset(0)}),
-                            Member("b", ty.vec3<f32>(), {MemberOffset(16)})},
-      ast::DecorationList{});
+  auto* s = Structure("Data", {
+                                  Member("a", ty.vec3<i32>()),
+                                  Member("b", ty.vec3<f32>()),
+                              });
 
-  auto* s = ty.struct_("Data", str);
   auto* coord_var = Global("data", s, ast::StorageClass::kStorage);
 
   auto* expr = MemberAccessor("data", "b");
@@ -512,20 +479,19 @@
 TEST_F(HlslGeneratorImplTest_MemberAccessor,
        EmitExpression_MemberAccessor_StorageBuffer_Store_Vec3) {
   // struct Data {
-  //   [[offset(0)]] a : vec3<i32>;
-  //   [[offset(16)]] b : vec3<f32>;
+  //   a : vec3<i32>;
+  //   b : vec3<f32>;
   // };
   // var<storage> data : Data;
   // data.b = vec3<f32>(2.3f, 1.2f, 0.2f);
   //
   // -> data.Store(16, asuint(float3(2.3f, 1.2f, 0.2f)));
 
-  auto* str = create<ast::Struct>(
-      ast::StructMemberList{Member("a", ty.vec3<i32>(), {MemberOffset(0)}),
-                            Member("b", ty.vec3<f32>(), {MemberOffset(16)})},
-      ast::DecorationList{});
+  auto* s = Structure("Data", {
+                                  Member("a", ty.vec3<i32>()),
+                                  Member("b", ty.vec3<f32>()),
+                              });
 
-  auto* s = ty.struct_("Data", str);
   auto* coord_var = Global("data", s, ast::StorageClass::kStorage);
 
   auto* lhs = MemberAccessor("data", "b");
@@ -548,8 +514,8 @@
 TEST_F(HlslGeneratorImplTest_MemberAccessor,
        EmitExpression_MemberAccessor_StorageBuffer_Load_MultiLevel) {
   // struct Data {
-  //   [[offset(0)]] a : vec3<i32>;
-  //   [[offset(16)]] b : vec3<f32>;
+  //   a : vec3<i32>;
+  //   b : vec3<f32>;
   // };
   // struct Pre {
   //   var c : [[stride(32)]] array<Data, 4>;
@@ -560,24 +526,18 @@
   //
   // -> asfloat(data.Load3(16 + (2 * 32)))
 
-  auto* data_str = create<ast::Struct>(
-      ast::StructMemberList{
-          Member("a", ty.vec3<i32>(), {MemberOffset(0)}),
-          Member("b", ty.vec3<f32>(), {MemberOffset(16)}),
-      },
-      ast::DecorationList{});
+  auto* data = Structure("Data", {
+                                     Member("a", ty.vec3<i32>()),
+                                     Member("b", ty.vec3<f32>()),
+                                 });
 
-  auto* data = ty.struct_("Data", data_str);
   type::Array ary(data, 4,
                   ast::DecorationList{
                       create<ast::StrideDecoration>(32),
                   });
 
-  auto* pre_str = create<ast::Struct>(
-      ast::StructMemberList{Member("c", &ary, {MemberOffset(0)})},
-      ast::DecorationList{});
+  auto* pre_struct = Structure("Pre", {Member("c", &ary)});
 
-  auto* pre_struct = ty.struct_("Pre", pre_str);
   auto* coord_var = Global("data", pre_struct, ast::StorageClass::kStorage);
 
   auto* expr =
@@ -595,8 +555,8 @@
 TEST_F(HlslGeneratorImplTest_MemberAccessor,
        EmitExpression_MemberAccessor_StorageBuffer_Load_MultiLevel_Swizzle) {
   // struct Data {
-  //   [[offset(0)]] a : vec3<i32>;
-  //   [[offset(16)]] b : vec3<f32>;
+  //   a : vec3<i32>;
+  //   b : vec3<f32>;
   // };
   // struct Pre {
   //   var c : [[stride(32)]] array<Data, 4>;
@@ -607,22 +567,16 @@
   //
   // -> asfloat(data.Load3(16 + (2 * 32))).xy
 
-  auto* data_str = create<ast::Struct>(
-      ast::StructMemberList{
-          Member("a", ty.vec3<i32>(), {MemberOffset(0)}),
-          Member("b", ty.vec3<f32>(), {MemberOffset(16)}),
-      },
-      ast::DecorationList{});
+  auto* data = Structure("Data", {
+                                     Member("a", ty.vec3<i32>()),
+                                     Member("b", ty.vec3<f32>()),
+                                 });
 
-  auto* data = ty.struct_("Data", data_str);
   type::Array ary(data, 4,
                   ast::DecorationList{create<ast::StrideDecoration>(32)});
 
-  auto* pre_str = create<ast::Struct>(
-      ast::StructMemberList{Member("c", &ary, {MemberOffset(0)})},
-      ast::DecorationList{});
+  auto* pre_struct = Structure("Pre", {Member("c", &ary)});
 
-  auto* pre_struct = ty.struct_("Pre", pre_str);
   auto* coord_var = Global("data", pre_struct, ast::StorageClass::kStorage);
 
   auto* expr = MemberAccessor(
@@ -642,8 +596,8 @@
     HlslGeneratorImplTest_MemberAccessor,
     EmitExpression_MemberAccessor_StorageBuffer_Load_MultiLevel_Swizzle_SingleLetter) {  // NOLINT
   // struct Data {
-  //   [[offset(0)]] a : vec3<i32>;
-  //   [[offset(16)]] b : vec3<f32>;
+  //   a : vec3<i32>;
+  //   b : vec3<f32>;
   // };
   // struct Pre {
   //   var c : [[stride(32)]] array<Data, 4>;
@@ -654,24 +608,18 @@
   //
   // -> asfloat(data.Load((4 * 1) + 16 + (2 * 32) + 0))
 
-  auto* data_str = create<ast::Struct>(
-      ast::StructMemberList{
-          Member("a", ty.vec3<i32>(), {MemberOffset(0)}),
-          Member("b", ty.vec3<f32>(), {MemberOffset(16)}),
-      },
-      ast::DecorationList{});
+  auto* data = Structure("Data", {
+                                     Member("a", ty.vec3<i32>()),
+                                     Member("b", ty.vec3<f32>()),
+                                 });
 
-  auto* data = ty.struct_("Data", data_str);
   type::Array ary(data, 4,
                   ast::DecorationList{
                       create<ast::StrideDecoration>(32),
                   });
 
-  auto* pre_str = create<ast::Struct>(
-      ast::StructMemberList{Member("c", &ary, {MemberOffset(0)})},
-      ast::DecorationList{});
+  auto* pre_struct = Structure("Pre", {Member("c", &ary)});
 
-  auto* pre_struct = ty.struct_("Pre", pre_str);
   auto* coord_var = Global("data", pre_struct, ast::StorageClass::kStorage);
 
   auto* expr = MemberAccessor(
@@ -690,8 +638,8 @@
 TEST_F(HlslGeneratorImplTest_MemberAccessor,
        EmitExpression_MemberAccessor_StorageBuffer_Load_MultiLevel_Index) {
   // struct Data {
-  //   [[offset(0)]] a : vec3<i32>;
-  //   [[offset(16)]] b : vec3<f32>;
+  //   a : vec3<i32>;
+  //   b : vec3<f32>;
   // };
   // struct Pre {
   //   var c : [[stride(32)]] array<Data, 4>;
@@ -702,24 +650,18 @@
   //
   // -> asfloat(data.Load(4 + 16 + (2 * 32)))
 
-  auto* data_str = create<ast::Struct>(
-      ast::StructMemberList{
-          Member("a", ty.vec3<i32>(), {MemberOffset(0)}),
-          Member("b", ty.vec3<f32>(), {MemberOffset(16)}),
-      },
-      ast::DecorationList{});
+  auto* data = Structure("Data", {
+                                     Member("a", ty.vec3<i32>()),
+                                     Member("b", ty.vec3<f32>()),
+                                 });
 
-  auto* data = ty.struct_("Data", data_str);
   type::Array ary(data, 4,
                   ast::DecorationList{
                       create<ast::StrideDecoration>(32),
                   });
 
-  auto* pre_str = create<ast::Struct>(
-      ast::StructMemberList{Member("c", &ary, {MemberOffset(0)})},
-      ast::DecorationList{});
+  auto* pre_struct = Structure("Pre", {Member("c", &ary)});
 
-  auto* pre_struct = ty.struct_("Pre", pre_str);
   auto* coord_var = Global("data", pre_struct, ast::StorageClass::kStorage);
 
   auto* expr = IndexAccessor(
@@ -738,8 +680,8 @@
 TEST_F(HlslGeneratorImplTest_MemberAccessor,
        EmitExpression_MemberAccessor_StorageBuffer_Store_MultiLevel) {
   // struct Data {
-  //   [[offset(0)]] a : vec3<i32>;
-  //   [[offset(16)]] b : vec3<f32>;
+  //   a : vec3<i32>;
+  //   b : vec3<f32>;
   // };
   // struct Pre {
   //   var c : [[stride(32)]] array<Data, 4>;
@@ -750,24 +692,18 @@
   //
   // -> data.Store3(16 + (2 * 32), asuint(float3(1.0f, 2.0f, 3.0f)));
 
-  auto* data_str = create<ast::Struct>(
-      ast::StructMemberList{
-          Member("a", ty.vec3<i32>(), {MemberOffset(0)}),
-          Member("b", ty.vec3<f32>(), {MemberOffset(16)}),
-      },
-      ast::DecorationList{});
+  auto* data = Structure("Data", {
+                                     Member("a", ty.vec3<i32>()),
+                                     Member("b", ty.vec3<f32>()),
+                                 });
 
-  auto* data = ty.struct_("Data", data_str);
   type::Array ary(data, 4,
                   ast::DecorationList{
                       create<ast::StrideDecoration>(32),
                   });
 
-  auto* pre_str = create<ast::Struct>(
-      ast::StructMemberList{Member("c", &ary, {MemberOffset(0)})},
-      ast::DecorationList{});
+  auto* pre_struct = Structure("Pre", {Member("c", &ary)});
 
-  auto* pre_struct = ty.struct_("Pre", pre_str);
   auto* coord_var = Global("data", pre_struct, ast::StorageClass::kStorage);
 
   auto* lhs =
@@ -791,8 +727,8 @@
 TEST_F(HlslGeneratorImplTest_MemberAccessor,
        EmitExpression_MemberAccessor_StorageBuffer_Store_Swizzle_SingleLetter) {
   // struct Data {
-  //   [[offset(0)]] a : vec3<i32>;
-  //   [[offset(16)]] b : vec3<f32>;
+  //   a : vec3<i32>;
+  //   b : vec3<f32>;
   // };
   // struct Pre {
   //   var c : [[stride(32)]] array<Data, 4>;
@@ -803,24 +739,18 @@
   //
   // -> data.Store((4 * 1) + 16 + (2 * 32) + 0, asuint(1.0f));
 
-  auto* data_str = create<ast::Struct>(
-      ast::StructMemberList{
-          Member("a", ty.vec3<i32>(), {MemberOffset(0)}),
-          Member("b", ty.vec3<f32>(), {MemberOffset(16)}),
-      },
-      ast::DecorationList{});
+  auto* data = Structure("Data", {
+                                     Member("a", ty.vec3<i32>()),
+                                     Member("b", ty.vec3<f32>()),
+                                 });
 
-  auto* data = ty.struct_("Data", data_str);
   type::Array ary(data, 4,
                   ast::DecorationList{
                       create<ast::StrideDecoration>(32),
                   });
 
-  auto* pre_str = create<ast::Struct>(
-      ast::StructMemberList{Member("c", &ary, {MemberOffset(0)})},
-      ast::DecorationList{});
+  auto* pre_struct = Structure("Pre", {Member("c", &ary)});
 
-  auto* pre_struct = ty.struct_("Pre", pre_str);
   auto* coord_var = Global("data", pre_struct, ast::StorageClass::kStorage);
 
   auto* lhs = MemberAccessor(
diff --git a/src/writer/hlsl/generator_impl_type_test.cc b/src/writer/hlsl/generator_impl_type_test.cc
index 23a9ef7..5353b5c 100644
--- a/src/writer/hlsl/generator_impl_type_test.cc
+++ b/src/writer/hlsl/generator_impl_type_test.cc
@@ -167,12 +167,10 @@
 }
 
 TEST_F(HlslGeneratorImplTest_Type, EmitType_StructDecl) {
-  auto* str = create<ast::Struct>(
-      ast::StructMemberList{Member("a", ty.i32()),
-                            Member("b", ty.f32(), {MemberOffset(4)})},
-      ast::DecorationList{});
-
-  auto* s = ty.struct_("S", str);
+  auto* s = Structure("S", {
+                               Member("a", ty.i32()),
+                               Member("b", ty.f32()),
+                           });
 
   GeneratorImpl& gen = Build();
 
@@ -185,12 +183,10 @@
 }
 
 TEST_F(HlslGeneratorImplTest_Type, EmitType_Struct) {
-  auto* str = create<ast::Struct>(
-      ast::StructMemberList{Member("a", ty.i32()),
-                            Member("b", ty.f32(), {MemberOffset(4)})},
-      ast::DecorationList{});
-
-  auto* s = ty.struct_("S", str);
+  auto* s = Structure("S", {
+                               Member("a", ty.i32()),
+                               Member("b", ty.f32()),
+                           });
 
   GeneratorImpl& gen = Build();
 
@@ -198,35 +194,35 @@
   EXPECT_EQ(result(), "S");
 }
 
+/// TODO(bclayton): Enable this, fix it, add tests for vector, matrix, array and
+/// nested structures.
 TEST_F(HlslGeneratorImplTest_Type, DISABLED_EmitType_Struct_InjectPadding) {
-  auto* str = create<ast::Struct>(
-      ast::StructMemberList{Member("a", ty.i32(), {MemberOffset(4)}),
-                            Member("b", ty.f32(), {MemberOffset(32)}),
-                            Member("c", ty.f32(), {MemberOffset(128)})},
-      ast::DecorationList{});
-
-  auto* s = ty.struct_("S", str);
+  auto* s = Structure(
+      "S", {
+               Member("a", ty.i32(), {MemberSize(32)}),
+               Member("b", ty.f32()),
+               Member("c", ty.f32(), {MemberAlign(128), MemberSize(128)}),
+           });
 
   GeneratorImpl& gen = Build();
 
   ASSERT_TRUE(gen.EmitType(out, s, "")) << gen.error();
-  EXPECT_EQ(result(), R"(struct {
-  int8_t pad_0[4];
+  EXPECT_EQ(gen.result(), R"(struct S {
   int a;
-  int8_t pad_1[24];
+  int8_t pad_0[28];
   float b;
-  int8_t pad_2[92];
+  int8_t pad_1[92];
   float c;
-})");
+  int8_t pad_2[124];
+};
+)");
 }
 
 TEST_F(HlslGeneratorImplTest_Type, EmitType_Struct_NameCollision) {
-  auto* str =
-      create<ast::Struct>(ast::StructMemberList{Member("double", ty.i32()),
-                                                Member("float", ty.f32())},
-                          ast::DecorationList{});
-
-  auto* s = ty.struct_("S", str);
+  auto* s = Structure("S", {
+                               Member("double", ty.i32()),
+                               Member("float", ty.f32()),
+                           });
 
   GeneratorImpl& gen = Build();
 
@@ -240,15 +236,12 @@
 
 // TODO(dsinclair): How to translate [[block]]
 TEST_F(HlslGeneratorImplTest_Type, DISABLED_EmitType_Struct_WithDecoration) {
-  ast::DecorationList decos;
-  decos.push_back(create<ast::StructBlockDecoration>());
-
-  auto* str = create<ast::Struct>(
-      ast::StructMemberList{Member("a", ty.i32()),
-                            Member("b", ty.f32(), {MemberOffset(4)})},
-      decos);
-
-  auto* s = ty.struct_("S", str);
+  auto* s = Structure("S",
+                      {
+                          Member("a", ty.i32()),
+                          Member("b", ty.f32()),
+                      },
+                      {create<ast::StructBlockDecoration>()});
 
   GeneratorImpl& gen = Build();
 
diff --git a/src/writer/msl/generator_impl.cc b/src/writer/msl/generator_impl.cc
index 00f9ffb..29b1e51 100644
--- a/src/writer/msl/generator_impl.cc
+++ b/src/writer/msl/generator_impl.cc
@@ -25,12 +25,12 @@
 #include "src/ast/float_literal.h"
 #include "src/ast/module.h"
 #include "src/ast/sint_literal.h"
-#include "src/ast/struct_member_offset_decoration.h"
 #include "src/ast/uint_literal.h"
 #include "src/ast/variable_decl_statement.h"
 #include "src/semantic/call.h"
 #include "src/semantic/function.h"
 #include "src/semantic/member_accessor_expression.h"
+#include "src/semantic/struct.h"
 #include "src/semantic/variable.h"
 #include "src/type/access_control_type.h"
 #include "src/type/alias_type.h"
@@ -68,14 +68,6 @@
          stmts->last()->Is<ast::FallthroughStatement>();
 }
 
-uint32_t adjust_for_alignment(uint32_t count, uint32_t alignment) {
-  const auto spill = count % alignment;
-  if (spill == 0) {
-    return count;
-  }
-  return count + alignment - spill;
-}
-
 }  // namespace
 
 GeneratorImpl::GeneratorImpl(const Program* program)
@@ -140,89 +132,6 @@
   return true;
 }
 
-uint32_t GeneratorImpl::calculate_largest_alignment(type::Struct* type) {
-  auto* stct = type->As<type::Struct>()->impl();
-  uint32_t largest_alignment = 0;
-  for (auto* mem : stct->members()) {
-    auto align = calculate_alignment_size(mem->type());
-    if (align == 0) {
-      return 0;
-    }
-    if (!mem->type()->Is<type::Struct>()) {
-      largest_alignment = std::max(largest_alignment, align);
-    } else {
-      largest_alignment = std::max(
-          largest_alignment,
-          calculate_largest_alignment(mem->type()->As<type::Struct>()));
-    }
-  }
-  return largest_alignment;
-}
-
-uint32_t GeneratorImpl::calculate_alignment_size(type::Type* type) {
-  if (auto* alias = type->As<type::Alias>()) {
-    return calculate_alignment_size(alias->type());
-  }
-  if (auto* ary = type->As<type::Array>()) {
-    // TODO(dsinclair): Handle array stride and adjust for alignment.
-    uint32_t type_size = calculate_alignment_size(ary->type());
-    return ary->size() * type_size;
-  }
-  if (type->Is<type::Bool>()) {
-    return 1;
-  }
-  if (type->Is<type::Pointer>()) {
-    return 0;
-  }
-  if (type->Is<type::F32>() || type->Is<type::I32>() || type->Is<type::U32>()) {
-    return 4;
-  }
-  if (auto* mat = type->As<type::Matrix>()) {
-    // TODO(dsinclair): Handle MatrixStride
-    // https://github.com/gpuweb/gpuweb/issues/773
-    uint32_t type_size = calculate_alignment_size(mat->type());
-    return mat->rows() * mat->columns() * type_size;
-  }
-  if (auto* stct_ty = type->As<type::Struct>()) {
-    auto* stct = stct_ty->impl();
-    uint32_t count = 0;
-    uint32_t largest_alignment = 0;
-    // Offset decorations in WGSL must be in increasing order.
-    for (auto* mem : stct->members()) {
-      for (auto* deco : mem->decorations()) {
-        if (auto* offset = deco->As<ast::StructMemberOffsetDecoration>()) {
-          count = offset->offset();
-        }
-      }
-      auto align = calculate_alignment_size(mem->type());
-      if (align == 0) {
-        return 0;
-      }
-      if (auto* str = mem->type()->As<type::Struct>()) {
-        largest_alignment =
-            std::max(largest_alignment, calculate_largest_alignment(str));
-      } else {
-        largest_alignment = std::max(largest_alignment, align);
-      }
-
-      // Round up to the alignment size
-      count = adjust_for_alignment(count, align);
-      count += align;
-    }
-    // Round struct up to largest align size
-    count = adjust_for_alignment(count, largest_alignment);
-    return count;
-  }
-  if (auto* vec = type->As<type::Vector>()) {
-    uint32_t type_size = calculate_alignment_size(vec->type());
-    if (vec->size() == 2) {
-      return 2 * type_size;
-    }
-    return 4 * type_size;
-  }
-  return 0;
-}
-
 bool GeneratorImpl::EmitConstructedType(const type::Type* ty) {
   make_indent();
 
@@ -2075,52 +1984,76 @@
   out_ << "struct " << program_->Symbols().NameFor(str->symbol()) << " {"
        << std::endl;
 
+  uint32_t pad_count = 0;
+  auto add_padding = [&](uint32_t size) {
+    out_ << "int8_t pad_" << pad_count << "[" << size << "];" << std::endl;
+    pad_count++;
+  };
+
   increment_indent();
   uint32_t current_offset = 0;
-  uint32_t pad_count = 0;
   for (auto* mem : str->impl()->members()) {
     std::string attributes;
 
     make_indent();
+
+    auto* sem_mem = program_->Sem().Get(mem);
+    if (!sem_mem) {
+      TINT_ICE(diagnostics_) << "struct member missing semantic info";
+      return false;
+    }
+
+    auto const offset = sem_mem->Offset();
+    if (offset != current_offset) {
+      add_padding(offset - current_offset);
+      make_indent();
+    }
+
     for (auto* deco : mem->decorations()) {
-      if (auto* o = deco->As<ast::StructMemberOffsetDecoration>()) {
-        uint32_t offset = o->offset();
-        if (offset != current_offset) {
-          out_ << "int8_t pad_" << pad_count << "[" << (offset - current_offset)
-               << "];" << std::endl;
-          pad_count++;
-          make_indent();
-        }
-        current_offset = offset;
-      } else if (auto* loc = deco->As<ast::LocationDecoration>()) {
+      if (auto* loc = deco->As<ast::LocationDecoration>()) {
         attributes = " [[user(locn" + std::to_string(loc->value()) + ")]]";
-      } else {
-        diagnostics_.add_error("unsupported member decoration: " +
-                               program_->str(deco));
-        return false;
       }
     }
 
     if (!EmitType(mem->type(), program_->Symbols().NameFor(mem->symbol()))) {
       return false;
     }
-    auto size = calculate_alignment_size(mem->type());
-    if (size == 0) {
-      diagnostics_.add_error("unable to calculate byte size for: " +
-                             mem->type()->type_name());
-      return false;
-    }
-    current_offset += size;
+
+    auto* ty = mem->type()->UnwrapAliasIfNeeded();
 
     // Array member name will be output with the type
-    if (!mem->type()->Is<type::Array>()) {
+    if (!ty->Is<type::Array>()) {
       out_ << " " << program_->Symbols().NameFor(mem->symbol());
     }
 
     out_ << attributes;
 
     out_ << ";" << std::endl;
+
+    if (ty->is_scalar()) {
+      current_offset = offset + 4;
+    } else if (ty->Is<type::Struct>()) {
+      /// Structure will already contain padding matching the WGSL size
+      current_offset = offset + sem_mem->Size();
+    } else {
+      /// TODO(bclayton): Implement for vector, matrix, array and nested
+      /// structures.
+      TINT_UNREACHABLE(diagnostics_)
+          << "Unhandled type " << ty->TypeInfo().name;
+      return false;
+    }
   }
+
+  auto* sem_str = program_->Sem().Get(str);
+  if (!sem_str) {
+    TINT_ICE(diagnostics_) << "struct missing semantic info";
+    return false;
+  }
+  if (sem_str->Size() != current_offset) {
+    make_indent();
+    add_padding(sem_str->Size() - current_offset);
+  }
+
   decrement_indent();
   make_indent();
 
diff --git a/src/writer/msl/generator_impl.h b/src/writer/msl/generator_impl.h
index 07c90e9..b5989b0 100644
--- a/src/writer/msl/generator_impl.h
+++ b/src/writer/msl/generator_impl.h
@@ -60,16 +60,6 @@
   /// @returns true on successful generation; false otherwise
   bool Generate();
 
-  /// Calculates the alignment size of the given `type`. This returns 0
-  /// for pointers as the size is unknown.
-  /// @param type the type to calculate the alignment size for
-  /// @returns the number of bytes used to align `type` or 0 on error
-  uint32_t calculate_alignment_size(type::Type* type);
-  /// Calculates the largest alignment seen within a struct
-  /// @param type the struct to calculate
-  /// @returns the largest alignment value
-  uint32_t calculate_largest_alignment(type::Struct* type);
-
   /// Handles generating a constructed
   /// @param ty the constructed type to generate
   /// @returns true if the constructed type was emitted
diff --git a/src/writer/msl/generator_impl_alias_type_test.cc b/src/writer/msl/generator_impl_alias_type_test.cc
index d663ba9..b5ec3df 100644
--- a/src/writer/msl/generator_impl_alias_type_test.cc
+++ b/src/writer/msl/generator_impl_alias_type_test.cc
@@ -32,12 +32,10 @@
 }
 
 TEST_F(MslGeneratorImplTest, EmitConstructedType_Struct) {
-  auto* str = create<ast::Struct>(
-      ast::StructMemberList{Member("a", ty.f32()),
-                            Member("b", ty.i32(), {MemberOffset(4)})},
-      ast::DecorationList{});
-
-  auto* s = ty.struct_("a", str);
+  auto* s = Structure("a", {
+                               Member("a", ty.f32()),
+                               Member("b", ty.i32()),
+                           });
 
   GeneratorImpl& gen = Build();
 
@@ -50,12 +48,11 @@
 }
 
 TEST_F(MslGeneratorImplTest, EmitConstructedType_AliasStructIdent) {
-  auto* str = create<ast::Struct>(
-      ast::StructMemberList{Member("a", ty.f32()),
-                            Member("b", ty.i32(), {MemberOffset(4)})},
-      ast::DecorationList{});
+  auto* s = Structure("b", {
+                               Member("a", ty.f32()),
+                               Member("b", ty.i32()),
+                           });
 
-  auto* s = ty.struct_("b", str);
   auto* alias = ty.alias("a", s);
 
   GeneratorImpl& gen = Build();
diff --git a/src/writer/msl/generator_impl_function_test.cc b/src/writer/msl/generator_impl_function_test.cc
index 543d0a8..8f4de92 100644
--- a/src/writer/msl/generator_impl_function_test.cc
+++ b/src/writer/msl/generator_impl_function_test.cc
@@ -243,17 +243,15 @@
 )");
 }
 
-TEST_F(MslGeneratorImplTest, Emit_Decoration_EntryPoint_With_RW_StorageBuffer) {
-  auto* str = create<ast::Struct>(
-      ast::StructMemberList{Member("a", ty.i32(), {MemberOffset(0)}),
-                            Member("b", ty.f32(), {MemberOffset(4)})},
-      ast::DecorationList{});
+TEST_F(MslGeneratorImplTest,
+       Emit_FunctionDecoration_EntryPoint_With_RW_StorageBuffer) {
+  auto* s = Structure("Data", {
+                                  Member("a", ty.i32()),
+                                  Member("b", ty.f32()),
+                              });
 
-  auto* s = ty.struct_("Data", str);
   type::AccessControl ac(ast::AccessControl::kReadWrite, s);
 
-  AST().AddConstructedType(s);
-
   Global("coord", &ac, ast::StorageClass::kStorage, nullptr,
          ast::DecorationList{create<ast::BindingDecoration>(0),
                              create<ast::GroupDecoration>(1)});
@@ -289,15 +287,14 @@
 )");
 }
 
-TEST_F(MslGeneratorImplTest, Emit_Decoration_EntryPoint_With_RO_StorageBuffer) {
-  auto* str = create<ast::Struct>(
-      ast::StructMemberList{Member("a", ty.i32(), {MemberOffset(0)}),
-                            Member("b", ty.f32(), {MemberOffset(4)})},
-      ast::DecorationList{});
+TEST_F(MslGeneratorImplTest,
+       Emit_FunctionDecoration_EntryPoint_With_RO_StorageBuffer) {
+  auto* s = Structure("Data", {
+                                  Member("a", ty.i32()),
+                                  Member("b", ty.f32()),
+                              });
 
-  auto* s = ty.struct_("Data", str);
   type::AccessControl ac(ast::AccessControl::kReadOnly, s);
-  AST().AddConstructedType(s);
 
   Global("coord", &ac, ast::StorageClass::kStorage, nullptr,
          ast::DecorationList{create<ast::BindingDecoration>(0),
@@ -555,15 +552,13 @@
 }
 
 TEST_F(MslGeneratorImplTest,
-       Emit_Decoration_Called_By_EntryPoint_With_RW_StorageBuffer) {
-  auto* str = create<ast::Struct>(
-      ast::StructMemberList{Member("a", ty.i32(), {MemberOffset(0)}),
-                            Member("b", ty.f32(), {MemberOffset(4)})},
-      ast::DecorationList{});
+       Emit_FunctionDecoration_Called_By_EntryPoint_With_RW_StorageBuffer) {
+  auto* s = Structure("Data", {
+                                  Member("a", ty.i32()),
+                                  Member("b", ty.f32()),
+                              });
 
-  auto* s = ty.struct_("Data", str);
   type::AccessControl ac(ast::AccessControl::kReadWrite, s);
-  AST().AddConstructedType(s);
 
   Global("coord", &ac, ast::StorageClass::kStorage, nullptr,
          ast::DecorationList{create<ast::BindingDecoration>(0),
@@ -613,15 +608,13 @@
 }
 
 TEST_F(MslGeneratorImplTest,
-       Emit_Decoration_Called_By_EntryPoint_With_RO_StorageBuffer) {
-  auto* str = create<ast::Struct>(
-      ast::StructMemberList{Member("a", ty.i32(), {MemberOffset(0)}),
-                            Member("b", ty.f32(), {MemberOffset(4)})},
-      ast::DecorationList{});
+       Emit_FunctionDecoration_Called_By_EntryPoint_With_RO_StorageBuffer) {
+  auto* s = Structure("Data", {
+                                  Member("a", ty.i32()),
+                                  Member("b", ty.f32()),
+                              });
 
-  auto* s = ty.struct_("Data", str);
   type::AccessControl ac(ast::AccessControl::kReadOnly, s);
-  AST().AddConstructedType(s);
 
   Global("coord", &ac, ast::StorageClass::kStorage, nullptr,
          ast::DecorationList{create<ast::BindingDecoration>(0),
@@ -746,7 +739,7 @@
 TEST_F(MslGeneratorImplTest,
        Emit_Function_Multiple_EntryPoint_With_Same_ModuleVar) {
   // [[block]] struct Data {
-  //   [[offset(0)]] d : f32;
+  //   d : f32;
   // };
   // [[binding(0), group(0)]] var<storage> data : Data;
   //
@@ -760,21 +753,15 @@
   //   return;
   // }
 
-  ast::DecorationList s_decos;
-  s_decos.push_back(create<ast::StructBlockDecoration>());
+  auto* s = Structure("Data", {Member("d", ty.f32())},
+                      {create<ast::StructBlockDecoration>()});
 
-  auto* str = create<ast::Struct>(
-      ast::StructMemberList{Member("d", ty.f32(), {MemberOffset(0)})}, s_decos);
-
-  auto* s = ty.struct_("Data", str);
   type::AccessControl ac(ast::AccessControl::kReadWrite, s);
 
   Global("data", &ac, ast::StorageClass::kStorage, nullptr,
          ast::DecorationList{create<ast::BindingDecoration>(0),
                              create<ast::GroupDecoration>(0)});
 
-  AST().AddConstructedType(s);
-
   {
     auto* var = Var("v", ty.f32(), ast::StorageClass::kFunction,
                     MemberAccessor("data", "d"));
diff --git a/src/writer/msl/generator_impl_test.cc b/src/writer/msl/generator_impl_test.cc
index 9c6dfad..1fa7776 100644
--- a/src/writer/msl/generator_impl_test.cc
+++ b/src/writer/msl/generator_impl_test.cc
@@ -78,178 +78,6 @@
                     MslBuiltinData{ast::Builtin::kSampleMaskOut,
                                    "sample_mask"}));
 
-TEST_F(MslGeneratorImplTest, calculate_alignment_size_alias) {
-  auto* alias = ty.alias("a", ty.f32());
-
-  GeneratorImpl& gen = Build();
-
-  EXPECT_EQ(4u, gen.calculate_alignment_size(alias));
-}
-
-TEST_F(MslGeneratorImplTest, calculate_alignment_size_array) {
-  auto* array = ty.array<f32, 4>();
-
-  GeneratorImpl& gen = Build();
-
-  EXPECT_EQ(4u * 4u, gen.calculate_alignment_size(array));
-}
-
-TEST_F(MslGeneratorImplTest, calculate_alignment_size_bool) {
-  auto* bool_ = ty.bool_();
-
-  GeneratorImpl& gen = Build();
-
-  EXPECT_EQ(1u, gen.calculate_alignment_size(bool_));
-}
-
-TEST_F(MslGeneratorImplTest, calculate_alignment_size_f32) {
-  auto* f32 = ty.f32();
-
-  GeneratorImpl& gen = Build();
-
-  EXPECT_EQ(4u, gen.calculate_alignment_size(f32));
-}
-
-TEST_F(MslGeneratorImplTest, calculate_alignment_size_i32) {
-  auto* i32 = ty.i32();
-
-  GeneratorImpl& gen = Build();
-
-  EXPECT_EQ(4u, gen.calculate_alignment_size(i32));
-}
-
-TEST_F(MslGeneratorImplTest, calculate_alignment_size_matrix) {
-  auto* mat3x2 = ty.mat3x2<f32>();
-
-  GeneratorImpl& gen = Build();
-
-  EXPECT_EQ(4u * 3u * 2u, gen.calculate_alignment_size(mat3x2));
-}
-
-TEST_F(MslGeneratorImplTest, calculate_alignment_size_pointer) {
-  type::Pointer ptr(ty.bool_(), ast::StorageClass::kPrivate);
-
-  GeneratorImpl& gen = Build();
-
-  EXPECT_EQ(0u, gen.calculate_alignment_size(&ptr));
-}
-
-TEST_F(MslGeneratorImplTest, calculate_alignment_size_struct) {
-  auto* str = create<ast::Struct>(
-      ast::StructMemberList{Member("a", ty.i32(), {MemberOffset(4)}),
-                            Member("b", ty.f32(), {MemberOffset(32)}),
-                            Member("c", ty.f32(), {MemberOffset(128)})},
-      ast::DecorationList{});
-
-  auto* s = ty.struct_("S", str);
-
-  GeneratorImpl& gen = Build();
-
-  EXPECT_EQ(132u, gen.calculate_alignment_size(s));
-}
-
-TEST_F(MslGeneratorImplTest, calculate_alignment_size_struct_of_struct) {
-  auto* inner_str = create<ast::Struct>(
-      ast::StructMemberList{Member("a", ty.i32(), {MemberOffset(0)}),
-                            Member("b", ty.vec3<f32>(), {MemberOffset(16)}),
-                            Member("c", ty.f32(), {MemberOffset(32)})},
-      ast::DecorationList{});
-
-  auto* inner_s = ty.struct_("Inner", inner_str);
-
-  auto* outer_str = create<ast::Struct>(
-      ast::StructMemberList{Member("d", ty.f32(), {MemberOffset(0)}),
-                            Member("e", inner_s, {MemberOffset(32)}),
-                            Member("f", ty.f32(), {MemberOffset(64)})},
-      ast::DecorationList{});
-
-  auto* outer_s = ty.struct_("Outer", outer_str);
-
-  GeneratorImpl& gen = Build();
-
-  EXPECT_EQ(80u, gen.calculate_alignment_size(outer_s));
-}
-
-TEST_F(MslGeneratorImplTest, calculate_alignment_size_u32) {
-  auto* u32 = ty.u32();
-
-  GeneratorImpl& gen = Build();
-
-  EXPECT_EQ(4u, gen.calculate_alignment_size(u32));
-}
-
-struct MslVectorSizeData {
-  uint32_t elements;
-  uint32_t byte_size;
-};
-inline std::ostream& operator<<(std::ostream& out, MslVectorSizeData data) {
-  out << data.elements;
-  return out;
-}
-using MslVectorSizeBoolTest = TestParamHelper<MslVectorSizeData>;
-TEST_P(MslVectorSizeBoolTest, calculate) {
-  auto param = GetParam();
-
-  type::Vector vec(ty.bool_(), param.elements);
-
-  GeneratorImpl& gen = Build();
-
-  EXPECT_EQ(param.byte_size, gen.calculate_alignment_size(&vec));
-}
-INSTANTIATE_TEST_SUITE_P(MslGeneratorImplTest,
-                         MslVectorSizeBoolTest,
-                         testing::Values(MslVectorSizeData{2u, 2u},
-                                         MslVectorSizeData{3u, 4u},
-                                         MslVectorSizeData{4u, 4u}));
-
-using MslVectorSizeI32Test = TestParamHelper<MslVectorSizeData>;
-TEST_P(MslVectorSizeI32Test, calculate) {
-  auto param = GetParam();
-
-  type::Vector vec(ty.i32(), param.elements);
-
-  GeneratorImpl& gen = Build();
-
-  EXPECT_EQ(param.byte_size, gen.calculate_alignment_size(&vec));
-}
-INSTANTIATE_TEST_SUITE_P(MslGeneratorImplTest,
-                         MslVectorSizeI32Test,
-                         testing::Values(MslVectorSizeData{2u, 8u},
-                                         MslVectorSizeData{3u, 16u},
-                                         MslVectorSizeData{4u, 16u}));
-
-using MslVectorSizeU32Test = TestParamHelper<MslVectorSizeData>;
-TEST_P(MslVectorSizeU32Test, calculate) {
-  auto param = GetParam();
-
-  type::Vector vec(ty.u32(), param.elements);
-
-  GeneratorImpl& gen = Build();
-
-  EXPECT_EQ(param.byte_size, gen.calculate_alignment_size(&vec));
-}
-INSTANTIATE_TEST_SUITE_P(MslGeneratorImplTest,
-                         MslVectorSizeU32Test,
-                         testing::Values(MslVectorSizeData{2u, 8u},
-                                         MslVectorSizeData{3u, 16u},
-                                         MslVectorSizeData{4u, 16u}));
-
-using MslVectorSizeF32Test = TestParamHelper<MslVectorSizeData>;
-TEST_P(MslVectorSizeF32Test, calculate) {
-  auto param = GetParam();
-
-  type::Vector vec(ty.f32(), param.elements);
-
-  GeneratorImpl& gen = Build();
-
-  EXPECT_EQ(param.byte_size, gen.calculate_alignment_size(&vec));
-}
-INSTANTIATE_TEST_SUITE_P(MslGeneratorImplTest,
-                         MslVectorSizeF32Test,
-                         testing::Values(MslVectorSizeData{2u, 8u},
-                                         MslVectorSizeData{3u, 16u},
-                                         MslVectorSizeData{4u, 16u}));
-
 }  // namespace
 }  // namespace msl
 }  // namespace writer
diff --git a/src/writer/msl/generator_impl_type_test.cc b/src/writer/msl/generator_impl_type_test.cc
index 1e99815..ebd0f72 100644
--- a/src/writer/msl/generator_impl_type_test.cc
+++ b/src/writer/msl/generator_impl_type_test.cc
@@ -143,12 +143,10 @@
 }
 
 TEST_F(MslGeneratorImplTest, EmitType_Struct) {
-  auto* str = create<ast::Struct>(
-      ast::StructMemberList{Member("a", ty.i32()),
-                            Member("b", ty.f32(), {MemberOffset(4)})},
-      ast::DecorationList{});
-
-  auto* s = ty.struct_("S", str);
+  auto* s = Structure("S", {
+                               Member("a", ty.i32()),
+                               Member("b", ty.f32()),
+                           });
 
   GeneratorImpl& gen = Build();
 
@@ -157,12 +155,10 @@
 }
 
 TEST_F(MslGeneratorImplTest, EmitType_StructDecl) {
-  auto* str = create<ast::Struct>(
-      ast::StructMemberList{Member("a", ty.i32()),
-                            Member("b", ty.f32(), {MemberOffset(4)})},
-      ast::DecorationList{});
-
-  auto* s = ty.struct_("S", str);
+  auto* s = Structure("S", {
+                               Member("a", ty.i32()),
+                               Member("b", ty.f32()),
+                           });
 
   GeneratorImpl& gen = Build();
 
@@ -174,41 +170,37 @@
 )");
 }
 
+/// TODO(bclayton): Add tests for vector, matrix, array and nested structures.
 TEST_F(MslGeneratorImplTest, EmitType_Struct_InjectPadding) {
-  auto* str = create<ast::Struct>(
-      ast::StructMemberList{
-          Member("a", ty.i32(), {MemberOffset(4)}),
-          Member("b", ty.f32(), {MemberOffset(32)}),
-          Member("c", ty.f32(), {MemberOffset(128)}),
-      },
-      ast::DecorationList{});
-
-  auto* s = ty.struct_("S", str);
+  auto* s = Structure(
+      "S", {
+               Member("a", ty.i32(), {MemberSize(32)}),
+               Member("b", ty.f32()),
+               Member("c", ty.f32(), {MemberAlign(128), MemberSize(128)}),
+           });
 
   GeneratorImpl& gen = Build();
 
   ASSERT_TRUE(gen.EmitStructType(s)) << gen.error();
   EXPECT_EQ(gen.result(), R"(struct S {
-  int8_t pad_0[4];
   int a;
-  int8_t pad_1[24];
+  int8_t pad_0[28];
   float b;
-  int8_t pad_2[92];
+  int8_t pad_1[92];
   float c;
+  int8_t pad_2[124];
 };
 )");
 }
 
 // TODO(dsinclair): How to translate [[block]]
 TEST_F(MslGeneratorImplTest, DISABLED_EmitType_Struct_WithDecoration) {
-  ast::DecorationList decos;
-  decos.push_back(create<ast::StructBlockDecoration>());
-  auto* str = create<ast::Struct>(
-      ast::StructMemberList{Member("a", ty.i32()),
-                            Member("b", ty.f32(), {MemberOffset(4)})},
-      decos);
-
-  auto* s = ty.struct_("S", str);
+  auto* s = Structure("S",
+                      {
+                          Member("a", ty.i32()),
+                          Member("b", ty.f32()),
+                      },
+                      {create<ast::StructBlockDecoration>()});
 
   GeneratorImpl& gen = Build();
 
diff --git a/src/writer/msl/generator_impl_variable_decl_statement_test.cc b/src/writer/msl/generator_impl_variable_decl_statement_test.cc
index 095aa9d..e67fcdb 100644
--- a/src/writer/msl/generator_impl_variable_decl_statement_test.cc
+++ b/src/writer/msl/generator_impl_variable_decl_statement_test.cc
@@ -64,12 +64,11 @@
 }
 
 TEST_F(MslGeneratorImplTest, Emit_VariableDeclStatement_Struct) {
-  auto* str = create<ast::Struct>(
-      ast::StructMemberList{Member("a", ty.f32()),
-                            Member("b", ty.f32(), {MemberOffset(4)})},
-      ast::DecorationList{});
+  auto* s = Structure("S", {
+                               Member("a", ty.f32()),
+                               Member("b", ty.f32()),
+                           });
 
-  auto* s = ty.struct_("S", str);
   auto* var = Var("a", s, ast::StorageClass::kNone);
   auto* stmt = create<ast::VariableDeclStatement>(var);
   WrapInFunction(stmt);
diff --git a/src/writer/spirv/builder.cc b/src/writer/spirv/builder.cc
index 4a066fc..71054c8 100644
--- a/src/writer/spirv/builder.cc
+++ b/src/writer/spirv/builder.cc
@@ -22,8 +22,11 @@
 #include "src/ast/constant_id_decoration.h"
 #include "src/ast/fallthrough_statement.h"
 #include "src/ast/null_literal.h"
+#include "src/semantic/array.h"
 #include "src/semantic/call.h"
 #include "src/semantic/function.h"
+#include "src/semantic/intrinsic.h"
+#include "src/semantic/struct.h"
 #include "src/semantic/variable.h"
 #include "src/type/depth_texture_type.h"
 #include "src/type/multisampled_texture_type.h"
@@ -2961,11 +2964,14 @@
               {result, Operand::Int(elem_type), Operand::Int(len_id)});
   }
 
-  if (ary->has_array_stride()) {
-    push_annot(spv::Op::OpDecorate,
-               {Operand::Int(result_id), Operand::Int(SpvDecorationArrayStride),
-                Operand::Int(ary->array_stride())});
+  auto* sem_arr = builder_.Sem().Get(ary);
+  if (!sem_arr) {
+    error_ = "array type missing semantic info";
+    return false;
   }
+  push_annot(spv::Op::OpDecorate,
+             {Operand::Int(result_id), Operand::Int(SpvDecorationArrayStride),
+              Operand::Int(sem_arr->Stride())});
   return true;
 }
 
@@ -3054,38 +3060,39 @@
              {Operand::Int(struct_id), Operand::Int(idx),
               Operand::String(builder_.Symbols().NameFor(member->symbol()))});
 
-  bool has_layout = false;
-  for (auto* deco : member->decorations()) {
-    if (auto* offset = deco->As<ast::StructMemberOffsetDecoration>()) {
-      push_annot(
-          spv::Op::OpMemberDecorate,
-          {Operand::Int(struct_id), Operand::Int(idx),
-           Operand::Int(SpvDecorationOffset), Operand::Int(offset->offset())});
-      has_layout = true;
-    } else {
-      error_ = "unknown struct member decoration";
-      return 0;
-    }
+  // Note: This will generate layout annotations for *all* structs, whether or
+  // not they are used in host-shareable variables. This is officially ok in
+  // SPIR-V 1.0 through 1.3. If / when we migrate to using SPIR-V 1.4 we'll have
+  // to only generate the layout info for structs used for certain storage
+  // classes.
+
+  auto* sem_member = builder_.Sem().Get(member);
+  if (!sem_member) {
+    error_ = "Struct member has no semantic information";
+    return 0;
   }
 
-  if (has_layout) {
-    // Infer and emit matrix layout.
-    auto* matrix_type = GetNestedMatrixType(member->type());
-    if (matrix_type) {
-      push_annot(spv::Op::OpMemberDecorate,
-                 {Operand::Int(struct_id), Operand::Int(idx),
-                  Operand::Int(SpvDecorationColMajor)});
-      if (!matrix_type->type()->Is<type::F32>()) {
-        error_ = "matrix scalar element type must be f32";
-        return 0;
-      }
-      const auto scalar_elem_size = 4;
-      const auto effective_row_count = (matrix_type->rows() == 2) ? 2 : 4;
-      push_annot(spv::Op::OpMemberDecorate,
-                 {Operand::Int(struct_id), Operand::Int(idx),
-                  Operand::Int(SpvDecorationMatrixStride),
-                  Operand::Int(effective_row_count * scalar_elem_size)});
+  push_annot(
+      spv::Op::OpMemberDecorate,
+      {Operand::Int(struct_id), Operand::Int(idx),
+       Operand::Int(SpvDecorationOffset), Operand::Int(sem_member->Offset())});
+
+  // Infer and emit matrix layout.
+  auto* matrix_type = GetNestedMatrixType(member->type());
+  if (matrix_type) {
+    push_annot(spv::Op::OpMemberDecorate,
+               {Operand::Int(struct_id), Operand::Int(idx),
+                Operand::Int(SpvDecorationColMajor)});
+    if (!matrix_type->type()->Is<type::F32>()) {
+      error_ = "matrix scalar element type must be f32";
+      return 0;
     }
+    const auto scalar_elem_size = 4;
+    const auto effective_row_count = (matrix_type->rows() == 2) ? 2 : 4;
+    push_annot(spv::Op::OpMemberDecorate,
+               {Operand::Int(struct_id), Operand::Int(idx),
+                Operand::Int(SpvDecorationMatrixStride),
+                Operand::Int(effective_row_count * scalar_elem_size)});
   }
 
   return GenerateTypeIfNeeded(member->type());
diff --git a/src/writer/spirv/builder_accessor_expression_test.cc b/src/writer/spirv/builder_accessor_expression_test.cc
index e69f08d..78ec1ad 100644
--- a/src/writer/spirv/builder_accessor_expression_test.cc
+++ b/src/writer/spirv/builder_accessor_expression_test.cc
@@ -135,12 +135,12 @@
 }
 
 TEST_F(BuilderTest, ArrayAccessor_MultiLevel) {
-  type::Array ary4(ty.vec3<f32>(), 4, ast::DecorationList{});
+  auto* ary4 = ty.array(ty.vec3<f32>(), 4);
 
   // ary = array<vec3<f32>, 4>
   // ary[3][2];
 
-  auto* var = Global("ary", &ary4, ast::StorageClass::kFunction);
+  auto* var = Global("ary", ary4, ast::StorageClass::kFunction);
 
   auto* expr = IndexAccessor(IndexAccessor("ary", 3), 2);
   WrapInFunction(expr);
@@ -173,12 +173,12 @@
 }
 
 TEST_F(BuilderTest, Accessor_ArrayWithSwizzle) {
-  type::Array ary4(ty.vec3<f32>(), 4, ast::DecorationList{});
+  auto* ary4 = ty.array(ty.vec3<f32>(), 4);
 
   // var a : array<vec3<f32>, 4>;
   // a[2].xy;
 
-  auto* var = Global("ary", &ary4, ast::StorageClass::kFunction);
+  auto* var = Global("ary", ary4, ast::StorageClass::kFunction);
 
   auto* expr = MemberAccessor(IndexAccessor("ary", 2), "xy");
   WrapInFunction(expr);
@@ -219,12 +219,12 @@
   // var ident : my_struct
   // ident.b
 
-  auto* s = create<ast::Struct>(
-      ast::StructMemberList{Member("a", ty.f32()), Member("b", ty.f32())},
-      ast::DecorationList{});
+  auto* s = Structure("my_struct", {
+                                       Member("a", ty.f32()),
+                                       Member("b", ty.f32()),
+                                   });
 
-  auto* s_type = ty.struct_("my_struct", s);
-  auto* var = Global("ident", s_type, ast::StorageClass::kFunction);
+  auto* var = Global("ident", s, ast::StorageClass::kFunction);
 
   auto* expr = MemberAccessor("ident", "b");
   WrapInFunction(expr);
@@ -262,15 +262,12 @@
   //
   // var ident : my_struct
   // ident.inner.a
-  auto* inner_struct = ty.struct_(
-      "Inner", create<ast::Struct>(ast::StructMemberList{Member("a", ty.f32()),
-                                                         Member("b", ty.f32())},
-                                   ast::DecorationList{}));
+  auto* inner_struct = Structure("Inner", {
+                                              Member("a", ty.f32()),
+                                              Member("b", ty.f32()),
+                                          });
 
-  auto* s_type = ty.struct_(
-      "my_struct",
-      create<ast::Struct>(ast::StructMemberList{Member("inner", inner_struct)},
-                          ast::DecorationList{}));
+  auto* s_type = Structure("my_struct", {Member("inner", inner_struct)});
 
   auto* var = Global("ident", s_type, ast::StorageClass::kFunction);
   auto* expr = MemberAccessor(MemberAccessor("ident", "inner"), "a");
@@ -311,16 +308,13 @@
   //
   // var ident : my_struct
   // ident.inner.a
-  auto* inner_struct = ty.struct_(
-      "Inner", create<ast::Struct>(ast::StructMemberList{Member("a", ty.f32()),
-                                                         Member("b", ty.f32())},
-                                   ast::DecorationList{}));
+  auto* inner_struct = Structure("Inner", {
+                                              Member("a", ty.f32()),
+                                              Member("b", ty.f32()),
+                                          });
 
   auto* alias = ty.alias("Inner", inner_struct);
-  auto* s_type = ty.struct_(
-      "Outer",
-      create<ast::Struct>(ast::StructMemberList{Member("inner", alias)},
-                          ast::DecorationList{}));
+  auto* s_type = Structure("Outer", {Member("inner", alias)});
 
   auto* var = Global("ident", s_type, ast::StorageClass::kFunction);
   auto* expr = MemberAccessor(MemberAccessor("ident", "inner"), "a");
@@ -360,15 +354,12 @@
   //
   // var ident : my_struct
   // ident.inner.a = 2.0f;
-  auto* inner_struct = ty.struct_(
-      "Inner", create<ast::Struct>(ast::StructMemberList{Member("a", ty.f32()),
-                                                         Member("b", ty.f32())},
-                                   ast::DecorationList{}));
+  auto* inner_struct = Structure("Inner", {
+                                              Member("a", ty.f32()),
+                                              Member("b", ty.f32()),
+                                          });
 
-  auto* s_type = ty.struct_(
-      "my_struct",
-      create<ast::Struct>(ast::StructMemberList{Member("inner", inner_struct)},
-                          ast::DecorationList{}));
+  auto* s_type = Structure("my_struct", {Member("inner", inner_struct)});
 
   auto* var = Global("ident", s_type, ast::StorageClass::kFunction);
   auto* expr = create<ast::AssignmentStatement>(
@@ -412,15 +403,12 @@
   // var ident : my_struct
   // var store : f32 = ident.inner.a
 
-  auto* inner_struct = ty.struct_(
-      "Inner", create<ast::Struct>(ast::StructMemberList{Member("a", ty.f32()),
-                                                         Member("b", ty.f32())},
-                                   ast::DecorationList{}));
+  auto* inner_struct = Structure("Inner", {
+                                              Member("a", ty.f32()),
+                                              Member("b", ty.f32()),
+                                          });
 
-  auto* s_type = ty.struct_(
-      "my_struct",
-      create<ast::Struct>(ast::StructMemberList{Member("inner", inner_struct)},
-                          ast::DecorationList{}));
+  auto* s_type = Structure("my_struct", {Member("inner", inner_struct)});
 
   auto* var = Global("ident", s_type, ast::StorageClass::kFunction);
   auto* store = Global("store", ty.f32(), ast::StorageClass::kFunction);
@@ -625,21 +613,14 @@
   // var index : array<A, 2>
   // index[0].foo[2].bar.baz.yx
 
-  auto* s =
-      create<ast::Struct>(ast::StructMemberList{Member("baz", ty.vec3<f32>())},
-                          ast::DecorationList{});
-  auto* c_type = ty.struct_("C", s);
+  auto* c_type = Structure("C", {Member("baz", ty.vec3<f32>())});
 
-  s = create<ast::Struct>(ast::StructMemberList{Member("bar", c_type)},
-                          ast::DecorationList{});
-  auto* b_type = ty.struct_("B", s);
-  type::Array b_ary_type(b_type, 3, ast::DecorationList{});
-  s = create<ast::Struct>(ast::StructMemberList{Member("foo", &b_ary_type)},
-                          ast::DecorationList{});
-  auto* a_type = ty.struct_("A", s);
+  auto* b_type = Structure("B", {Member("bar", c_type)});
+  auto* b_ary_type = ty.array(b_type, 3);
+  auto* a_type = Structure("A", {Member("foo", b_ary_type)});
 
-  type::Array a_ary_type(a_type, 2, ast::DecorationList{});
-  auto* var = Global("index", &a_ary_type, ast::StorageClass::kFunction);
+  auto* a_ary_type = ty.array(a_type, 2);
+  auto* var = Global("index", a_ary_type, ast::StorageClass::kFunction);
   auto* expr = MemberAccessor(
       MemberAccessor(
           MemberAccessor(
@@ -693,12 +674,12 @@
   //   vec2<f32>(0.5, -0.5));
   // pos[1]
 
-  type::Array arr(ty.vec2<f32>(), 3, ast::DecorationList{});
+  auto* arr = ty.array(ty.vec2<f32>(), 3);
 
   auto* var =
-      GlobalConst("pos", &arr,
-                  Construct(&arr, vec2<f32>(0.0f, 0.5f),
-                            vec2<f32>(-0.5f, -0.5f), vec2<f32>(0.5f, -0.5f)));
+      GlobalConst("pos", arr,
+                  Construct(arr, vec2<f32>(0.0f, 0.5f), vec2<f32>(-0.5f, -0.5f),
+                            vec2<f32>(0.5f, -0.5f)));
 
   auto* expr = IndexAccessor("pos", 1u);
   WrapInFunction(expr);
diff --git a/src/writer/spirv/builder_assign_test.cc b/src/writer/spirv/builder_assign_test.cc
index 45d3769..c3dcf99 100644
--- a/src/writer/spirv/builder_assign_test.cc
+++ b/src/writer/spirv/builder_assign_test.cc
@@ -176,12 +176,12 @@
   // var ident : my_struct
   // ident.b = 4.0;
 
-  auto* s = create<ast::Struct>(
-      ast::StructMemberList{Member("a", ty.f32()), Member("b", ty.f32())},
-      ast::DecorationList{});
+  auto* s = Structure("my_struct", {
+                                       Member("a", ty.f32()),
+                                       Member("b", ty.f32()),
+                                   });
 
-  auto* s_type = ty.struct_("my_struct", s);
-  auto* v = Global("ident", s_type, ast::StorageClass::kFunction);
+  auto* v = Global("ident", s, ast::StorageClass::kFunction);
 
   auto* assign =
       create<ast::AssignmentStatement>(MemberAccessor("ident", "b"), Expr(4.f));
diff --git a/src/writer/spirv/builder_constructor_expression_test.cc b/src/writer/spirv/builder_constructor_expression_test.cc
index 4525ba4..6a11cb0 100644
--- a/src/writer/spirv/builder_constructor_expression_test.cc
+++ b/src/writer/spirv/builder_constructor_expression_test.cc
@@ -974,15 +974,12 @@
 }
 
 TEST_F(SpvBuilderConstructorTest, Type_Struct) {
-  auto* s = create<ast::Struct>(
-      ast::StructMemberList{
-          Member("a", ty.f32()),
-          Member("b", ty.vec3<f32>()),
-      },
-      ast::DecorationList{});
-  auto* s_type = ty.struct_("my_struct", s);
+  auto* s = Structure("my_struct", {
+                                       Member("a", ty.f32()),
+                                       Member("b", ty.vec3<f32>()),
+                                   });
 
-  auto* t = Construct(s_type, 2.0f, vec3<f32>(2.0f, 2.0f, 2.0f));
+  auto* t = Construct(s, 2.0f, vec3<f32>(2.0f, 2.0f, 2.0f));
   WrapInFunction(t);
 
   spirv::Builder& b = Build();
@@ -1127,13 +1124,8 @@
 }
 
 TEST_F(SpvBuilderConstructorTest, Type_ZeroInit_Struct) {
-  auto* s = create<ast::Struct>(
-      ast::StructMemberList{
-          Member("a", ty.f32()),
-      },
-      ast::DecorationList{});
-  auto* s_type = ty.struct_("my_struct", s);
-  auto* t = Construct(s_type);
+  auto* s = Structure("my_struct", {Member("a", ty.f32())});
+  auto* t = Construct(s);
   WrapInFunction(t);
 
   spirv::Builder& b = Build();
@@ -1564,14 +1556,12 @@
 }
 
 TEST_F(SpvBuilderConstructorTest, IsConstructorConst_Struct) {
-  auto* s = create<ast::Struct>(
-      ast::StructMemberList{
-          Member("a", ty.f32()),
-          Member("b", ty.vec3<f32>()),
-      },
-      ast::DecorationList{});
-  auto* s_type = ty.struct_("my_struct", s);
-  auto* t = Construct(s_type, 2.f, vec3<f32>(2.f, 2.f, 2.f));
+  auto* s = Structure("my_struct", {
+                                       Member("a", ty.f32()),
+                                       Member("b", ty.vec3<f32>()),
+                                   });
+
+  auto* t = Construct(s, 2.f, vec3<f32>(2.f, 2.f, 2.f));
   WrapInFunction(t);
 
   spirv::Builder& b = Build();
@@ -1582,15 +1572,12 @@
 
 TEST_F(SpvBuilderConstructorTest,
        IsConstructorConst_Struct_WithIdentSubExpression) {
-  auto* s = create<ast::Struct>(
-      ast::StructMemberList{
-          Member("a", ty.f32()),
-          Member("b", ty.vec3<f32>()),
-      },
-      ast::DecorationList{});
+  auto* s = Structure("my_struct", {
+                                       Member("a", ty.f32()),
+                                       Member("b", ty.vec3<f32>()),
+                                   });
 
-  auto* s_type = ty.struct_("my_struct", s);
-  auto* t = Construct(s_type, 2.f, "a", 2.f);
+  auto* t = Construct(s, 2.f, "a", 2.f);
   WrapInFunction(t);
 
   Global("a", ty.f32(), ast::StorageClass::kPrivate);
diff --git a/src/writer/spirv/builder_function_test.cc b/src/writer/spirv/builder_function_test.cc
index feecfac..392a05e 100644
--- a/src/writer/spirv/builder_function_test.cc
+++ b/src/writer/spirv/builder_function_test.cc
@@ -192,7 +192,7 @@
 // https://crbug.com/tint/297
 TEST_F(BuilderTest, Emit_Multiple_EntryPoint_With_Same_ModuleVar) {
   // [[block]] struct Data {
-  //   [[offset(0)]] d : f32;
+  //   d : f32;
   // };
   // [[binding(0), group(0)]] var<storage> data : Data;
   //
@@ -206,13 +206,9 @@
   //   return;
   // }
 
-  ast::DecorationList s_decos;
-  s_decos.push_back(create<ast::StructBlockDecoration>());
+  auto* s = Structure("Data", {Member("d", ty.f32())},
+                      {create<ast::StructBlockDecoration>()});
 
-  auto* str = create<ast::Struct>(
-      ast::StructMemberList{Member("d", ty.f32(), {MemberOffset(0)})}, s_decos);
-
-  auto* s = ty.struct_("Data", str);
   type::AccessControl ac(ast::AccessControl::kReadWrite, s);
 
   Global("data", &ac, ast::StorageClass::kStorage, nullptr,
@@ -221,8 +217,6 @@
              create<ast::GroupDecoration>(0),
          });
 
-  AST().AddConstructedType(s);
-
   {
     auto* var = Var("v", ty.f32(), ast::StorageClass::kFunction,
                     MemberAccessor("data", "d"));
diff --git a/src/writer/spirv/builder_global_variable_test.cc b/src/writer/spirv/builder_global_variable_test.cc
index 6a37928..bc782e1 100644
--- a/src/writer/spirv/builder_global_variable_test.cc
+++ b/src/writer/spirv/builder_global_variable_test.cc
@@ -383,10 +383,10 @@
   // };
   // var b : [[access(read)]] A
 
-  auto* A = ty.struct_(
-      "A", create<ast::Struct>(ast::StructMemberList{Member("a", ty.i32()),
-                                                     Member("b", ty.i32())},
-                               ast::DecorationList{}));
+  auto* A = Structure("A", {
+                               Member("a", ty.i32()),
+                               Member("b", ty.i32()),
+                           });
   auto* ac = create<type::AccessControl>(ast::AccessControl::kReadOnly, A);
 
   auto* var = Global("b", ac, ast::StorageClass::kStorage);
@@ -395,7 +395,9 @@
 
   EXPECT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
 
-  EXPECT_EQ(DumpInstructions(b.annots()), R"(OpMemberDecorate %3 0 NonWritable
+  EXPECT_EQ(DumpInstructions(b.annots()), R"(OpMemberDecorate %3 0 Offset 0
+OpMemberDecorate %3 0 NonWritable
+OpMemberDecorate %3 1 Offset 4
 OpMemberDecorate %3 1 NonWritable
 )");
   EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %3 "A"
@@ -417,9 +419,7 @@
   // type B = A;
   // var b : [[access(read)]] B
 
-  auto* A = ty.struct_(
-      "A", create<ast::Struct>(ast::StructMemberList{Member("a", ty.i32())},
-                               ast::DecorationList{}));
+  auto* A = Structure("A", {Member("a", ty.i32())});
   auto* B = ty.alias("B", A);
   auto* ac = create<type::AccessControl>(ast::AccessControl::kReadOnly, B);
   auto* var = Global("b", ac, ast::StorageClass::kStorage);
@@ -428,7 +428,8 @@
 
   EXPECT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
 
-  EXPECT_EQ(DumpInstructions(b.annots()), R"(OpMemberDecorate %3 0 NonWritable
+  EXPECT_EQ(DumpInstructions(b.annots()), R"(OpMemberDecorate %3 0 Offset 0
+OpMemberDecorate %3 0 NonWritable
 )");
   EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %3 "A"
 OpMemberName %3 0 "a"
@@ -448,9 +449,7 @@
   // type B = [[access(read)]] A;
   // var b : B
 
-  auto* A = ty.struct_(
-      "A", create<ast::Struct>(ast::StructMemberList{Member("a", ty.i32())},
-                               ast::DecorationList{}));
+  auto* A = Structure("A", {Member("a", ty.i32())});
   auto* ac = create<type::AccessControl>(ast::AccessControl::kReadOnly, A);
   auto* B = ty.alias("B", ac);
   auto* var = Global("b", B, ast::StorageClass::kStorage);
@@ -459,7 +458,8 @@
 
   EXPECT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
 
-  EXPECT_EQ(DumpInstructions(b.annots()), R"(OpMemberDecorate %3 0 NonWritable
+  EXPECT_EQ(DumpInstructions(b.annots()), R"(OpMemberDecorate %3 0 Offset 0
+OpMemberDecorate %3 0 NonWritable
 )");
   EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %3 "A"
 OpMemberName %3 0 "a"
@@ -479,9 +479,7 @@
   // var b : [[access(read)]] A
   // var c : [[access(read_write)]] A
 
-  auto* A = ty.struct_(
-      "A", create<ast::Struct>(ast::StructMemberList{Member("a", ty.i32())},
-                               ast::DecorationList{}));
+  auto* A = Structure("A", {Member("a", ty.i32())});
   type::AccessControl read{ast::AccessControl::kReadOnly, A};
   type::AccessControl rw{ast::AccessControl::kReadWrite, A};
 
@@ -494,7 +492,9 @@
   EXPECT_TRUE(b.GenerateGlobalVariable(var_c)) << b.error();
 
   EXPECT_EQ(DumpInstructions(b.annots()),
-            R"(OpMemberDecorate %3 0 NonWritable
+            R"(OpMemberDecorate %3 0 Offset 0
+OpMemberDecorate %3 0 NonWritable
+OpMemberDecorate %7 0 Offset 0
 )");
   EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %3 "A"
 OpMemberName %3 0 "a"
@@ -665,6 +665,7 @@
 OpName %1 "mask_in"
 OpName %6 "mask_out"
 OpName %11 "main"
+OpDecorate %3 ArrayStride 4
 OpDecorate %1 BuiltIn SampleMask
 OpDecorate %6 BuiltIn SampleMask
 %4 = OpTypeInt 32 0
diff --git a/src/writer/spirv/builder_intrinsic_test.cc b/src/writer/spirv/builder_intrinsic_test.cc
index 3d1510d..4fe131e 100644
--- a/src/writer/spirv/builder_intrinsic_test.cc
+++ b/src/writer/spirv/builder_intrinsic_test.cc
@@ -1377,13 +1377,9 @@
 }
 
 TEST_F(IntrinsicBuilderTest, Call_ArrayLength) {
-  auto* s = create<ast::Struct>(
-      ast::StructMemberList{Member(0, "a", ty.array<f32>(4))},
-      ast::DecorationList{
-          create<ast::StructBlockDecoration>(),
-      });
-  auto* s_type = ty.struct_("my_struct", s);
-  Global("b", s_type, ast::StorageClass::kStorage, nullptr,
+  auto* s = Structure("my_struct", {Member(0, "a", ty.array<f32>(4))},
+                      {create<ast::StructBlockDecoration>()});
+  Global("b", s, ast::StorageClass::kStorage, nullptr,
          ast::DecorationList{
              create<ast::BindingDecoration>(1),
              create<ast::GroupDecoration>(2),
@@ -1426,15 +1422,14 @@
 }
 
 TEST_F(IntrinsicBuilderTest, Call_ArrayLength_OtherMembersInStruct) {
-  auto* s = create<ast::Struct>(
-      ast::StructMemberList{Member(0, "z", ty.f32()),
-                            Member(4, "a", ty.array<f32>(4))},
-      ast::DecorationList{
-          create<ast::StructBlockDecoration>(),
-      });
+  auto* s = Structure("my_struct",
+                      {
+                          Member(0, "z", ty.f32()),
+                          Member(4, "a", ty.array<f32>(4)),
+                      },
+                      {create<ast::StructBlockDecoration>()});
 
-  auto* s_type = ty.struct_("my_struct", s);
-  Global("b", s_type, ast::StorageClass::kStorage, nullptr,
+  Global("b", s, ast::StorageClass::kStorage, nullptr,
          ast::DecorationList{
              create<ast::BindingDecoration>(1),
              create<ast::GroupDecoration>(2),
diff --git a/src/writer/spirv/builder_type_test.cc b/src/writer/spirv/builder_type_test.cc
index cb8a1d1..5e990a1 100644
--- a/src/writer/spirv/builder_type_test.cc
+++ b/src/writer/spirv/builder_type_test.cc
@@ -58,11 +58,11 @@
 }
 
 TEST_F(BuilderTest_Type, GenerateRuntimeArray) {
-  type::Array ary(ty.i32(), 0, ast::DecorationList{});
+  auto* ary = ty.array(ty.i32(), 0);
 
   spirv::Builder& b = Build();
 
-  auto id = b.GenerateTypeIfNeeded(&ary);
+  auto id = b.GenerateTypeIfNeeded(ary);
   ASSERT_FALSE(b.has_error()) << b.error();
   EXPECT_EQ(1u, id);
 
@@ -72,12 +72,12 @@
 }
 
 TEST_F(BuilderTest_Type, ReturnsGeneratedRuntimeArray) {
-  type::Array ary(ty.i32(), 0, ast::DecorationList{});
+  auto* ary = ty.array(ty.i32(), 0);
 
   spirv::Builder& b = Build();
 
-  EXPECT_EQ(b.GenerateTypeIfNeeded(&ary), 1u);
-  EXPECT_EQ(b.GenerateTypeIfNeeded(&ary), 1u);
+  EXPECT_EQ(b.GenerateTypeIfNeeded(ary), 1u);
+  EXPECT_EQ(b.GenerateTypeIfNeeded(ary), 1u);
   ASSERT_FALSE(b.has_error()) << b.error();
 
   EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
@@ -86,11 +86,11 @@
 }
 
 TEST_F(BuilderTest_Type, GenerateArray) {
-  type::Array ary(ty.i32(), 4, ast::DecorationList{});
+  auto* ary = ty.array(ty.i32(), 4);
 
   spirv::Builder& b = Build();
 
-  auto id = b.GenerateTypeIfNeeded(&ary);
+  auto id = b.GenerateTypeIfNeeded(ary);
   ASSERT_FALSE(b.has_error()) << b.error();
   EXPECT_EQ(1u, id);
 
@@ -102,14 +102,11 @@
 }
 
 TEST_F(BuilderTest_Type, GenerateArray_WithStride) {
-  type::Array ary(ty.i32(), 4,
-                  ast::DecorationList{
-                      create<ast::StrideDecoration>(16u),
-                  });
+  auto* ary = ty.array(ty.i32(), 4, 16u);
 
   spirv::Builder& b = Build();
 
-  auto id = b.GenerateTypeIfNeeded(&ary);
+  auto id = b.GenerateTypeIfNeeded(ary);
   ASSERT_FALSE(b.has_error()) << b.error();
   EXPECT_EQ(1u, id);
 
@@ -124,12 +121,12 @@
 }
 
 TEST_F(BuilderTest_Type, ReturnsGeneratedArray) {
-  type::Array ary(ty.i32(), 4, ast::DecorationList{});
+  auto* ary = ty.array(ty.i32(), 4);
 
   spirv::Builder& b = Build();
 
-  EXPECT_EQ(b.GenerateTypeIfNeeded(&ary), 1u);
-  EXPECT_EQ(b.GenerateTypeIfNeeded(&ary), 1u);
+  EXPECT_EQ(b.GenerateTypeIfNeeded(ary), 1u);
+  EXPECT_EQ(b.GenerateTypeIfNeeded(ary), 1u);
   ASSERT_FALSE(b.has_error()) << b.error();
 
   EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
@@ -277,12 +274,11 @@
 }
 
 TEST_F(BuilderTest_Type, GenerateStruct_Empty) {
-  auto* s = create<ast::Struct>(ast::StructMemberList{}, ast::DecorationList{});
-  auto* s_type = ty.struct_("S", s);
+  auto* s = Structure("S", {});
 
   spirv::Builder& b = Build();
 
-  auto id = b.GenerateTypeIfNeeded(s_type);
+  auto id = b.GenerateTypeIfNeeded(s);
   ASSERT_FALSE(b.has_error()) << b.error();
   EXPECT_EQ(id, 1u);
 
@@ -294,13 +290,11 @@
 }
 
 TEST_F(BuilderTest_Type, GenerateStruct) {
-  auto* s = create<ast::Struct>(ast::StructMemberList{Member("a", ty.f32())},
-                                ast::DecorationList{});
-  auto* s_type = ty.struct_("my_struct", s);
+  auto* s = Structure("my_struct", {Member("a", ty.f32())});
 
   spirv::Builder& b = Build();
 
-  auto id = b.GenerateTypeIfNeeded(s_type);
+  auto id = b.GenerateTypeIfNeeded(s);
   ASSERT_FALSE(b.has_error()) << b.error();
   EXPECT_EQ(id, 1u);
 
@@ -313,16 +307,12 @@
 }
 
 TEST_F(BuilderTest_Type, GenerateStruct_Decorated) {
-  ast::DecorationList struct_decos;
-  struct_decos.push_back(create<ast::StructBlockDecoration>());
-
-  auto* s = create<ast::Struct>(ast::StructMemberList{Member("a", ty.f32())},
-                                struct_decos);
-  auto* s_type = ty.struct_("my_struct", s);
+  auto* s = Structure("my_struct", {Member("a", ty.f32())},
+                      {create<ast::StructBlockDecoration>()});
 
   spirv::Builder& b = Build();
 
-  auto id = b.GenerateTypeIfNeeded(s_type);
+  auto id = b.GenerateTypeIfNeeded(s);
   ASSERT_FALSE(b.has_error()) << b.error();
   EXPECT_EQ(id, 1u);
 
@@ -333,19 +323,19 @@
 OpMemberName %1 0 "a"
 )");
   EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %1 Block
+OpMemberDecorate %1 0 Offset 0
 )");
 }
 
 TEST_F(BuilderTest_Type, GenerateStruct_DecoratedMembers) {
-  auto* s = create<ast::Struct>(
-      ast::StructMemberList{Member("a", ty.f32(), {MemberOffset(0)}),
-                            Member("b", ty.f32(), {MemberOffset(8)})},
-      ast::DecorationList{});
-  auto* s_type = ty.struct_("S", s);
+  auto* s = Structure("S", {
+                               Member("a", ty.f32()),
+                               Member("b", ty.f32(), {MemberAlign(8)}),
+                           });
 
   spirv::Builder& b = Build();
 
-  auto id = b.GenerateTypeIfNeeded(s_type);
+  auto id = b.GenerateTypeIfNeeded(s);
   ASSERT_FALSE(b.has_error()) << b.error();
   EXPECT_EQ(id, 1u);
 
@@ -362,16 +352,15 @@
 }
 
 TEST_F(BuilderTest_Type, GenerateStruct_NonLayout_Matrix) {
-  auto* s =
-      create<ast::Struct>(ast::StructMemberList{Member("a", ty.mat2x2<f32>()),
-                                                Member("b", ty.mat2x3<f32>()),
-                                                Member("c", ty.mat4x4<f32>())},
-                          ast::DecorationList{});
-  auto* s_type = ty.struct_("S", s);
+  auto* s = Structure("S", {
+                               Member("a", ty.mat2x2<f32>()),
+                               Member("b", ty.mat2x3<f32>()),
+                               Member("c", ty.mat4x4<f32>()),
+                           });
 
   spirv::Builder& b = Build();
 
-  auto id = b.GenerateTypeIfNeeded(s_type);
+  auto id = b.GenerateTypeIfNeeded(s);
   ASSERT_FALSE(b.has_error()) << b.error();
   EXPECT_EQ(id, 1u);
 
@@ -389,21 +378,29 @@
 OpMemberName %1 1 "b"
 OpMemberName %1 2 "c"
 )");
-  EXPECT_EQ(DumpInstructions(b.annots()), "");
+  EXPECT_EQ(DumpInstructions(b.annots()), R"(OpMemberDecorate %1 0 Offset 0
+OpMemberDecorate %1 0 ColMajor
+OpMemberDecorate %1 0 MatrixStride 8
+OpMemberDecorate %1 1 Offset 16
+OpMemberDecorate %1 1 ColMajor
+OpMemberDecorate %1 1 MatrixStride 16
+OpMemberDecorate %1 2 Offset 48
+OpMemberDecorate %1 2 ColMajor
+OpMemberDecorate %1 2 MatrixStride 16
+)");
 }
 
 TEST_F(BuilderTest_Type, GenerateStruct_DecoratedMembers_LayoutMatrix) {
   // We have to infer layout for matrix when it also has an offset.
-  auto* s = create<ast::Struct>(
-      ast::StructMemberList{Member("a", ty.mat2x2<f32>(), {MemberOffset(0)}),
-                            Member("b", ty.mat2x3<f32>(), {MemberOffset(16)}),
-                            Member("c", ty.mat4x4<f32>(), {MemberOffset(48)})},
-      ast::DecorationList{});
-  auto* s_type = ty.struct_("S", s);
+  auto* s = Structure("S", {
+                               Member("a", ty.mat2x2<f32>()),
+                               Member("b", ty.mat2x3<f32>()),
+                               Member("c", ty.mat4x4<f32>()),
+                           });
 
   spirv::Builder& b = Build();
 
-  auto id = b.GenerateTypeIfNeeded(s_type);
+  auto id = b.GenerateTypeIfNeeded(s);
   ASSERT_FALSE(b.has_error()) << b.error();
   EXPECT_EQ(id, 1u);
 
@@ -437,26 +434,19 @@
   // We have to infer layout for matrix when it also has an offset.
   // The decoration goes on the struct member, even if the matrix is buried
   // in levels of arrays.
-  type::Array arr_mat2x2(ty.mat2x2<f32>(), 1,
-                         ast::DecorationList{});  // Singly nested array
+  auto* arr_mat2x2 = ty.array(ty.mat2x2<f32>(), 1);      // Singly nested array
+  auto* arr_arr_mat2x3 = ty.array(ty.mat2x3<f32>(), 1);  // Doubly nested array
+  auto* rtarr_mat4x4 = ty.array(ty.mat4x4<f32>(), 0);    // Runtime array
 
-  type::Array arr_mat2x3(ty.mat2x3<f32>(), 1, ast::DecorationList{});
-  type::Array arr_arr_mat2x3(ty.mat2x3<f32>(), 1,
-                             ast::DecorationList{});  // Doubly nested array
-
-  type::Array rtarr_mat4x4(ty.mat4x4<f32>(), 0,
-                           ast::DecorationList{});  // Runtime array
-
-  auto* s = create<ast::Struct>(
-      ast::StructMemberList{Member("a", &arr_mat2x2, {MemberOffset(0)}),
-                            Member("b", &arr_arr_mat2x3, {MemberOffset(16)}),
-                            Member("c", &rtarr_mat4x4, {MemberOffset(48)})},
-      ast::DecorationList{});
-  auto* s_type = ty.struct_("S", s);
+  auto* s = Structure("S", {
+                               Member("a", arr_mat2x2),
+                               Member("b", arr_arr_mat2x3),
+                               Member("c", rtarr_mat4x4),
+                           });
 
   spirv::Builder& b = Build();
 
-  auto id = b.GenerateTypeIfNeeded(s_type);
+  auto id = b.GenerateTypeIfNeeded(s);
   ASSERT_FALSE(b.has_error()) << b.error();
   EXPECT_EQ(id, 1u);
 
@@ -482,12 +472,15 @@
   EXPECT_EQ(DumpInstructions(b.annots()), R"(OpMemberDecorate %1 0 Offset 0
 OpMemberDecorate %1 0 ColMajor
 OpMemberDecorate %1 0 MatrixStride 8
+OpDecorate %2 ArrayStride 16
 OpMemberDecorate %1 1 Offset 16
 OpMemberDecorate %1 1 ColMajor
 OpMemberDecorate %1 1 MatrixStride 16
+OpDecorate %8 ArrayStride 32
 OpMemberDecorate %1 2 Offset 48
 OpMemberDecorate %1 2 ColMajor
 OpMemberDecorate %1 2 MatrixStride 16
+OpDecorate %11 ArrayStride 64
 )");
 }
 
diff --git a/src/writer/wgsl/generator_impl.cc b/src/writer/wgsl/generator_impl.cc
index 7ea7a8f..304f720 100644
--- a/src/writer/wgsl/generator_impl.cc
+++ b/src/writer/wgsl/generator_impl.cc
@@ -24,7 +24,9 @@
 #include "src/ast/sint_literal.h"
 #include "src/ast/stage_decoration.h"
 #include "src/ast/stride_decoration.h"
+#include "src/ast/struct_member_align_decoration.h"
 #include "src/ast/struct_member_offset_decoration.h"
+#include "src/ast/struct_member_size_decoration.h"
 #include "src/ast/uint_literal.h"
 #include "src/ast/variable_decl_statement.h"
 #include "src/ast/workgroup_decoration.h"
@@ -582,6 +584,10 @@
       out_ << "constant_id(" << constant->value() << ")";
     } else if (auto* offset = deco->As<ast::StructMemberOffsetDecoration>()) {
       out_ << "offset(" << offset->offset() << ")";
+    } else if (auto* size = deco->As<ast::StructMemberSizeDecoration>()) {
+      out_ << "[[size(" << size->size() << ")]]" << std::endl;
+    } else if (auto* align = deco->As<ast::StructMemberAlignDecoration>()) {
+      out_ << "[[align(" << align->align() << ")]]" << std::endl;
     } else {
       diagnostics_.add_error("unknown variable decoration");
       return false;
diff --git a/src/writer/wgsl/generator_impl_alias_type_test.cc b/src/writer/wgsl/generator_impl_alias_type_test.cc
index 1155aa3..a1ecefa 100644
--- a/src/writer/wgsl/generator_impl_alias_type_test.cc
+++ b/src/writer/wgsl/generator_impl_alias_type_test.cc
@@ -31,12 +31,11 @@
 }
 
 TEST_F(WgslGeneratorImplTest, EmitConstructedType_Struct) {
-  auto* str = create<ast::Struct>(
-      ast::StructMemberList{Member("a", ty.f32()),
-                            Member("b", ty.i32(), {MemberOffset(4)})},
-      ast::DecorationList{});
+  auto* s = Structure("A", {
+                               Member("a", ty.f32()),
+                               Member("b", ty.i32()),
+                           });
 
-  auto* s = ty.struct_("A", str);
   auto* alias = ty.alias("B", s);
 
   GeneratorImpl& gen = Build();
@@ -45,7 +44,6 @@
   ASSERT_TRUE(gen.EmitConstructedType(alias)) << gen.error();
   EXPECT_EQ(gen.result(), R"(struct A {
   a : f32;
-  [[offset(4)]]
   b : i32;
 };
 type B = A;
@@ -53,12 +51,11 @@
 }
 
 TEST_F(WgslGeneratorImplTest, EmitAlias_ToStruct) {
-  auto* str = create<ast::Struct>(
-      ast::StructMemberList{Member("a", ty.f32()),
-                            Member("b", ty.i32(), {MemberOffset(4)})},
-      ast::DecorationList{});
+  auto* s = Structure("A", {
+                               Member("a", ty.f32()),
+                               Member("b", ty.i32()),
+                           });
 
-  auto* s = ty.struct_("A", str);
   auto* alias = ty.alias("B", s);
 
   GeneratorImpl& gen = Build();
diff --git a/src/writer/wgsl/generator_impl_function_test.cc b/src/writer/wgsl/generator_impl_function_test.cc
index 63cc1f2..f79d0d6 100644
--- a/src/writer/wgsl/generator_impl_function_test.cc
+++ b/src/writer/wgsl/generator_impl_function_test.cc
@@ -171,7 +171,7 @@
 TEST_F(WgslGeneratorImplTest,
        Emit_Function_Multiple_EntryPoint_With_Same_ModuleVar) {
   // [[block]] struct Data {
-  //   [[offset(0)]] d : f32;
+  //   d : f32;
   // };
   // [[binding(0), group(0)]] var<storage> data : Data;
   //
@@ -185,15 +185,10 @@
   //   return;
   // }
 
-  ast::DecorationList s_decos;
-  s_decos.push_back(create<ast::StructBlockDecoration>());
+  auto* s = Structure("Data", {Member("d", ty.f32())},
+                      {create<ast::StructBlockDecoration>()});
 
-  auto* str = create<ast::Struct>(
-      ast::StructMemberList{Member("d", ty.f32(), {MemberOffset(0)})}, s_decos);
-
-  auto* s = ty.struct_("Data", str);
   type::AccessControl ac(ast::AccessControl::kReadWrite, s);
-  AST().AddConstructedType(s);
 
   Global("data", &ac, ast::StorageClass::kStorage, nullptr,
          ast::DecorationList{
@@ -236,7 +231,6 @@
   ASSERT_TRUE(gen.Generate(nullptr)) << gen.error();
   EXPECT_EQ(gen.result(), R"([[block]]
 struct Data {
-  [[offset(0)]]
   d : f32;
 };
 
diff --git a/src/writer/wgsl/generator_impl_type_test.cc b/src/writer/wgsl/generator_impl_type_test.cc
index b3e5553..3d4d2ca 100644
--- a/src/writer/wgsl/generator_impl_type_test.cc
+++ b/src/writer/wgsl/generator_impl_type_test.cc
@@ -45,13 +45,8 @@
 }
 
 TEST_F(WgslGeneratorImplTest, EmitType_AccessControl_Read) {
-  auto* block_deco = create<ast::StructBlockDecoration>();
-  ast::DecorationList decos;
-  decos.push_back(block_deco);
-
-  auto* str =
-      create<ast::Struct>(ast::StructMemberList{Member("a", ty.i32())}, decos);
-  auto* s = ty.struct_("S", str);
+  auto* s = Structure("S", {Member("a", ty.i32())},
+                      {create<ast::StructBlockDecoration>()});
 
   type::AccessControl a(ast::AccessControl::kReadOnly, s);
 
@@ -62,13 +57,8 @@
 }
 
 TEST_F(WgslGeneratorImplTest, EmitType_AccessControl_ReadWrite) {
-  auto* block_deco = create<ast::StructBlockDecoration>();
-  ast::DecorationList decos;
-  decos.push_back(block_deco);
-
-  auto* str =
-      create<ast::Struct>(ast::StructMemberList{Member("a", ty.i32())}, decos);
-  auto* s = ty.struct_("S", str);
+  auto* s = Structure("S", {Member("a", ty.i32())},
+                      {create<ast::StructBlockDecoration>()});
 
   type::AccessControl a(ast::AccessControl::kReadWrite, s);
 
@@ -158,12 +148,11 @@
 }
 
 TEST_F(WgslGeneratorImplTest, EmitType_Struct) {
-  auto* str = create<ast::Struct>(
-      ast::StructMemberList{Member("a", ty.i32()),
-                            Member("b", ty.f32(), {MemberOffset(4)})},
-      ast::DecorationList{});
+  auto* s = Structure("S", {
+                               Member("a", ty.i32()),
+                               Member("b", ty.f32()),
+                           });
 
-  auto* s = ty.struct_("S", str);
   GeneratorImpl& gen = Build();
 
   ASSERT_TRUE(gen.EmitType(s)) << gen.error();
@@ -171,12 +160,11 @@
 }
 
 TEST_F(WgslGeneratorImplTest, EmitType_StructDecl) {
-  auto* str = create<ast::Struct>(
-      ast::StructMemberList{Member("a", ty.i32()),
-                            Member("b", ty.f32(), {MemberOffset(4)})},
-      ast::DecorationList{});
+  auto* s = Structure("S", {
+                               Member("a", ty.i32()),
+                               Member("b", ty.f32(), {MemberOffset(4)}),
+                           });
 
-  auto* s = ty.struct_("S", str);
   GeneratorImpl& gen = Build();
 
   ASSERT_TRUE(gen.EmitStructType(s)) << gen.error();
@@ -192,12 +180,13 @@
   ast::DecorationList decos;
   decos.push_back(create<ast::StructBlockDecoration>());
 
-  auto* str = create<ast::Struct>(
-      ast::StructMemberList{Member("a", ty.i32()),
-                            Member("b", ty.f32(), {MemberOffset(4)})},
-      decos);
+  auto* s = Structure("S",
+                      {
+                          Member("a", ty.i32()),
+                          Member("b", ty.f32(), {MemberOffset(4)}),
+                      },
+                      decos);
 
-  auto* s = ty.struct_("S", str);
   GeneratorImpl& gen = Build();
 
   ASSERT_TRUE(gen.EmitStructType(s)) << gen.error();
diff --git a/test/BUILD.gn b/test/BUILD.gn
index 775d5f4..a3eb062 100644
--- a/test/BUILD.gn
+++ b/test/BUILD.gn
@@ -145,7 +145,9 @@
     "../src/ast/sint_literal_test.cc",
     "../src/ast/stage_decoration_test.cc",
     "../src/ast/stride_decoration_test.cc",
+    "../src/ast/struct_member_align_decoration_test.cc",
     "../src/ast/struct_member_offset_decoration_test.cc",
+    "../src/ast/struct_member_size_decoration_test.cc",
     "../src/ast/struct_member_test.cc",
     "../src/ast/struct_test.cc",
     "../src/ast/switch_statement_test.cc",
@@ -168,9 +170,11 @@
     "../src/program_builder_test.cc",
     "../src/program_test.cc",
     "../src/resolver/intrinsic_test.cc",
-    "../src/resolver/resolver_test.cc",
+    "../src/resolver/is_storeable_test.cc",
     "../src/resolver/resolver_test_helper.cc",
     "../src/resolver/resolver_test_helper.h",
+    "../src/resolver/resolver_test.cc",
+    "../src/resolver/struct_layout_test.cc",
     "../src/resolver/validation_test.cc",
     "../src/scope_stack_test.cc",
     "../src/semantic/sem_intrinsic_test.cc",
diff --git a/test/compute_boids.wgsl b/test/compute_boids.wgsl
index 86617a6..0289b76 100644
--- a/test/compute_boids.wgsl
+++ b/test/compute_boids.wgsl
@@ -38,22 +38,22 @@
 
 // compute shader
 [[block]] struct Particle {
-  [[offset(0)]] pos : vec2<f32>;
-  [[offset(8)]] vel : vec2<f32>;
+  pos : vec2<f32>;
+  vel : vec2<f32>;
 };
 
 [[block]] struct SimParams {
-  [[offset(0)]] deltaT : f32;
-  [[offset(4)]] rule1Distance : f32;
-  [[offset(8)]] rule2Distance : f32;
-  [[offset(12)]] rule3Distance : f32;
-  [[offset(16)]] rule1Scale : f32;
-  [[offset(20)]] rule2Scale : f32;
-  [[offset(24)]] rule3Scale : f32;
+  deltaT : f32;
+  rule1Distance : f32;
+  rule2Distance : f32;
+  rule3Distance : f32;
+  rule1Scale : f32;
+  rule2Scale : f32;
+  rule3Scale : f32;
 };
 
 [[block]] struct Particles {
-  [[offset(0)]] particles : [[stride(16)]] array<Particle, 5>;
+  particles : array<Particle, 5>;
 };
 
 [[binding(0), group(0)]] var<uniform> params : [[access(read)]] SimParams;
diff --git a/test/cube.wgsl b/test/cube.wgsl
index 908756c..aef1be2 100644
--- a/test/cube.wgsl
+++ b/test/cube.wgsl
@@ -14,7 +14,7 @@
 
 // Vertex shader
 [[block]] struct Uniforms {
-  [[offset(0)]] modelViewProjectionMatrix : mat4x4<f32>;
+  modelViewProjectionMatrix : mat4x4<f32>;
 };
 
 [[binding(0), group(0)]] var<uniform> uniforms : [[access(read)]] Uniforms;