Add DuplicateStorageStruct transform
This transform is currently required by Dawn for it's GLSL backend so
that SPIRV-Cross does not end up renaming the structures internally,
which it does to fix aliasing problems. We can remove this transform
if/once Dawn using binding numbers rather than names for uniform/storage
buffer data.
Bug: tint:386
Bug: tint:808
CloneContext: allow insert before/after of a node marked for removal
Change-Id: I3ff3b37bca62db07d5c759250dd4777e279b7a4f
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/51403
Commit-Queue: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
diff --git a/src/BUILD.gn b/src/BUILD.gn
index 8c0a9fe..89c5b96 100644
--- a/src/BUILD.gn
+++ b/src/BUILD.gn
@@ -509,6 +509,8 @@
"transform/canonicalize_entry_point_io.h",
"transform/decompose_storage_access.cc",
"transform/decompose_storage_access.h",
+ "transform/duplicate_storage_structs.cc",
+ "transform/duplicate_storage_structs.h",
"transform/external_texture_transform.cc",
"transform/external_texture_transform.h",
"transform/first_index_offset.cc",
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 9927bdd..f9f5a82 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -273,6 +273,8 @@
transform/canonicalize_entry_point_io.h
transform/decompose_storage_access.cc
transform/decompose_storage_access.h
+ transform/duplicate_storage_structs.cc
+ transform/duplicate_storage_structs.h
transform/external_texture_transform.cc
transform/external_texture_transform.h
transform/first_index_offset.cc
@@ -800,6 +802,7 @@
transform/calculate_array_length_test.cc
transform/canonicalize_entry_point_io_test.cc
transform/decompose_storage_access_test.cc
+ transform/duplicate_storage_structs_test.cc
transform/external_texture_transform_test.cc
transform/first_index_offset_test.cc
transform/renamer_test.cc
diff --git a/src/transform/duplicate_storage_structs.cc b/src/transform/duplicate_storage_structs.cc
new file mode 100644
index 0000000..42ffccc
--- /dev/null
+++ b/src/transform/duplicate_storage_structs.cc
@@ -0,0 +1,126 @@
+// 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/transform/duplicate_storage_structs.h"
+
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include "src/program_builder.h"
+#include "src/sem/variable.h"
+#include "src/utils/get_or_create.h"
+#include "src/utils/hash.h"
+
+namespace {
+struct StructAccessPair {
+ tint::sem::Struct const* str;
+ tint::ast::AccessControl::Access access;
+
+ bool operator==(const StructAccessPair& rhs) const {
+ return str == rhs.str && access == rhs.access;
+ }
+};
+} // namespace
+
+namespace std {
+/// Custom std::hash specialization for StructAccessPair so
+/// StructAccessPairs can be used as keys for std::unordered_map and
+/// std::unordered_set.
+template <>
+class hash<StructAccessPair> {
+ public:
+ /// @param struct_access_pair the StructAccessPair to create a hash for
+ /// @return the hash value
+ inline std::size_t operator()(
+ const StructAccessPair& struct_access_pair) const {
+ return tint::utils::Hash(struct_access_pair.str, struct_access_pair.access);
+ }
+};
+} // namespace std
+
+namespace tint {
+namespace transform {
+DuplicateStorageStructs::DuplicateStorageStructs() = default;
+DuplicateStorageStructs::~DuplicateStorageStructs() = default;
+
+Output DuplicateStorageStructs::Run(const Program* in, const DataMap&) {
+ ProgramBuilder out;
+ CloneContext ctx(&out, in);
+
+ // Create struct duplicates of storage buffers per access type.
+ // We do this by finding all storage variables, and creating a copy of the
+ // Struct they point to per struct/access pair.
+
+ std::unordered_map<StructAccessPair, ast::Struct*> sap_to_new_struct;
+
+ for (auto* node : in->ASTNodes().Objects()) {
+ if (auto* ast_var = node->As<ast::Variable>()) {
+ auto* var = in->Sem().Get(ast_var);
+ if (auto* str = var->Type()->UnwrapRef()->As<sem::Struct>()) {
+ // Skip non-storage structs
+ if (!str->UsedAs(ast::StorageClass::kStorage)) {
+ continue;
+ }
+
+ auto sap = StructAccessPair{str, var->AccessControl()};
+
+ auto* new_str = utils::GetOrCreate(sap_to_new_struct, sap, [&] {
+ // For this ast::Struct/Access pair, create a clone of the existing
+ // ast::Struct with a new name.
+ auto* old_str = str->Declaration();
+ std::string old_name = ctx.src->Symbols().NameFor(old_str->name());
+
+ auto* new_struct = [&] {
+ Symbol new_name = ctx.dst->Symbols().New(old_name);
+ auto new_members = ctx.Clone(old_str->members());
+ auto new_decorations = ctx.Clone(old_str->decorations());
+ return ctx.dst->create<ast::Struct>(new_name, new_members,
+ new_decorations);
+ }();
+
+ // Insert it after the original struct
+ ctx.InsertAfter(ctx.src->AST().GlobalDeclarations(),
+ str->Declaration(), new_struct);
+
+ // Remove the original struct
+ ctx.Remove(ctx.src->AST().GlobalDeclarations(), old_str);
+
+ return new_struct;
+ });
+
+ // Replace the existing variable with a new one who's type is an
+ // AccessControl<TypeName<"New Struct">>.
+ auto* new_var = [&] {
+ auto new_name = ctx.Clone(ast_var->symbol());
+ auto* new_ty = ctx.dst->ty.access(
+ var->AccessControl(), ctx.dst->ty.type_name(new_str->name()));
+ auto* new_constructor = ctx.Clone(ast_var->constructor());
+ auto new_decorations = ctx.Clone(ast_var->decorations());
+ return ctx.dst->create<ast::Variable>(
+ new_name, ast_var->declared_storage_class(), new_ty,
+ ast_var->is_const(), new_constructor, new_decorations);
+ }();
+ ctx.Replace(ast_var, new_var);
+ }
+ }
+ }
+
+ ctx.Clone();
+
+ return Output(Program(std::move(out)));
+}
+
+} // namespace transform
+} // namespace tint
diff --git a/src/transform/duplicate_storage_structs.h b/src/transform/duplicate_storage_structs.h
new file mode 100644
index 0000000..f843977
--- /dev/null
+++ b/src/transform/duplicate_storage_structs.h
@@ -0,0 +1,54 @@
+// 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_TRANSFORM_DUPLICATE_STORAGE_STRUCTS_H_
+#define SRC_TRANSFORM_DUPLICATE_STORAGE_STRUCTS_H_
+
+#include <string>
+#include <unordered_map>
+
+#include "src/transform/transform.h"
+
+namespace tint {
+namespace transform {
+
+/// DuplicateStorageStructs is a Transform that creates duplicates of storage
+/// structures, one per access control type.
+///
+/// This transform is currently required by Dawn for it's GLSL backend so that
+/// SPIRV-Cross does not end up renaming the structures internally, which it
+/// does to fix aliasing problems (see
+/// https://github.com/KhronosGroup/SPIRV-Cross/pull/799). We can remove this
+/// transform if/once Dawn using binding numbers rather than names for
+/// uniform/storage buffer data. See https://crbug.com/tint/386.
+///
+class DuplicateStorageStructs : public Transform {
+ public:
+ /// Constructor
+ DuplicateStorageStructs();
+
+ /// Destructor
+ ~DuplicateStorageStructs() override;
+
+ /// Runs the transform on `program`, returning the transformation result.
+ /// @param program the source program to transform
+ /// @param data optional extra transform-specific data
+ /// @returns the transformation result
+ Output Run(const Program* program, const DataMap& data = {}) override;
+};
+
+} // namespace transform
+} // namespace tint
+
+#endif // SRC_TRANSFORM_DUPLICATE_STORAGE_STRUCTS_H_
diff --git a/src/transform/duplicate_storage_structs_test.cc b/src/transform/duplicate_storage_structs_test.cc
new file mode 100644
index 0000000..ab58a1d
--- /dev/null
+++ b/src/transform/duplicate_storage_structs_test.cc
@@ -0,0 +1,275 @@
+// 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/transform/duplicate_storage_structs.h"
+
+#include "src/transform/test_helper.h"
+namespace tint {
+namespace transform {
+namespace {
+
+using DuplicateStorageStructsTransformTest = TransformTest;
+
+TEST_F(DuplicateStorageStructsTransformTest, Simple) {
+ auto* src = R"(
+[[block]]
+ struct S {
+ a : f32;
+};
+
+[[group(0), binding(0)]] var<storage> r : [[access(read)]] S;
+[[group(0), binding(1)]] var<storage> w : [[access(write)]] S;
+
+[[stage(compute)]]
+ fn main() {
+}
+)";
+
+ auto* expect = R"(
+[[block]]
+struct S_1 {
+ a : f32;
+};
+
+[[block]]
+struct S_2 {
+ a : f32;
+};
+
+[[group(0), binding(0)]] var<storage> r : [[access(read)]] S_1;
+
+[[group(0), binding(1)]] var<storage> w : [[access(write)]] S_2;
+
+[[stage(compute)]]
+fn main() {
+}
+)";
+
+ auto got = Run<DuplicateStorageStructs>(src);
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DuplicateStorageStructsTransformTest, MultipleStorageStructs) {
+ auto* src = R"(
+[[block]]
+ struct S1 {
+ a : f32;
+};
+
+[[block]]
+ struct S2 {
+ a : i32;
+};
+
+[[group(0), binding(0)]] var<storage> r1 : [[access(read)]] S1;
+[[group(0), binding(1)]] var<storage> w1 : [[access(write)]] S1;
+
+[[group(0), binding(2)]] var<storage> r2 : [[access(read)]] S2;
+[[group(0), binding(3)]] var<storage> w2 : [[access(write)]] S2;
+
+[[stage(compute)]]
+ fn main() {
+}
+)";
+
+ auto* expect = R"(
+[[block]]
+struct S1_1 {
+ a : f32;
+};
+
+[[block]]
+struct S1_2 {
+ a : f32;
+};
+
+[[block]]
+struct S2_1 {
+ a : i32;
+};
+
+[[block]]
+struct S2_2 {
+ a : i32;
+};
+
+[[group(0), binding(0)]] var<storage> r1 : [[access(read)]] S1_1;
+
+[[group(0), binding(1)]] var<storage> w1 : [[access(write)]] S1_2;
+
+[[group(0), binding(2)]] var<storage> r2 : [[access(read)]] S2_1;
+
+[[group(0), binding(3)]] var<storage> w2 : [[access(write)]] S2_2;
+
+[[stage(compute)]]
+fn main() {
+}
+)";
+
+ auto got = Run<DuplicateStorageStructs>(src);
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DuplicateStorageStructsTransformTest, MultipleStorageVariables) {
+ auto* src = R"(
+[[block]]
+ struct S {
+ a : f32;
+};
+
+[[group(0), binding(0)]] var<storage> r1 : [[access(read)]] S;
+[[group(0), binding(1)]] var<storage> r2 : [[access(read)]] S;
+[[group(0), binding(2)]] var<storage> r3 : [[access(read)]] S;
+[[group(0), binding(3)]] var<storage> w1 : [[access(write)]] S;
+[[group(0), binding(4)]] var<storage> w2 : [[access(write)]] S;
+[[group(0), binding(5)]] var<storage> w3 : [[access(write)]] S;
+[[group(0), binding(3)]] var<storage> rw1 : [[access(read_write)]] S;
+[[group(0), binding(4)]] var<storage> rw2 : [[access(read_write)]] S;
+[[group(0), binding(5)]] var<storage> rw3 : [[access(read_write)]] S;
+
+[[stage(compute)]]
+ fn main() {
+}
+)";
+
+ auto* expect = R"(
+[[block]]
+struct S_1 {
+ a : f32;
+};
+
+[[block]]
+struct S_2 {
+ a : f32;
+};
+
+[[block]]
+struct S_3 {
+ a : f32;
+};
+
+[[group(0), binding(0)]] var<storage> r1 : [[access(read)]] S_1;
+
+[[group(0), binding(1)]] var<storage> r2 : [[access(read)]] S_1;
+
+[[group(0), binding(2)]] var<storage> r3 : [[access(read)]] S_1;
+
+[[group(0), binding(3)]] var<storage> w1 : [[access(write)]] S_2;
+
+[[group(0), binding(4)]] var<storage> w2 : [[access(write)]] S_2;
+
+[[group(0), binding(5)]] var<storage> w3 : [[access(write)]] S_2;
+
+[[group(0), binding(3)]] var<storage> rw1 : [[access(read_write)]] S_3;
+
+[[group(0), binding(4)]] var<storage> rw2 : [[access(read_write)]] S_3;
+
+[[group(0), binding(5)]] var<storage> rw3 : [[access(read_write)]] S_3;
+
+[[stage(compute)]]
+fn main() {
+}
+)";
+
+ auto got = Run<DuplicateStorageStructs>(src);
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DuplicateStorageStructsTransformTest, MixedStructTypes) {
+ auto* src = R"(
+
+[[block]]
+struct S_1 {
+ a : f32;
+};
+
+[[block]]
+struct S_2 {
+ a : f32;
+};
+
+[[block]]
+struct S_3 {
+ a : f32;
+};
+
+[[group(0), binding(0)]] var<storage> r1 : [[access(read)]] S_1;
+
+[[group(0), binding(1)]] var<storage> r2 : [[access(read)]] S_1;
+
+[[group(0), binding(2)]] var<storage> r3 : [[access(read)]] S_1;
+
+[[group(0), binding(3)]] var<storage> w1 : [[access(write)]] S_2;
+
+[[group(0), binding(4)]] var<storage> w2 : [[access(write)]] S_2;
+
+[[group(0), binding(5)]] var<storage> w3 : [[access(write)]] S_2;
+
+[[group(0), binding(3)]] var<storage> rw1 : [[access(read_write)]] S_3;
+
+[[group(0), binding(4)]] var<storage> rw2 : [[access(read_write)]] S_3;
+
+[[group(0), binding(5)]] var<storage> rw3 : [[access(read_write)]] S_3;
+
+[[stage(compute)]]
+fn main() {
+}
+)";
+
+ auto* expect = R"(
+[[block]]
+struct S_1_1 {
+ a : f32;
+};
+
+[[block]]
+struct S_2_1 {
+ a : f32;
+};
+
+[[block]]
+struct S_3_1 {
+ a : f32;
+};
+
+[[group(0), binding(0)]] var<storage> r1 : [[access(read)]] S_1_1;
+
+[[group(0), binding(1)]] var<storage> r2 : [[access(read)]] S_1_1;
+
+[[group(0), binding(2)]] var<storage> r3 : [[access(read)]] S_1_1;
+
+[[group(0), binding(3)]] var<storage> w1 : [[access(write)]] S_2_1;
+
+[[group(0), binding(4)]] var<storage> w2 : [[access(write)]] S_2_1;
+
+[[group(0), binding(5)]] var<storage> w3 : [[access(write)]] S_2_1;
+
+[[group(0), binding(3)]] var<storage> rw1 : [[access(read_write)]] S_3_1;
+
+[[group(0), binding(4)]] var<storage> rw2 : [[access(read_write)]] S_3_1;
+
+[[group(0), binding(5)]] var<storage> rw3 : [[access(read_write)]] S_3_1;
+
+[[stage(compute)]]
+fn main() {
+}
+)";
+
+ auto got = Run<DuplicateStorageStructs>(src);
+ EXPECT_EQ(expect, str(got));
+}
+
+} // namespace
+} // namespace transform
+} // namespace tint
diff --git a/src/transform/spirv.cc b/src/transform/spirv.cc
index 20bdf68..75525cb 100644
--- a/src/transform/spirv.cc
+++ b/src/transform/spirv.cc
@@ -26,6 +26,7 @@
#include "src/sem/statement.h"
#include "src/sem/struct.h"
#include "src/sem/variable.h"
+#include "src/transform/duplicate_storage_structs.h"
#include "src/transform/external_texture_transform.h"
#include "src/transform/manager.h"
@@ -40,6 +41,7 @@
Output Spirv::Run(const Program* in, const DataMap& data) {
Manager manager;
manager.Add<ExternalTextureTransform>();
+ manager.Add<DuplicateStorageStructs>();
auto transformedInput = manager.Run(in, data);
auto* cfg = data.Get<Config>();
diff --git a/test/BUILD.gn b/test/BUILD.gn
index 867e8d7..616ab31 100644
--- a/test/BUILD.gn
+++ b/test/BUILD.gn
@@ -257,9 +257,9 @@
"../src/resolver/intrinsic_test.cc",
"../src/resolver/is_host_shareable_test.cc",
"../src/resolver/is_storeable_test.cc",
+ "../src/resolver/pipeline_overridable_constant_test.cc",
"../src/resolver/ptr_ref_test.cc",
"../src/resolver/ptr_ref_validation_test.cc",
- "../src/resolver/pipeline_overridable_constant_test.cc",
"../src/resolver/resolver_test.cc",
"../src/resolver/resolver_test_helper.cc",
"../src/resolver/resolver_test_helper.h",
@@ -299,6 +299,7 @@
"../src/transform/calculate_array_length_test.cc",
"../src/transform/canonicalize_entry_point_io_test.cc",
"../src/transform/decompose_storage_access_test.cc",
+ "../src/transform/duplicate_storage_structs_test.cc",
"../src/transform/external_texture_transform_test.cc",
"../src/transform/first_index_offset_test.cc",
"../src/transform/renamer_test.cc",