[ir][spirv-writer] BlockDecoratedStructs transform

Use it to enable codegen for storage and uniform buffers.

Bug: tint:1906
Change-Id: I512b5da5aa9b49685158d1e821d531c731107e80
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/135767
Auto-Submit: James Price <jrprice@google.com>
Commit-Queue: James Price <jrprice@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Dan Sinclair <dsinclair@chromium.org>
Reviewed-by: Ben Clayton <bclayton@google.com>
diff --git a/src/tint/BUILD.gn b/src/tint/BUILD.gn
index 44e60a3..1ca8658 100644
--- a/src/tint/BUILD.gn
+++ b/src/tint/BUILD.gn
@@ -521,6 +521,8 @@
     sources = [
       "ir/transform/add_empty_entry_point.cc",
       "ir/transform/add_empty_entry_point.h",
+      "ir/transform/block_decorated_structs.cc",
+      "ir/transform/block_decorated_structs.h",
     ]
     deps = [
       ":libtint_builtins_src",
@@ -1814,6 +1816,7 @@
     tint_unittests_source_set("tint_unittests_ir_transform_src") {
       sources = [
         "ir/transform/add_empty_entry_point_test.cc",
+        "ir/transform/block_decorated_structs_test.cc",
         "ir/transform/test_helper.h",
       ]
 
@@ -2331,7 +2334,6 @@
         "ir/switch_test.cc",
         "ir/swizzle_test.cc",
         "ir/to_program_roundtrip_test.cc",
-        "ir/transform/add_empty_entry_point_test.cc",
         "ir/unary_test.cc",
         "ir/user_call_test.cc",
         "ir/validate_test.cc",
diff --git a/src/tint/CMakeLists.txt b/src/tint/CMakeLists.txt
index caef246..af06ca5 100644
--- a/src/tint/CMakeLists.txt
+++ b/src/tint/CMakeLists.txt
@@ -800,6 +800,8 @@
     ir/var.h
     ir/transform/add_empty_entry_point.cc
     ir/transform/add_empty_entry_point.h
+    ir/transform/block_decorated_structs.cc
+    ir/transform/block_decorated_structs.h
     ir/transform/transform.cc
     ir/transform/transform.h
   )
@@ -1534,6 +1536,7 @@
       ir/switch_test.cc
       ir/swizzle_test.cc
       ir/transform/add_empty_entry_point_test.cc
+      ir/transform/block_decorated_structs_test.cc
       ir/unary_test.cc
       ir/user_call_test.cc
       ir/validate_test.cc
diff --git a/src/tint/ir/disassembler.cc b/src/tint/ir/disassembler.cc
index 7b4a401..9f7aa8d 100644
--- a/src/tint/ir/disassembler.cc
+++ b/src/tint/ir/disassembler.cc
@@ -39,6 +39,7 @@
 #include "src/tint/ir/store.h"
 #include "src/tint/ir/switch.h"
 #include "src/tint/ir/swizzle.h"
+#include "src/tint/ir/transform/block_decorated_structs.h"
 #include "src/tint/ir/user_call.h"
 #include "src/tint/ir/var.h"
 #include "src/tint/switch.h"
@@ -747,9 +748,12 @@
 }
 
 void Disassembler::EmitStructDecl(const type::Struct* str) {
-    out_ << str->Name().Name() << " = struct @align(" << str->Align() << ") {";
+    out_ << str->Name().Name() << " = struct @align(" << str->Align() << ")";
+    if (str->StructFlags().Contains(type::StructFlag::kBlock)) {
+        out_ << ", @block";
+    }
+    out_ << " {";
     EmitLine();
-
     for (auto* member : str->Members()) {
         out_ << "  " << member->Name().Name() << ":" << member->Type()->FriendlyName();
         out_ << " @offset(" << member->Offset() << ")";
diff --git a/src/tint/ir/transform/block_decorated_structs.cc b/src/tint/ir/transform/block_decorated_structs.cc
new file mode 100644
index 0000000..f9f191c
--- /dev/null
+++ b/src/tint/ir/transform/block_decorated_structs.cc
@@ -0,0 +1,117 @@
+// Copyright 2023 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/tint/ir/transform/block_decorated_structs.h"
+
+#include <utility>
+
+#include "src/tint/ir/builder.h"
+#include "src/tint/ir/module.h"
+#include "src/tint/type/pointer.h"
+#include "src/tint/type/struct.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ir::transform::BlockDecoratedStructs);
+
+using namespace tint::number_suffixes;  // NOLINT
+
+namespace tint::ir::transform {
+
+BlockDecoratedStructs::BlockDecoratedStructs() = default;
+
+BlockDecoratedStructs::~BlockDecoratedStructs() = default;
+
+void BlockDecoratedStructs::Run(Module* ir, const DataMap&, DataMap&) const {
+    Builder builder(*ir);
+
+    if (!ir->root_block) {
+        return;
+    }
+
+    // Loop over module-scope declarations, looking for storage or uniform buffers.
+    utils::Vector<Var*, 8> buffer_variables;
+    for (auto inst : *ir->root_block) {
+        auto* var = inst->As<Var>();
+        if (!var) {
+            continue;
+        }
+        auto* ptr = var->Type()->As<type::Pointer>();
+        if (!ptr || !(ptr->AddressSpace() == builtin::AddressSpace::kStorage ||
+                      ptr->AddressSpace() == builtin::AddressSpace::kUniform)) {
+            continue;
+        }
+        buffer_variables.Push(var);
+    }
+
+    // Now process the buffer variables.
+    for (auto* var : buffer_variables) {
+        auto* ptr = var->Type()->As<type::Pointer>();
+        auto* store_ty = ptr->StoreType();
+
+        bool wrapped = false;
+        utils::Vector<const type::StructMember*, 4> members;
+
+        // Build the member list for the block-decorated structure.
+        if (auto* str = store_ty->As<type::Struct>(); str && !str->HasFixedFootprint()) {
+            // We know the original struct will only ever be used as the store type of a buffer, so
+            // just redeclare it as a block-decorated struct.
+            for (auto* member : str->Members()) {
+                members.Push(member);
+            }
+        } else {
+            // The original struct might be used in other places, so create a new block-decorated
+            // struct that wraps the original struct.
+            members.Push(ir->Types().Get<type::StructMember>(
+                /* name */ ir->symbols.New(),
+                /* type */ store_ty,
+                /* index */ 0u,
+                /* offset */ 0u,
+                /* align */ store_ty->Align(),
+                /* size */ store_ty->Size(),
+                /* attributes */ type::StructMemberAttributes{}));
+            wrapped = true;
+        }
+
+        // Create the block-decorated struct.
+        auto* block_struct = ir->Types().Get<type::Struct>(
+            /* name */ ir->symbols.New(),
+            /* members */ members,
+            /* align */ store_ty->Align(),
+            /* size */ utils::RoundUp(store_ty->Align(), store_ty->Size()),
+            /* size_no_padding */ store_ty->Size());
+        block_struct->SetStructFlag(type::StructFlag::kBlock);
+
+        // Replace the old variable declaration with one that uses the block-decorated struct type.
+        auto* new_var =
+            builder.Declare(ir->Types().pointer(block_struct, ptr->AddressSpace(), ptr->Access()));
+        new_var->SetBindingPoint(var->BindingPoint()->group, var->BindingPoint()->binding);
+        var->ReplaceWith(new_var);
+
+        // Replace uses of the old variable.
+        while (!var->Usages().IsEmpty()) {
+            auto& use = *var->Usages().begin();
+            if (wrapped) {
+                // The structure has been wrapped, so replace all uses of the old variable with a
+                // member accessor on the new variable.
+                auto* access =
+                    builder.Access(var->Type(), new_var, utils::Vector{builder.Constant(0_u)});
+                access->InsertBefore(use.instruction);
+                use.instruction->SetOperand(use.operand_index, access);
+            } else {
+                use.instruction->SetOperand(use.operand_index, new_var);
+            }
+        }
+    }
+}
+
+}  // namespace tint::ir::transform
diff --git a/src/tint/ir/transform/block_decorated_structs.h b/src/tint/ir/transform/block_decorated_structs.h
new file mode 100644
index 0000000..654f818
--- /dev/null
+++ b/src/tint/ir/transform/block_decorated_structs.h
@@ -0,0 +1,40 @@
+// Copyright 2023 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_TINT_IR_TRANSFORM_BLOCK_DECORATED_STRUCTS_H_
+#define SRC_TINT_IR_TRANSFORM_BLOCK_DECORATED_STRUCTS_H_
+
+#include "src/tint/ir/transform/transform.h"
+
+#include "src/tint/type/struct.h"
+
+namespace tint::ir::transform {
+
+/// BlockDecoratedStructs is a transform that changes the store type of a buffer to be a special
+/// structure that is recognized as needing a block decoration in SPIR-V, potentially wrapping the
+/// existing store type in a new structure if necessary.
+class BlockDecoratedStructs final : public utils::Castable<BlockDecoratedStructs, Transform> {
+  public:
+    /// Constructor
+    BlockDecoratedStructs();
+    /// Destructor
+    ~BlockDecoratedStructs() override;
+
+    /// @copydoc Transform::Run
+    void Run(ir::Module* module, const DataMap& inputs, DataMap& outputs) const override;
+};
+
+}  // namespace tint::ir::transform
+
+#endif  // SRC_TINT_IR_TRANSFORM_BLOCK_DECORATED_STRUCTS_H_
diff --git a/src/tint/ir/transform/block_decorated_structs_test.cc b/src/tint/ir/transform/block_decorated_structs_test.cc
new file mode 100644
index 0000000..a308f68
--- /dev/null
+++ b/src/tint/ir/transform/block_decorated_structs_test.cc
@@ -0,0 +1,334 @@
+// Copyright 2023 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/tint/ir/transform/block_decorated_structs.h"
+
+#include <utility>
+
+#include "src/tint/ir/transform/test_helper.h"
+#include "src/tint/type/array.h"
+#include "src/tint/type/pointer.h"
+#include "src/tint/type/struct.h"
+
+namespace tint::ir::transform {
+namespace {
+
+using IR_BlockDecoratedStructsTest = TransformTest;
+
+using namespace tint::number_suffixes;  // NOLINT
+
+TEST_F(IR_BlockDecoratedStructsTest, NoRootBlock) {
+    auto* func = b.CreateFunction("foo", ty.void_());
+    func->StartTarget()->Append(b.Return(func));
+    mod.functions.Push(func);
+
+    auto* expect = R"(
+%foo = func():void -> %b1 {
+  %b1 = block {
+    ret
+  }
+}
+)";
+
+    Run<BlockDecoratedStructs>();
+
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_BlockDecoratedStructsTest, Scalar_Uniform) {
+    auto* buffer = b.Declare(
+        ty.pointer(ty.i32(), builtin::AddressSpace::kUniform, builtin::Access::kReadWrite));
+    buffer->SetBindingPoint(0, 0);
+    b.CreateRootBlockIfNeeded()->Append(buffer);
+
+    auto* func = b.CreateFunction("foo", ty.i32());
+    auto* load = b.Load(buffer);
+    func->StartTarget()->Append(load);
+    func->StartTarget()->Append(b.Return(func, utils::Vector{load}));
+    mod.functions.Push(func);
+
+    auto* expect = R"(
+tint_symbol_1 = struct @align(4), @block {
+  tint_symbol:i32 @offset(0)
+}
+
+# Root block
+%b1 = block {
+  %1:ptr<uniform, tint_symbol_1, read_write> = var @binding_point(0, 0)
+}
+
+%foo = func():i32 -> %b2 {
+  %b2 = block {
+    %3:ptr<uniform, i32, read_write> = access %1, 0u
+    %4:i32 = load %3
+    ret %4
+  }
+}
+)";
+
+    Run<BlockDecoratedStructs>();
+
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_BlockDecoratedStructsTest, Scalar_Storage) {
+    auto* buffer = b.Declare(
+        ty.pointer(ty.i32(), builtin::AddressSpace::kStorage, builtin::Access::kReadWrite));
+    buffer->SetBindingPoint(0, 0);
+    b.CreateRootBlockIfNeeded()->Append(buffer);
+
+    auto* func = b.CreateFunction("foo", ty.void_());
+    func->StartTarget()->Append(b.Store(buffer, b.Constant(42_i)));
+    func->StartTarget()->Append(b.Return(func));
+    mod.functions.Push(func);
+
+    auto* expect = R"(
+tint_symbol_1 = struct @align(4), @block {
+  tint_symbol:i32 @offset(0)
+}
+
+# Root block
+%b1 = block {
+  %1:ptr<storage, tint_symbol_1, read_write> = var @binding_point(0, 0)
+}
+
+%foo = func():void -> %b2 {
+  %b2 = block {
+    %3:ptr<storage, i32, read_write> = access %1, 0u
+    store %3, 42i
+    ret
+  }
+}
+)";
+
+    Run<BlockDecoratedStructs>();
+
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_BlockDecoratedStructsTest, RuntimeArray) {
+    auto* buffer = b.Declare(ty.pointer(ty.runtime_array(ty.i32()), builtin::AddressSpace::kStorage,
+                                        builtin::Access::kReadWrite));
+    buffer->SetBindingPoint(0, 0);
+    b.CreateRootBlockIfNeeded()->Append(buffer);
+
+    auto* func = b.CreateFunction("foo", ty.void_());
+    auto* access =
+        b.Access(ty.pointer(ty.i32(), builtin::AddressSpace::kStorage, builtin::Access::kReadWrite),
+                 buffer, utils::Vector{b.Constant(1_u)});
+    func->StartTarget()->Append(access);
+    func->StartTarget()->Append(b.Store(access, b.Constant(42_i)));
+    func->StartTarget()->Append(b.Return(func));
+    mod.functions.Push(func);
+
+    auto* expect = R"(
+tint_symbol_1 = struct @align(4), @block {
+  tint_symbol:array<i32> @offset(0)
+}
+
+# Root block
+%b1 = block {
+  %1:ptr<storage, tint_symbol_1, read_write> = var @binding_point(0, 0)
+}
+
+%foo = func():void -> %b2 {
+  %b2 = block {
+    %3:ptr<storage, array<i32>, read_write> = access %1, 0u
+    %4:ptr<storage, i32, read_write> = access %3, 1u
+    store %4, 42i
+    ret
+  }
+}
+)";
+
+    Run<BlockDecoratedStructs>();
+
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_BlockDecoratedStructsTest, RuntimeArray_InStruct) {
+    utils::Vector<const type::StructMember*, 4> members;
+    members.Push(ty.Get<type::StructMember>(mod.symbols.New(), ty.i32(), 0u, 0u, 4u, 4u,
+                                            type::StructMemberAttributes{}));
+    members.Push(ty.Get<type::StructMember>(mod.symbols.New(), ty.runtime_array(ty.i32()), 1u, 4u,
+                                            4u, 4u, type::StructMemberAttributes{}));
+    auto* structure = ty.Get<type::Struct>(mod.symbols.New(), members, 4u, 8u, 8u);
+
+    auto* buffer = b.Declare(
+        ty.pointer(structure, builtin::AddressSpace::kStorage, builtin::Access::kReadWrite));
+    buffer->SetBindingPoint(0, 0);
+    b.CreateRootBlockIfNeeded()->Append(buffer);
+
+    auto* i32_ptr =
+        ty.pointer(ty.i32(), builtin::AddressSpace::kStorage, builtin::Access::kReadWrite);
+
+    auto* func = b.CreateFunction("foo", ty.void_());
+    auto* val_ptr = b.Access(i32_ptr, buffer, utils::Vector{b.Constant(0_u)});
+    auto* load = b.Load(val_ptr);
+    auto* elem_ptr = b.Access(i32_ptr, buffer, utils::Vector{b.Constant(1_u), b.Constant(3_u)});
+    func->StartTarget()->Append(val_ptr);
+    func->StartTarget()->Append(load);
+    func->StartTarget()->Append(elem_ptr);
+    func->StartTarget()->Append(b.Store(elem_ptr, load));
+    func->StartTarget()->Append(b.Return(func));
+    mod.functions.Push(func);
+
+    auto* expect = R"(
+tint_symbol_2 = struct @align(4) {
+  tint_symbol:i32 @offset(0)
+  tint_symbol_1:array<i32> @offset(4)
+}
+
+tint_symbol_3 = struct @align(4), @block {
+  tint_symbol:i32 @offset(0)
+  tint_symbol_1:array<i32> @offset(4)
+}
+
+# Root block
+%b1 = block {
+  %1:ptr<storage, tint_symbol_3, read_write> = var @binding_point(0, 0)
+}
+
+%foo = func():void -> %b2 {
+  %b2 = block {
+    %3:ptr<storage, i32, read_write> = access %1, 0u
+    %4:i32 = load %3
+    %5:ptr<storage, i32, read_write> = access %1, 1u, 3u
+    store %5, %4
+    ret
+  }
+}
+)";
+
+    Run<BlockDecoratedStructs>();
+
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_BlockDecoratedStructsTest, StructUsedElsewhere) {
+    utils::Vector<const type::StructMember*, 4> members;
+    members.Push(ty.Get<type::StructMember>(mod.symbols.New(), ty.i32(), 0u, 0u, 4u, 4u,
+                                            type::StructMemberAttributes{}));
+    members.Push(ty.Get<type::StructMember>(mod.symbols.New(), ty.i32(), 1u, 4u, 4u, 4u,
+                                            type::StructMemberAttributes{}));
+    auto* structure = ty.Get<type::Struct>(mod.symbols.New(), members, 4u, 8u, 8u);
+
+    auto* buffer = b.Declare(
+        ty.pointer(structure, builtin::AddressSpace::kStorage, builtin::Access::kReadWrite));
+    buffer->SetBindingPoint(0, 0);
+    b.CreateRootBlockIfNeeded()->Append(buffer);
+
+    auto* private_var = b.Declare(
+        ty.pointer(structure, builtin::AddressSpace::kPrivate, builtin::Access::kReadWrite));
+    b.CreateRootBlockIfNeeded()->Append(private_var);
+
+    auto* func = b.CreateFunction("foo", ty.void_());
+    func->StartTarget()->Append(b.Store(buffer, private_var));
+    func->StartTarget()->Append(b.Return(func));
+    mod.functions.Push(func);
+
+    auto* expect = R"(
+tint_symbol_2 = struct @align(4) {
+  tint_symbol:i32 @offset(0)
+  tint_symbol_1:i32 @offset(4)
+}
+
+tint_symbol_4 = struct @align(4), @block {
+  tint_symbol_3:tint_symbol_2 @offset(0)
+}
+
+# Root block
+%b1 = block {
+  %1:ptr<storage, tint_symbol_4, read_write> = var @binding_point(0, 0)
+  %2:ptr<private, tint_symbol_2, read_write> = var
+}
+
+%foo = func():void -> %b2 {
+  %b2 = block {
+    %4:ptr<storage, tint_symbol_2, read_write> = access %1, 0u
+    store %4, %2
+    ret
+  }
+}
+)";
+
+    Run<BlockDecoratedStructs>();
+
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_BlockDecoratedStructsTest, MultipleBuffers) {
+    auto* buffer_a = b.Declare(
+        ty.pointer(ty.i32(), builtin::AddressSpace::kStorage, builtin::Access::kReadWrite));
+    auto* buffer_b = b.Declare(
+        ty.pointer(ty.i32(), builtin::AddressSpace::kStorage, builtin::Access::kReadWrite));
+    auto* buffer_c = b.Declare(
+        ty.pointer(ty.i32(), builtin::AddressSpace::kStorage, builtin::Access::kReadWrite));
+    buffer_a->SetBindingPoint(0, 0);
+    buffer_b->SetBindingPoint(0, 1);
+    buffer_c->SetBindingPoint(0, 2);
+    auto* root = b.CreateRootBlockIfNeeded();
+    root->Append(buffer_a);
+    root->Append(buffer_b);
+    root->Append(buffer_c);
+
+    auto* func = b.CreateFunction("foo", ty.void_());
+    auto* load_b = b.Load(buffer_b);
+    auto* load_c = b.Load(buffer_c);
+    func->StartTarget()->Append(load_b);
+    func->StartTarget()->Append(load_c);
+    func->StartTarget()->Append(b.Store(buffer_a, b.Add(ty.i32(), load_b, load_c)));
+    func->StartTarget()->Append(b.Return(func));
+    mod.functions.Push(func);
+
+    auto* expect = R"(
+tint_symbol_1 = struct @align(4), @block {
+  tint_symbol:i32 @offset(0)
+}
+
+tint_symbol_3 = struct @align(4), @block {
+  tint_symbol_2:i32 @offset(0)
+}
+
+tint_symbol_5 = struct @align(4), @block {
+  tint_symbol_4:i32 @offset(0)
+}
+
+# Root block
+%b1 = block {
+  %1:ptr<storage, tint_symbol_1, read_write> = var @binding_point(0, 0)
+  %2:ptr<storage, tint_symbol_3, read_write> = var @binding_point(0, 1)
+  %3:ptr<storage, tint_symbol_5, read_write> = var @binding_point(0, 2)
+}
+
+%foo = func():void -> %b2 {
+  %b2 = block {
+    %5:ptr<storage, i32, read_write> = access %2, 0u
+    %6:i32 = load %5
+    %7:ptr<storage, i32, read_write> = access %3, 0u
+    %8:i32 = load %7
+    %9:ptr<storage, i32, read_write> = access %1, 0u
+    store %9, %10
+    ret
+  }
+}
+)";
+
+    Run<BlockDecoratedStructs>();
+
+    EXPECT_EQ(expect, str());
+}
+
+}  // namespace
+}  // namespace tint::ir::transform
diff --git a/src/tint/ir/transform/test_helper.h b/src/tint/ir/transform/test_helper.h
index a122eb2..83ff8c2 100644
--- a/src/tint/ir/transform/test_helper.h
+++ b/src/tint/ir/transform/test_helper.h
@@ -57,6 +57,8 @@
     ir::Module mod;
     /// The test IR builder.
     ir::Builder b{mod};
