[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