Add simple dead code eliminator transform.
This CL adds a new transform, used by the SPIR-V Reader lower pass, to
do a simple dead code elimination pass over the module. To start this
pass will remove:
* Unused functions.
* Unused module scoped `__in`, `__out` and `private` vars.
The SPIR-V IR reader pass then uses this to remove any unused
Input/Output variables from the program.
Bug: 42250952
Change-Id: I9022248702cde50c020dc7724a609333172604b3
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/246694
Reviewed-by: James Price <jrprice@google.com>
Commit-Queue: dan sinclair <dsinclair@chromium.org>
diff --git a/src/tint/lang/core/ir/module.cc b/src/tint/lang/core/ir/module.cc
index 73adfa7..a112b13 100644
--- a/src/tint/lang/core/ir/module.cc
+++ b/src/tint/lang/core/ir/module.cc
@@ -183,4 +183,9 @@
return FunctionSorter<const Function>::SortFunctions(*this);
}
+void Module::Destroy(Function* func) {
+ functions.EraseIf([&](const core::ir::Function* o) { return o == func; });
+ func->Destroy();
+}
+
} // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/module.h b/src/tint/lang/core/ir/module.h
index 15639d6..7100195 100644
--- a/src/tint/lang/core/ir/module.h
+++ b/src/tint/lang/core/ir/module.h
@@ -178,6 +178,10 @@
/// @returns the functions in the module, in dependency order
Vector<const Function*, 16> DependencyOrderedFunctions() const;
+ /// Removes `func` from the module and destroys it.
+ /// @param func the function to destroy
+ void Destroy(Function* func);
+
/// The block allocator
BlockAllocator<Block> blocks;
diff --git a/src/tint/lang/core/ir/transform/BUILD.bazel b/src/tint/lang/core/ir/transform/BUILD.bazel
index 1e1e14e..b801ad0 100644
--- a/src/tint/lang/core/ir/transform/BUILD.bazel
+++ b/src/tint/lang/core/ir/transform/BUILD.bazel
@@ -48,6 +48,7 @@
"builtin_polyfill.cc",
"combine_access_instructions.cc",
"conversion_polyfill.cc",
+ "dead_code_elimination.cc",
"demote_to_helper.cc",
"direct_variable_access.cc",
"multiplanar_external_texture.cc",
@@ -77,6 +78,7 @@
"builtin_polyfill.h",
"combine_access_instructions.h",
"conversion_polyfill.h",
+ "dead_code_elimination.h",
"demote_to_helper.h",
"direct_variable_access.h",
"multiplanar_external_texture.h",
@@ -134,6 +136,7 @@
"builtin_polyfill_test.cc",
"combine_access_instructions_test.cc",
"conversion_polyfill_test.cc",
+ "dead_code_elimination_test.cc",
"demote_to_helper_test.cc",
"direct_variable_access_test.cc",
"helper_test.h",
diff --git a/src/tint/lang/core/ir/transform/BUILD.cmake b/src/tint/lang/core/ir/transform/BUILD.cmake
index de4583d..3f2abf8 100644
--- a/src/tint/lang/core/ir/transform/BUILD.cmake
+++ b/src/tint/lang/core/ir/transform/BUILD.cmake
@@ -57,6 +57,8 @@
lang/core/ir/transform/combine_access_instructions.h
lang/core/ir/transform/conversion_polyfill.cc
lang/core/ir/transform/conversion_polyfill.h
+ lang/core/ir/transform/dead_code_elimination.cc
+ lang/core/ir/transform/dead_code_elimination.h
lang/core/ir/transform/demote_to_helper.cc
lang/core/ir/transform/demote_to_helper.h
lang/core/ir/transform/direct_variable_access.cc
@@ -135,6 +137,7 @@
lang/core/ir/transform/builtin_polyfill_test.cc
lang/core/ir/transform/combine_access_instructions_test.cc
lang/core/ir/transform/conversion_polyfill_test.cc
+ lang/core/ir/transform/dead_code_elimination_test.cc
lang/core/ir/transform/demote_to_helper_test.cc
lang/core/ir/transform/direct_variable_access_test.cc
lang/core/ir/transform/helper_test.h
@@ -195,6 +198,7 @@
lang/core/ir/transform/builtin_polyfill_fuzz.cc
lang/core/ir/transform/combine_access_instructions_fuzz.cc
lang/core/ir/transform/conversion_polyfill_fuzz.cc
+ lang/core/ir/transform/dead_code_elimination_fuzz.cc
lang/core/ir/transform/demote_to_helper_fuzz.cc
lang/core/ir/transform/direct_variable_access_fuzz.cc
lang/core/ir/transform/multiplanar_external_texture_fuzz.cc
diff --git a/src/tint/lang/core/ir/transform/BUILD.gn b/src/tint/lang/core/ir/transform/BUILD.gn
index 3e4098f..cdf8820 100644
--- a/src/tint/lang/core/ir/transform/BUILD.gn
+++ b/src/tint/lang/core/ir/transform/BUILD.gn
@@ -63,6 +63,8 @@
"combine_access_instructions.h",
"conversion_polyfill.cc",
"conversion_polyfill.h",
+ "dead_code_elimination.cc",
+ "dead_code_elimination.h",
"demote_to_helper.cc",
"demote_to_helper.h",
"direct_variable_access.cc",
@@ -135,6 +137,7 @@
"builtin_polyfill_test.cc",
"combine_access_instructions_test.cc",
"conversion_polyfill_test.cc",
+ "dead_code_elimination_test.cc",
"demote_to_helper_test.cc",
"direct_variable_access_test.cc",
"helper_test.h",
@@ -190,6 +193,7 @@
"builtin_polyfill_fuzz.cc",
"combine_access_instructions_fuzz.cc",
"conversion_polyfill_fuzz.cc",
+ "dead_code_elimination_fuzz.cc",
"demote_to_helper_fuzz.cc",
"direct_variable_access_fuzz.cc",
"multiplanar_external_texture_fuzz.cc",
diff --git a/src/tint/lang/core/ir/transform/dead_code_elimination.cc b/src/tint/lang/core/ir/transform/dead_code_elimination.cc
new file mode 100644
index 0000000..df909d4
--- /dev/null
+++ b/src/tint/lang/core/ir/transform/dead_code_elimination.cc
@@ -0,0 +1,115 @@
+// Copyright 2025 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "src/tint/lang/core/ir/transform/dead_code_elimination.h"
+
+#include <vector>
+
+#include "src/tint/lang/core/ir/builder.h"
+#include "src/tint/lang/core/ir/module.h"
+#include "src/tint/lang/core/ir/validator.h"
+#include "src/tint/utils/ice/ice.h"
+
+using namespace tint::core::fluent_types; // NOLINT
+using namespace tint::core::number_suffixes; // NOLINT
+
+namespace tint::core::ir::transform {
+
+namespace {
+
+/// PIMPL state for the transform.
+struct State {
+ /// The IR module.
+ Module& ir_;
+
+ /// The IR builder.
+ Builder b_{ir_};
+
+ /// Process the module.
+ void Process() {
+ auto funcs = ir_.DependencyOrderedFunctions();
+ for (auto iter = funcs.rbegin(); iter != funcs.rend(); ++iter) {
+ auto func = *iter;
+ if (func->IsEntryPoint()) {
+ continue;
+ }
+
+ bool used = false;
+ for (auto usage : func->UsagesUnsorted()) {
+ if (usage->instruction->Is<core::ir::Call>()) {
+ used = true;
+ break;
+ }
+ }
+
+ if (!used) {
+ ir_.Destroy(func);
+ }
+ }
+
+ // Find any unused vars, do this after removing functions in case the usage was in a
+ // function which is unused.
+ for (auto* inst : *ir_.root_block) {
+ if (inst->Result()->IsUsed()) {
+ continue;
+ }
+
+ // Only want to process `var`s
+ auto* var = inst->As<core::ir::Var>();
+ if (!var) {
+ continue;
+ }
+
+ auto* ptr = var->Result()->Type()->As<core::type::Pointer>();
+ TINT_ASSERT(ptr);
+
+ auto space = ptr->AddressSpace();
+ if (space != core::AddressSpace::kOut && space != core::AddressSpace::kIn &&
+ space != core::AddressSpace::kPrivate) {
+ continue;
+ }
+
+ inst->Destroy();
+ }
+ }
+};
+
+} // namespace
+
+Result<SuccessType> DeadCodeElimination(Module& ir) {
+ auto result =
+ ValidateAndDumpIfNeeded(ir, "core.DeadCodeElimination", kDeadCodeEliminationCapabilities);
+ if (result != Success) {
+ return result;
+ }
+
+ State{ir}.Process();
+
+ return Success;
+}
+
+} // namespace tint::core::ir::transform
diff --git a/src/tint/lang/core/ir/transform/dead_code_elimination.h b/src/tint/lang/core/ir/transform/dead_code_elimination.h
new file mode 100644
index 0000000..fdf7fc9
--- /dev/null
+++ b/src/tint/lang/core/ir/transform/dead_code_elimination.h
@@ -0,0 +1,61 @@
+// Copyright 2025 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef SRC_TINT_LANG_CORE_IR_TRANSFORM_DEAD_CODE_ELIMINATION_H_
+#define SRC_TINT_LANG_CORE_IR_TRANSFORM_DEAD_CODE_ELIMINATION_H_
+
+#include "src/tint/lang/core/ir/validator.h"
+#include "src/tint/utils/result.h"
+
+// Forward declarations.
+namespace tint::core::ir {
+class Module;
+}
+
+namespace tint::core::ir::transform {
+
+/// The capabilities that the transform can support.
+const core::ir::Capabilities kDeadCodeEliminationCapabilities{
+ core::ir::Capability::kAllowOverrides,
+ core::ir::Capability::kAllowVectorElementPointer,
+ core::ir::Capability::kAllowPhonyInstructions,
+ core::ir::Capability::kAllowUnannotatedModuleIOVariables,
+};
+
+/// DeadCodeElimination is a transform that removes dead code from the given IR module.
+///
+/// Currently the eliminator will try to remove:
+/// * Unused functions.
+/// * Unused `private`, `__in` and `__out` module scoped variables.
+///
+/// @param module the module to transform
+/// @returns success or failure
+Result<SuccessType> DeadCodeElimination(Module& module);
+
+} // namespace tint::core::ir::transform
+
+#endif // SRC_TINT_LANG_CORE_IR_TRANSFORM_DEAD_CODE_ELIMINATION_H_
diff --git a/src/tint/lang/core/ir/transform/dead_code_elimination_fuzz.cc b/src/tint/lang/core/ir/transform/dead_code_elimination_fuzz.cc
new file mode 100644
index 0000000..ca0de1b
--- /dev/null
+++ b/src/tint/lang/core/ir/transform/dead_code_elimination_fuzz.cc
@@ -0,0 +1,44 @@
+// Copyright 2025 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "src/tint/lang/core/ir/transform/dead_code_elimination.h"
+
+#include "src/tint/cmd/fuzz/ir/fuzz.h"
+#include "src/tint/lang/core/ir/validator.h"
+
+namespace tint::core::ir::transform {
+namespace {
+
+Result<SuccessType> DeadCodeEliminationFuzzer(Module& ir, const fuzz::ir::Context&) {
+ return DeadCodeElimination(ir);
+}
+
+} // namespace
+} // namespace tint::core::ir::transform
+
+TINT_IR_MODULE_FUZZER(tint::core::ir::transform::DeadCodeEliminationFuzzer,
+ tint::core::ir::transform::kDeadCodeEliminationCapabilities);
diff --git a/src/tint/lang/core/ir/transform/dead_code_elimination_test.cc b/src/tint/lang/core/ir/transform/dead_code_elimination_test.cc
new file mode 100644
index 0000000..f3e02c2
--- /dev/null
+++ b/src/tint/lang/core/ir/transform/dead_code_elimination_test.cc
@@ -0,0 +1,383 @@
+// Copyright 2025 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "src/tint/lang/core/ir/transform/dead_code_elimination.h"
+
+#include <utility>
+
+#include "src/tint/lang/core/ir/transform/helper_test.h"
+
+namespace tint::core::ir::transform {
+namespace {
+
+using namespace tint::core::fluent_types; // NOLINT
+using namespace tint::core::number_suffixes; // NOLINT
+
+using IR_DeadCodeEliminationTest = TransformTest;
+
+TEST_F(IR_DeadCodeEliminationTest, NoModify) {
+ capabilities = Capability::kAllowUnannotatedModuleIOVariables;
+
+ auto* buffer = b.Var("buffer", ty.ptr(core::AddressSpace::kOut, ty.i32()));
+ mod.root_block->Append(buffer);
+
+ auto* used = b.Function("used", ty.f32());
+ b.Append(used->Block(), [&] { b.Return(used, 0.5_f); });
+
+ auto* ep = b.Function("ep", ty.f32(), Function::PipelineStage::kFragment);
+ ep->SetReturnLocation(0_u);
+
+ b.Append(ep->Block(), [&] { //
+ b.Store(buffer, 42_i);
+ b.Return(ep, b.Call(ty.f32(), used));
+ });
+
+ auto* src = R"(
+$B1: { # root
+ %buffer:ptr<__out, i32, read_write> = var undef
+}
+
+%used = func():f32 {
+ $B2: {
+ ret 0.5f
+ }
+}
+%ep = @fragment func():f32 [@location(0)] {
+ $B3: {
+ store %buffer, 42i
+ %4:f32 = call %used
+ ret %4
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = src;
+
+ Run(DeadCodeElimination);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_DeadCodeEliminationTest, RemoveFunction) {
+ capabilities = Capabilities{
+ Capability::kAllowUnannotatedModuleIOVariables,
+ Capability::kAllowPhonyInstructions,
+ };
+
+ auto* buffer = b.Var("buffer", ty.ptr(core::AddressSpace::kIn, ty.i32()));
+ mod.root_block->Append(buffer);
+
+ auto* unused = b.Function("unused", ty.f32());
+ b.Append(unused->Block(), [&] { b.Return(unused, 0.5_f); });
+
+ auto* ep = b.Function("ep", ty.f32(), Function::PipelineStage::kFragment);
+ ep->SetReturnLocation(0_u);
+
+ b.Append(ep->Block(), [&] { //
+ b.Phony(b.Load(buffer));
+ b.Return(ep, 0.5_f);
+ });
+
+ auto* src = R"(
+$B1: { # root
+ %buffer:ptr<__in, i32, read> = var undef
+}
+
+%unused = func():f32 {
+ $B2: {
+ ret 0.5f
+ }
+}
+%ep = @fragment func():f32 [@location(0)] {
+ $B3: {
+ %4:i32 = load %buffer
+ undef = phony %4
+ ret 0.5f
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+$B1: { # root
+ %buffer:ptr<__in, i32, read> = var undef
+}
+
+%ep = @fragment func():f32 [@location(0)] {
+ $B2: {
+ %3:i32 = load %buffer
+ undef = phony %3
+ ret 0.5f
+ }
+}
+)";
+
+ Run(DeadCodeElimination);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_DeadCodeEliminationTest, RemoveFunction_Recursive) {
+ auto* buffer = b.Var("buffer", ty.ptr(core::AddressSpace::kPrivate, ty.i32()));
+ mod.root_block->Append(buffer);
+
+ auto* unused2 = b.Function("unused2", ty.f32());
+ b.Append(unused2->Block(), [&] { b.Return(unused2, 0.5_f); });
+
+ auto* unused = b.Function("unused", ty.f32());
+ b.Append(unused->Block(), [&] { b.Return(unused, b.Call(ty.f32(), unused2)); });
+
+ auto* ep = b.Function("ep", ty.f32(), Function::PipelineStage::kFragment);
+ ep->SetReturnLocation(0_u);
+
+ b.Append(ep->Block(), [&] { //
+ b.Store(buffer, 42_i);
+ b.Return(ep, 0.5_f);
+ });
+
+ auto* src = R"(
+$B1: { # root
+ %buffer:ptr<private, i32, read_write> = var undef
+}
+
+%unused2 = func():f32 {
+ $B2: {
+ ret 0.5f
+ }
+}
+%unused = func():f32 {
+ $B3: {
+ %4:f32 = call %unused2
+ ret %4
+ }
+}
+%ep = @fragment func():f32 [@location(0)] {
+ $B4: {
+ store %buffer, 42i
+ ret 0.5f
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+$B1: { # root
+ %buffer:ptr<private, i32, read_write> = var undef
+}
+
+%ep = @fragment func():f32 [@location(0)] {
+ $B2: {
+ store %buffer, 42i
+ ret 0.5f
+ }
+}
+)";
+
+ Run(DeadCodeElimination);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_DeadCodeEliminationTest, RemoveVarIn_Nested) {
+ auto* buffer = b.Var("buffer", ty.ptr(core::AddressSpace::kIn, ty.i32()));
+ mod.root_block->Append(buffer);
+
+ auto* unused2 = b.Function("unused2", ty.f32());
+ b.Append(unused2->Block(), [&] {
+ b.Phony(b.Load(buffer));
+ b.Return(unused2, 0.5_f);
+ });
+
+ auto* unused = b.Function("unused", ty.f32());
+ b.Append(unused->Block(), [&] { b.Return(unused, b.Call(ty.f32(), unused2)); });
+
+ auto* ep = b.Function("ep", ty.f32(), Function::PipelineStage::kFragment);
+ ep->SetReturnLocation(0_u);
+
+ b.Append(ep->Block(), [&] { //
+ b.Return(ep, 0.5_f);
+ });
+
+ auto* src = R"(
+$B1: { # root
+ %buffer:ptr<__in, i32, read> = var undef
+}
+
+%unused2 = func():f32 {
+ $B2: {
+ %3:i32 = load %buffer
+ undef = phony %3
+ ret 0.5f
+ }
+}
+%unused = func():f32 {
+ $B3: {
+ %5:f32 = call %unused2
+ ret %5
+ }
+}
+%ep = @fragment func():f32 [@location(0)] {
+ $B4: {
+ ret 0.5f
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%ep = @fragment func():f32 [@location(0)] {
+ $B1: {
+ ret 0.5f
+ }
+}
+)";
+
+ Run(DeadCodeElimination);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_DeadCodeEliminationTest, RemoveVarOut_Nested) {
+ auto* buffer =
+ b.Var("buffer", ty.ptr(core::AddressSpace::kOut, ty.i32(), core::Access::kWrite));
+ mod.root_block->Append(buffer);
+
+ auto* unused2 = b.Function("unused2", ty.f32());
+ b.Append(unused2->Block(), [&] {
+ b.Store(buffer, 42_i);
+ b.Return(unused2, 0.5_f);
+ });
+
+ auto* unused = b.Function("unused", ty.f32());
+ b.Append(unused->Block(), [&] { b.Return(unused, b.Call(ty.f32(), unused2)); });
+
+ auto* ep = b.Function("ep", ty.f32(), Function::PipelineStage::kFragment);
+ ep->SetReturnLocation(0_u);
+
+ b.Append(ep->Block(), [&] { //
+ b.Return(ep, 0.5_f);
+ });
+
+ auto* src = R"(
+$B1: { # root
+ %buffer:ptr<__out, i32, write> = var undef
+}
+
+%unused2 = func():f32 {
+ $B2: {
+ store %buffer, 42i
+ ret 0.5f
+ }
+}
+%unused = func():f32 {
+ $B3: {
+ %4:f32 = call %unused2
+ ret %4
+ }
+}
+%ep = @fragment func():f32 [@location(0)] {
+ $B4: {
+ ret 0.5f
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%ep = @fragment func():f32 [@location(0)] {
+ $B1: {
+ ret 0.5f
+ }
+}
+)";
+
+ Run(DeadCodeElimination);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_DeadCodeEliminationTest, RemoveVarPrivate_Nested) {
+ auto* buffer = b.Var("buffer", ty.ptr(core::AddressSpace::kPrivate, ty.i32()));
+ mod.root_block->Append(buffer);
+
+ auto* unused2 = b.Function("unused2", ty.f32());
+ b.Append(unused2->Block(), [&] {
+ b.Store(buffer, 42_i);
+ b.Return(unused2, 0.5_f);
+ });
+
+ auto* unused = b.Function("unused", ty.f32());
+ b.Append(unused->Block(), [&] { b.Return(unused, b.Call(ty.f32(), unused2)); });
+
+ auto* ep = b.Function("ep", ty.f32(), Function::PipelineStage::kFragment);
+ ep->SetReturnLocation(0_u);
+
+ b.Append(ep->Block(), [&] { //
+ b.Return(ep, 0.5_f);
+ });
+
+ auto* src = R"(
+$B1: { # root
+ %buffer:ptr<private, i32, read_write> = var undef
+}
+
+%unused2 = func():f32 {
+ $B2: {
+ store %buffer, 42i
+ ret 0.5f
+ }
+}
+%unused = func():f32 {
+ $B3: {
+ %4:f32 = call %unused2
+ ret %4
+ }
+}
+%ep = @fragment func():f32 [@location(0)] {
+ $B4: {
+ ret 0.5f
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%ep = @fragment func():f32 [@location(0)] {
+ $B1: {
+ ret 0.5f
+ }
+}
+)";
+
+ Run(DeadCodeElimination);
+
+ EXPECT_EQ(expect, str());
+}
+
+} // namespace
+} // namespace tint::core::ir::transform
diff --git a/src/tint/lang/core/ir/validator.cc b/src/tint/lang/core/ir/validator.cc
index 98816df..4248682 100644
--- a/src/tint/lang/core/ir/validator.cc
+++ b/src/tint/lang/core/ir/validator.cc
@@ -2707,7 +2707,8 @@
}
if (var->Block() == mod_.root_block) {
- if (mv->AddressSpace() == AddressSpace::kIn || mv->AddressSpace() == AddressSpace::kOut) {
+ if ((mv->AddressSpace() == AddressSpace::kIn || mv->AddressSpace() == AddressSpace::kOut) &&
+ !capabilities_.Contains(Capability::kAllowUnannotatedModuleIOVariables)) {
auto result = ValidateShaderIOAnnotations(var->Result()->Type(), var->BindingPoint(),
var->Attributes(), "module scope variable");
if (result != Success) {
@@ -2759,8 +2760,10 @@
const core::IOAttributes& attr,
const std::string& target_str) {
EnumSet<IOAnnotation> annotations;
+
// Since there is no entries in the set at this point, this should never fail.
TINT_ASSERT(AddIOAnnotationsFromIOAttributes(annotations, attr) == Success);
+
if (binding_point.has_value()) {
annotations.Add(IOAnnotation::kBindingPoint);
}
diff --git a/src/tint/lang/core/ir/validator.h b/src/tint/lang/core/ir/validator.h
index 99f46da..34ec2cc 100644
--- a/src/tint/lang/core/ir/validator.h
+++ b/src/tint/lang/core/ir/validator.h
@@ -75,6 +75,8 @@
/// Allows binding points to be non-unique. Used after BindingRemapper is
/// invoked by MSL & GLSL backends.
kAllowDuplicateBindings,
+ /// Allows module scope `var`s to exist without an IO annotation
+ kAllowUnannotatedModuleIOVariables,
};
/// Capabilities is a set of Capability
diff --git a/src/tint/lang/spirv/reader/lower/lower.cc b/src/tint/lang/spirv/reader/lower/lower.cc
index 4870fbe..feda9e1 100644
--- a/src/tint/lang/spirv/reader/lower/lower.cc
+++ b/src/tint/lang/spirv/reader/lower/lower.cc
@@ -27,6 +27,7 @@
#include "src/tint/lang/spirv/reader/lower/lower.h"
+#include "src/tint/lang/core/ir/transform/dead_code_elimination.h"
#include "src/tint/lang/core/ir/transform/remove_terminator_args.h"
#include "src/tint/lang/core/ir/validator.h"
#include "src/tint/lang/spirv/reader/lower/atomics.h"
@@ -46,6 +47,7 @@
} \
} while (false)
+ RUN_TRANSFORM(core::ir::transform::DeadCodeElimination, mod);
RUN_TRANSFORM(lower::VectorElementPointer, mod);
RUN_TRANSFORM(lower::ShaderIO, mod);
RUN_TRANSFORM(lower::Builtins, mod);