+    /// The type manager.
+    type::Manager& ty{mod.Types()};
 
   private:
     std::vector<std::unique_ptr<Source::File>> files_;
diff --git a/src/tint/type/struct.h b/src/tint/type/struct.h
index 08119d0..dd2f7da 100644
--- a/src/tint/type/struct.h
+++ b/src/tint/type/struct.h
@@ -45,6 +45,14 @@
     kComputeOutput,
 };
 
+enum StructFlag {
+    /// The structure is a block-decorated structure (for SPIR-V or GLSL).
+    kBlock,
+};
+
+/// An alias to utils::EnumSet<StructFlag>
+using StructFlags = utils::EnumSet<StructFlag>;
+
 /// Struct holds the Type information for structures.
 class Struct : public utils::Castable<Struct, Type> {
   public:
@@ -94,6 +102,13 @@
     /// alignment padding
     uint32_t SizeNoPadding() const { return size_no_padding_; }
 
+    /// @returns the structure flags
+    type::StructFlags StructFlags() const { return struct_flags_; }
+
+    /// Set a structure flag.
+    /// @param flag the flag to set
+    void SetStructFlag(StructFlag flag) { struct_flags_.Add(flag); }
+
     /// Adds the AddressSpace usage to the structure.
     /// @param usage the storage usage
     void AddUsage(builtin::AddressSpace usage) { address_space_usage_.emplace(usage); }
@@ -153,6 +168,7 @@
     const uint32_t align_;
     const uint32_t size_;
     const uint32_t size_no_padding_;
+    type::StructFlags struct_flags_;
     std::unordered_set<builtin::AddressSpace> address_space_usage_;
     std::unordered_set<PipelineStageUsage> pipeline_stage_uses_;
     utils::Vector<const Struct*, 2> concrete_types_;
diff --git a/src/tint/writer/spirv/ir/generator_impl_ir.cc b/src/tint/writer/spirv/ir/generator_impl_ir.cc
index 9e5e742..773c96f 100644
--- a/src/tint/writer/spirv/ir/generator_impl_ir.cc
+++ b/src/tint/writer/spirv/ir/generator_impl_ir.cc
@@ -37,6 +37,7 @@
 #include "src/tint/ir/store.h"
 #include "src/tint/ir/switch.h"
 #include "src/tint/ir/transform/add_empty_entry_point.h"
+#include "src/tint/ir/transform/block_decorated_structs.h"
 #include "src/tint/ir/user_call.h"
 #include "src/tint/ir/validate.h"
 #include "src/tint/ir/var.h"
@@ -66,6 +67,7 @@
     transform::DataMap data;
 
     manager.Add<ir::transform::AddEmptyEntryPoint>();
+    manager.Add<ir::transform::BlockDecoratedStructs>();
 
     transform::DataMap outputs;
     manager.Run(module, data, outputs);
@@ -299,6 +301,11 @@
     }
     module_.PushType(spv::Op::OpTypeStruct, std::move(operands));
 
