[ir][spirv-writer] Add BindingRemapper transform
Just remap binding points for now, leaving access mode remapping as
unimplemented.
Bug: tint:1906
Change-Id: If16909584ac37da5539a1a07e69f0b383f38f92f
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/143425
Auto-Submit: James Price <jrprice@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: James Price <jrprice@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
diff --git a/src/tint/BUILD.gn b/src/tint/BUILD.gn
index 60e8ad2..f60cd8f 100644
--- a/src/tint/BUILD.gn
+++ b/src/tint/BUILD.gn
@@ -493,6 +493,8 @@
sources = [
"lang/core/ir/transform/add_empty_entry_point.cc",
"lang/core/ir/transform/add_empty_entry_point.h",
+ "lang/core/ir/transform/binding_remapper.cc",
+ "lang/core/ir/transform/binding_remapper.h",
"lang/core/ir/transform/block_decorated_structs.cc",
"lang/core/ir/transform/block_decorated_structs.h",
"lang/core/ir/transform/demote_to_helper.cc",
@@ -1885,6 +1887,7 @@
tint_unittests_source_set("tint_unittests_ir_transform_src") {
sources = [
"lang/core/ir/transform/add_empty_entry_point_test.cc",
+ "lang/core/ir/transform/binding_remapper_test.cc",
"lang/core/ir/transform/block_decorated_structs_test.cc",
"lang/core/ir/transform/demote_to_helper_test.cc",
"lang/core/ir/transform/helper_test.h",
diff --git a/src/tint/CMakeLists.txt b/src/tint/CMakeLists.txt
index 84e30c8..8dc1023 100644
--- a/src/tint/CMakeLists.txt
+++ b/src/tint/CMakeLists.txt
@@ -872,6 +872,8 @@
lang/core/ir/var.h
lang/core/ir/transform/add_empty_entry_point.cc
lang/core/ir/transform/add_empty_entry_point.h
+ lang/core/ir/transform/binding_remapper.cc
+ lang/core/ir/transform/binding_remapper.h
lang/core/ir/transform/block_decorated_structs.cc
lang/core/ir/transform/block_decorated_structs.h
lang/core/ir/transform/demote_to_helper.cc
@@ -1671,6 +1673,7 @@
lang/core/ir/switch_test.cc
lang/core/ir/swizzle_test.cc
lang/core/ir/transform/add_empty_entry_point_test.cc
+ lang/core/ir/transform/binding_remapper_test.cc
lang/core/ir/transform/block_decorated_structs_test.cc
lang/core/ir/transform/demote_to_helper_test.cc
lang/core/ir/unary_test.cc
diff --git a/src/tint/lang/core/ir/transform/binding_remapper.cc b/src/tint/lang/core/ir/transform/binding_remapper.cc
new file mode 100644
index 0000000..04217bf
--- /dev/null
+++ b/src/tint/lang/core/ir/transform/binding_remapper.cc
@@ -0,0 +1,77 @@
+// 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/lang/core/ir/transform/binding_remapper.h"
+
+#include <utility>
+
+#include "src/tint/lang/core/ir/module.h"
+#include "src/tint/lang/core/ir/validator.h"
+#include "src/tint/lang/core/ir/var.h"
+#include "src/tint/utils/result/result.h"
+#include "src/tint/utils/text/string.h"
+
+using namespace tint::number_suffixes; // NOLINT
+
+namespace tint::ir::transform {
+
+namespace {
+
+Result<SuccessType, std::string> Run(ir::Module* ir, const BindingRemapperOptions& options) {
+ if (!options.access_controls.empty()) {
+ return std::string("remapping access controls is currently unsupported");
+ }
+ if (options.binding_points.empty()) {
+ return Success;
+ }
+ if (!ir->root_block) {
+ return Success;
+ }
+
+ // Find binding resources.
+ for (auto inst : *ir->root_block) {
+ auto* var = inst->As<Var>();
+ if (!var || !var->Alive()) {
+ continue;
+ }
+
+ auto bp = var->BindingPoint();
+ if (!bp) {
+ continue;
+ }
+
+ // Replace group and binding index if requested.
+ tint::BindingPoint from{bp->group, bp->binding};
+ auto to = options.binding_points.find(from);
+ if (to != options.binding_points.end()) {
+ var->SetBindingPoint(to->second.group, to->second.binding);
+ }
+ }
+
+ return Success;
+}
+
+} // namespace
+
+Result<SuccessType, std::string> BindingRemapper(Module* ir,
+ const BindingRemapperOptions& options) {
+ auto result = ValidateAndDumpIfNeeded(*ir, "BindingRemapper transform");
+ if (!result) {
+ return result;
+ }
+
+ return Run(ir, options);
+}
+
+} // namespace tint::ir::transform
diff --git a/src/tint/lang/core/ir/transform/binding_remapper.h b/src/tint/lang/core/ir/transform/binding_remapper.h
new file mode 100644
index 0000000..2c1fbe6
--- /dev/null
+++ b/src/tint/lang/core/ir/transform/binding_remapper.h
@@ -0,0 +1,39 @@
+// 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_LANG_CORE_IR_TRANSFORM_BINDING_REMAPPER_H_
+#define SRC_TINT_LANG_CORE_IR_TRANSFORM_BINDING_REMAPPER_H_
+
+#include <string>
+
+#include "src/tint/utils/result/result.h"
+#include "tint/binding_remapper_options.h"
+
+// Forward declarations.
+namespace tint::ir {
+class Module;
+}
+
+namespace tint::ir::transform {
+
+/// BindingRemapper is a transform that remaps binding point indices and access controls.
+/// @param module the module to transform
+/// @param options the remapping options
+/// @returns an error string on failure
+Result<SuccessType, std::string> BindingRemapper(Module* module,
+ const BindingRemapperOptions& options);
+
+} // namespace tint::ir::transform
+
+#endif // SRC_TINT_LANG_CORE_IR_TRANSFORM_BINDING_REMAPPER_H_
diff --git a/src/tint/lang/core/ir/transform/binding_remapper_test.cc b/src/tint/lang/core/ir/transform/binding_remapper_test.cc
new file mode 100644
index 0000000..9b02052
--- /dev/null
+++ b/src/tint/lang/core/ir/transform/binding_remapper_test.cc
@@ -0,0 +1,241 @@
+// 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/lang/core/ir/transform/binding_remapper.h"
+
+#include <utility>
+
+#include "src/tint/lang/core/ir/transform/helper_test.h"
+
+namespace tint::ir::transform {
+namespace {
+
+using namespace tint::builtin::fluent_types; // NOLINT
+using namespace tint::number_suffixes; // NOLINT
+
+using IR_BindingRemapperTest = TransformTest;
+
+TEST_F(IR_BindingRemapperTest, NoModify_NoRemappings) {
+ auto* buffer = b.Var("buffer", ty.ptr<uniform, i32>());
+ buffer->SetBindingPoint(0, 0);
+ b.RootBlock()->Append(buffer);
+
+ auto* src = R"(
+%b1 = block { # root
+ %buffer:ptr<uniform, i32, read_write> = var @binding_point(0, 0)
+}
+
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = src;
+
+ BindingRemapperOptions options;
+ Run(BindingRemapper, options);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_BindingRemapperTest, NoModify_RemappingDifferentBindingPoint) {
+ auto* buffer = b.Var("buffer", ty.ptr<uniform, i32>());
+ buffer->SetBindingPoint(0, 0);
+ b.RootBlock()->Append(buffer);
+
+ auto* src = R"(
+%b1 = block { # root
+ %buffer:ptr<uniform, i32, read_write> = var @binding_point(0, 0)
+}
+
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = src;
+
+ BindingRemapperOptions options;
+ options.binding_points[tint::BindingPoint{0u, 1u}] = tint::BindingPoint{1u, 0u};
+ Run(BindingRemapper, options);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_BindingRemapperTest, RemappingGroup) {
+ auto* buffer = b.Var("buffer", ty.ptr<uniform, i32>());
+ buffer->SetBindingPoint(1, 2);
+ b.RootBlock()->Append(buffer);
+
+ auto* src = R"(
+%b1 = block { # root
+ %buffer:ptr<uniform, i32, read_write> = var @binding_point(1, 2)
+}
+
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%b1 = block { # root
+ %buffer:ptr<uniform, i32, read_write> = var @binding_point(3, 2)
+}
+
+)";
+
+ BindingRemapperOptions options;
+ options.binding_points[tint::BindingPoint{1u, 2u}] = tint::BindingPoint{3u, 2u};
+ Run(BindingRemapper, options);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_BindingRemapperTest, RemappingBindingIndex) {
+ auto* buffer = b.Var("buffer", ty.ptr<uniform, i32>());
+ buffer->SetBindingPoint(1, 2);
+ b.RootBlock()->Append(buffer);
+
+ auto* src = R"(
+%b1 = block { # root
+ %buffer:ptr<uniform, i32, read_write> = var @binding_point(1, 2)
+}
+
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%b1 = block { # root
+ %buffer:ptr<uniform, i32, read_write> = var @binding_point(1, 3)
+}
+
+)";
+
+ BindingRemapperOptions options;
+ options.binding_points[tint::BindingPoint{1u, 2u}] = tint::BindingPoint{1u, 3u};
+ Run(BindingRemapper, options);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_BindingRemapperTest, RemappingGroupAndBindingIndex) {
+ auto* buffer = b.Var("buffer", ty.ptr<uniform, i32>());
+ buffer->SetBindingPoint(1, 2);
+ b.RootBlock()->Append(buffer);
+
+ auto* src = R"(
+%b1 = block { # root
+ %buffer:ptr<uniform, i32, read_write> = var @binding_point(1, 2)
+}
+
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%b1 = block { # root
+ %buffer:ptr<uniform, i32, read_write> = var @binding_point(3, 4)
+}
+
+)";
+
+ BindingRemapperOptions options;
+ options.binding_points[tint::BindingPoint{1u, 2u}] = tint::BindingPoint{3u, 4u};
+ Run(BindingRemapper, options);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_BindingRemapperTest, SwapTwoBindingPoints) {
+ auto* buffer_a = b.Var("buffer_a", ty.ptr<uniform, i32>());
+ buffer_a->SetBindingPoint(1, 2);
+ b.RootBlock()->Append(buffer_a);
+ auto* buffer_b = b.Var("buffer_b", ty.ptr<uniform, i32>());
+ buffer_b->SetBindingPoint(3, 4);
+ b.RootBlock()->Append(buffer_b);
+
+ auto* src = R"(
+%b1 = block { # root
+ %buffer_a:ptr<uniform, i32, read_write> = var @binding_point(1, 2)
+ %buffer_b:ptr<uniform, i32, read_write> = var @binding_point(3, 4)
+}
+
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%b1 = block { # root
+ %buffer_a:ptr<uniform, i32, read_write> = var @binding_point(3, 4)
+ %buffer_b:ptr<uniform, i32, read_write> = var @binding_point(1, 2)
+}
+
+)";
+
+ BindingRemapperOptions options;
+ options.binding_points[tint::BindingPoint{1u, 2u}] = tint::BindingPoint{3u, 4u};
+ options.binding_points[tint::BindingPoint{3u, 4u}] = tint::BindingPoint{1u, 2u};
+ Run(BindingRemapper, options);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_BindingRemapperTest, BindingPointCollisionSameEntryPoint) {
+ auto* buffer_a = b.Var("buffer_a", ty.ptr<uniform, i32>());
+ buffer_a->SetBindingPoint(1, 2);
+ b.RootBlock()->Append(buffer_a);
+ auto* buffer_b = b.Var("buffer_b", ty.ptr<uniform, i32>());
+ buffer_b->SetBindingPoint(3, 4);
+ b.RootBlock()->Append(buffer_b);
+
+ auto* ep = b.Function("main", mod.Types().void_(), Function::PipelineStage::kFragment);
+ b.Append(ep->Block(), [&] {
+ b.Load(buffer_a);
+ b.Load(buffer_b);
+ b.Return(ep);
+ });
+
+ auto* src = R"(
+%b1 = block { # root
+ %buffer_a:ptr<uniform, i32, read_write> = var @binding_point(1, 2)
+ %buffer_b:ptr<uniform, i32, read_write> = var @binding_point(3, 4)
+}
+
+%main = @fragment func():void -> %b2 {
+ %b2 = block {
+ %4:i32 = load %buffer_a
+ %5:i32 = load %buffer_b
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%b1 = block { # root
+ %buffer_a:ptr<uniform, i32, read_write> = var @binding_point(0, 1)
+ %buffer_b:ptr<uniform, i32, read_write> = var @binding_point(0, 1)
+}
+
+%main = @fragment func():void -> %b2 {
+ %b2 = block {
+ %4:i32 = load %buffer_a
+ %5:i32 = load %buffer_b
+ ret
+ }
+}
+)";
+
+ BindingRemapperOptions options;
+ options.binding_points[tint::BindingPoint{1u, 2u}] = tint::BindingPoint{0u, 1u};
+ options.binding_points[tint::BindingPoint{3u, 4u}] = tint::BindingPoint{0u, 1u};
+ Run(BindingRemapper, options);
+
+ EXPECT_EQ(expect, str());
+}
+
+} // namespace
+} // namespace tint::ir::transform
diff --git a/src/tint/lang/core/ir/transform/helper_test.h b/src/tint/lang/core/ir/transform/helper_test.h
index e055afb..8590171 100644
--- a/src/tint/lang/core/ir/transform/helper_test.h
+++ b/src/tint/lang/core/ir/transform/helper_test.h
@@ -54,9 +54,11 @@
/// Transforms the module, using @p transform.
/// @param transform_func the transform to run
- void Run(std::function<Result<SuccessType, std::string>(Module*)> transform_func) {
+ /// @param args the arguments to the transform function
+ template <typename TRANSFORM, typename... ARGS>
+ void Run(TRANSFORM&& transform_func, ARGS&&... args) {
// Run the transform.
- auto result = transform_func(&mod);
+ auto result = transform_func(&mod, args...);
EXPECT_TRUE(result) << result.Failure();
if (!result) {
return;
diff --git a/src/tint/lang/spirv/writer/writer.cc b/src/tint/lang/spirv/writer/writer.cc
index 4972769..1f364ce 100644
--- a/src/tint/lang/spirv/writer/writer.cc
+++ b/src/tint/lang/spirv/writer/writer.cc
@@ -19,6 +19,7 @@
#include "src/tint/lang/spirv/writer/ast_printer/ast_printer.h"
#if TINT_BUILD_IR
+#include "src/tint/lang/core/ir/transform/binding_remapper.h"
#include "src/tint/lang/spirv/writer/printer/printer.h" // nogncheck
#include "src/tint/lang/wgsl/reader/program_to_ir/program_to_ir.h" // nogncheck
#endif // TINT_BUILD_IR
@@ -46,8 +47,15 @@
return "IR converter: " + converted.Failure();
}
- // Generate the SPIR-V code.
auto ir = converted.Move();
+
+ // Apply transforms as required by writer options.
+ auto remapper = ir::transform::BindingRemapper(&ir, options.binding_remapper_options);
+ if (!remapper) {
+ return remapper.Failure();
+ }
+
+ // Generate the SPIR-V code.
auto impl = std::make_unique<Printer>(&ir, zero_initialize_workgroup_memory);
auto spirv = impl->Generate();
if (!spirv) {