+    // Add a Block decoration if necessary.
+    if (str->StructFlags().Contains(type::StructFlag::kBlock)) {
+        module_.PushAnnot(spv::Op::OpDecorate, {id, U32Operand(SpvDecorationBlock)});
+    }
+
     if (str->Name().IsValid()) {
         module_.PushDebug(spv::Op::OpName, {operands[0], Operand(str->Name().Name())});
     }
@@ -890,6 +897,18 @@
             module_.PushType(spv::Op::OpVariable, operands);
             break;
         }
+        case builtin::AddressSpace::kStorage:
+        case builtin::AddressSpace::kUniform: {
+            TINT_ASSERT(Writer, !current_function_);
+            module_.PushType(spv::Op::OpVariable,
+                             {ty, id, U32Operand(StorageClass(ptr->AddressSpace()))});
+            auto bp = var->BindingPoint().value();
+            module_.PushAnnot(spv::Op::OpDecorate,
+                              {id, U32Operand(SpvDecorationDescriptorSet), bp.group});
+            module_.PushAnnot(spv::Op::OpDecorate,
+                              {id, U32Operand(SpvDecorationBinding), bp.binding});
+            break;
+        }
         case builtin::AddressSpace::kWorkgroup: {
             TINT_ASSERT(Writer, !current_function_);
             OperandList operands = {ty, id, U32Operand(SpvStorageClassWorkgroup)};
diff --git a/src/tint/writer/spirv/ir/generator_impl_ir_var_test.cc b/src/tint/writer/spirv/ir/generator_impl_ir_var_test.cc
index cc193d8..70c1712 100644
--- a/src/tint/writer/spirv/ir/generator_impl_ir_var_test.cc
+++ b/src/tint/writer/spirv/ir/generator_impl_ir_var_test.cc
@@ -409,5 +409,224 @@
 )");
 }
 
+TEST_F(SpvGeneratorImplTest, StorageVar) {
+    auto* v = b.Declare(
+        ty.pointer(ty.i32(), builtin::AddressSpace::kStorage, builtin::Access::kReadWrite));
+    v->SetBindingPoint(0, 0);
+    b.CreateRootBlockIfNeeded()->SetInstructions(utils::Vector{v});
+
+    ASSERT_TRUE(generator_.Generate()) << generator_.Diagnostics().str();
+    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %5 "unused_entry_point"
+OpExecutionMode %5 LocalSize 1 1 1
+OpMemberName %3 0 "tint_symbol"
+OpName %3 "tint_symbol_1"
+OpName %5 "unused_entry_point"
+OpMemberDecorate %3 0 Offset 0
+OpDecorate %3 Block
+OpDecorate %1 DescriptorSet 0
+OpDecorate %1 Binding 0
+%4 = OpTypeInt 32 1
+%3 = OpTypeStruct %4
+%2 = OpTypePointer StorageBuffer %3
+%1 = OpVariable %2 StorageBuffer
+%6 = OpTypeVoid
+%7 = OpTypeFunction %6
+%5 = OpFunction %6 None %7
+%8 = OpLabel
+OpReturn
+OpFunctionEnd
+)");
+}
+
+TEST_F(SpvGeneratorImplTest, StorageVar_Name) {
+    auto* v = b.Declare(
+        ty.pointer(ty.i32(), builtin::AddressSpace::kStorage, builtin::Access::kReadWrite));
+    v->SetBindingPoint(0, 0);
+    b.CreateRootBlockIfNeeded()->SetInstructions(utils::Vector{v});
+    mod.SetName(v, "myvar");
+
+    ASSERT_TRUE(generator_.Generate()) << generator_.Diagnostics().str();
+    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %5 "unused_entry_point"
+OpExecutionMode %5 LocalSize 1 1 1
+OpMemberName %3 0 "tint_symbol"
+OpName %3 "tint_symbol_1"
+OpName %5 "unused_entry_point"
+OpMemberDecorate %3 0 Offset 0
+OpDecorate %3 Block
+OpDecorate %1 DescriptorSet 0
+OpDecorate %1 Binding 0
+%4 = OpTypeInt 32 1
+%3 = OpTypeStruct %4
+%2 = OpTypePointer StorageBuffer %3
+%1 = OpVariable %2 StorageBuffer
+%6 = OpTypeVoid
+%7 = OpTypeFunction %6
+%5 = OpFunction %6 None %7
+%8 = OpLabel
+OpReturn
+OpFunctionEnd
+)");
+}
+
+TEST_F(SpvGeneratorImplTest, StorageVar_LoadAndStore) {
+    auto* v = b.Declare(
+        ty.pointer(ty.i32(), builtin::AddressSpace::kStorage, builtin::Access::kReadWrite));
+    v->SetBindingPoint(0, 0);
+    b.CreateRootBlockIfNeeded()->SetInstructions(utils::Vector{v});
+
+    auto* func = b.CreateFunction("foo", ty.void_(), ir::Function::PipelineStage::kCompute,
+                                  std::array{1u, 1u, 1u});
+    mod.functions.Push(func);
+
+    auto* load = b.Load(v);
+    auto* add = b.Add(ty.i32(), v, b.Constant(1_i));
+    auto* store = b.Store(v, add);
+    func->StartTarget()->SetInstructions(utils::Vector{load, add, store, b.Return(func)});
+
+    ASSERT_TRUE(generator_.Generate()) << generator_.Diagnostics().str();
+    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %5 "foo"
+OpExecutionMode %5 LocalSize 1 1 1
+OpMemberName %3 0 "tint_symbol"
+OpName %3 "tint_symbol_1"
+OpName %5 "foo"
+OpMemberDecorate %3 0 Offset 0
+OpDecorate %3 Block
+OpDecorate %1 DescriptorSet 0
+OpDecorate %1 Binding 0
+%4 = OpTypeInt 32 1
+%3 = OpTypeStruct %4
+%2 = OpTypePointer StorageBuffer %3
+%1 = OpVariable %2 StorageBuffer
+%6 = OpTypeVoid
+%7 = OpTypeFunction %6
+%10 = OpTypePointer StorageBuffer %4
+%12 = OpTypeInt 32 0
+%11 = OpConstant %12 0
+%16 = OpConstant %4 1
+%5 = OpFunction %6 None %7
+%8 = OpLabel
+%9 = OpAccessChain %10 %1 %11
+%13 = OpLoad %4 %9
+%14 = OpAccessChain %10 %1 %11
+%15 = OpIAdd %4 %14 %16
+%17 = OpAccessChain %10 %1 %11
+OpStore %17 %15
+OpReturn
+OpFunctionEnd
+)");
+}
+
+TEST_F(SpvGeneratorImplTest, UniformVar) {
+    auto* v = b.Declare(
+        ty.pointer(ty.i32(), builtin::AddressSpace::kUniform, builtin::Access::kReadWrite));
+    v->SetBindingPoint(0, 0);
+    b.CreateRootBlockIfNeeded()->SetInstructions(utils::Vector{v});
+
+    ASSERT_TRUE(generator_.Generate()) << generator_.Diagnostics().str();
+    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %5 "unused_entry_point"
+OpExecutionMode %5 LocalSize 1 1 1
+OpMemberName %3 0 "tint_symbol"
+OpName %3 "tint_symbol_1"
+OpName %5 "unused_entry_point"
+OpMemberDecorate %3 0 Offset 0
+OpDecorate %3 Block
+OpDecorate %1 DescriptorSet 0
+OpDecorate %1 Binding 0
+%4 = OpTypeInt 32 1
+%3 = OpTypeStruct %4
+%2 = OpTypePointer Uniform %3
+%1 = OpVariable %2 Uniform
+%6 = OpTypeVoid
+%7 = OpTypeFunction %6
+%5 = OpFunction %6 None %7
+%8 = OpLabel
+OpReturn
+OpFunctionEnd
+)");
+}
+
+TEST_F(SpvGeneratorImplTest, UniformVar_Name) {
+    auto* v = b.Declare(
+        ty.pointer(ty.i32(), builtin::AddressSpace::kUniform, builtin::Access::kReadWrite));
+    v->SetBindingPoint(0, 0);
+    b.CreateRootBlockIfNeeded()->SetInstructions(utils::Vector{v});
+    mod.SetName(v, "myvar");
+
+    ASSERT_TRUE(generator_.Generate()) << generator_.Diagnostics().str();
+    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %5 "unused_entry_point"
+OpExecutionMode %5 LocalSize 1 1 1
+OpMemberName %3 0 "tint_symbol"
+OpName %3 "tint_symbol_1"
+OpName %5 "unused_entry_point"
+OpMemberDecorate %3 0 Offset 0
+OpDecorate %3 Block
+OpDecorate %1 DescriptorSet 0
+OpDecorate %1 Binding 0
+%4 = OpTypeInt 32 1
+%3 = OpTypeStruct %4
+%2 = OpTypePointer Uniform %3
+%1 = OpVariable %2 Uniform
+%6 = OpTypeVoid
+%7 = OpTypeFunction %6
+%5 = OpFunction %6 None %7
+%8 = OpLabel
+OpReturn
+OpFunctionEnd
+)");
+}
+
+TEST_F(SpvGeneratorImplTest, UniformVar_Load) {
+    auto* v = b.Declare(
+        ty.pointer(ty.i32(), builtin::AddressSpace::kUniform, builtin::Access::kReadWrite));
+    v->SetBindingPoint(0, 0);
+    b.CreateRootBlockIfNeeded()->SetInstructions(utils::Vector{v});
+
+    auto* func = b.CreateFunction("foo", ty.void_(), ir::Function::PipelineStage::kCompute,
+                                  std::array{1u, 1u, 1u});
+    mod.functions.Push(func);
+
+    auto* load = b.Load(v);
+    func->StartTarget()->SetInstructions(utils::Vector{load, b.Return(func)});
+
+    ASSERT_TRUE(generator_.Generate()) << generator_.Diagnostics().str();
+    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %5 "foo"
+OpExecutionMode %5 LocalSize 1 1 1
+OpMemberName %3 0 "tint_symbol"
+OpName %3 "tint_symbol_1"
+OpName %5 "foo"
+OpMemberDecorate %3 0 Offset 0
+OpDecorate %3 Block
+OpDecorate %1 DescriptorSet 0
+OpDecorate %1 Binding 0
+%4 = OpTypeInt 32 1
+%3 = OpTypeStruct %4
+%2 = OpTypePointer Uniform %3
+%1 = OpVariable %2 Uniform
+%6 = OpTypeVoid
+%7 = OpTypeFunction %6
+%10 = OpTypePointer Uniform %4
+%12 = OpTypeInt 32 0
+%11 = OpConstant %12 0
+%5 = OpFunction %6 None %7
+%8 = OpLabel
+%9 = OpAccessChain %10 %1 %11
+%13 = OpLoad %4 %9
+OpReturn
+OpFunctionEnd
+)");
+}
+
 }  // namespace
 }  // namespace tint::writer::spirv