Skip to content

Commit

Permalink
spirv-opt: A pass to removed unused input on OpEntryPoint instruction…
Browse files Browse the repository at this point in the history
…s. (#4275)

The new pass will removed interface variable on the OpEntryPoint instruction when they are not statically referenced in the call tree of the entry point.

It can be enabled on the command line using the options `remove-unused-interface-variables`.
  • Loading branch information
thatname committed Jun 29, 2021
1 parent 8442a18 commit f9893c4
Show file tree
Hide file tree
Showing 11 changed files with 331 additions and 1 deletion.
1 change: 1 addition & 0 deletions Android.mk
Expand Up @@ -153,6 +153,7 @@ SPVTOOLS_OPT_SRC_FILES := \
source/opt/register_pressure.cpp \
source/opt/relax_float_ops_pass.cpp \
source/opt/remove_duplicates_pass.cpp \
source/opt/remove_unused_interface_variables_pass.cpp \
source/opt/replace_invalid_opc.cpp \
source/opt/scalar_analysis.cpp \
source/opt/scalar_analysis_simplification.cpp \
Expand Down
7 changes: 7 additions & 0 deletions include/spirv-tools/optimizer.hpp
Expand Up @@ -514,6 +514,13 @@ Optimizer::PassToken CreateDeadInsertElimPass();
// eliminated with standard dead code elimination.
Optimizer::PassToken CreateAggressiveDCEPass();

// Creates a remove-unused-interface-variables pass.
// Removes variables referenced on the |OpEntryPoint| instruction that are not
// referenced in the entry point function or any function in its call tree. Note
// that this could cause the shader interface to no longer match other shader
// stages.
Optimizer::PassToken CreateRemoveUnusedInterfaceVariablesPass();

// Creates an empty pass.
// This is deprecated and will be removed.
// TODO(jaebaek): remove this pass after handling glslang's broken unit tests.
Expand Down
2 changes: 2 additions & 0 deletions source/opt/CMakeLists.txt
Expand Up @@ -96,6 +96,7 @@ set(SPIRV_TOOLS_OPT_SOURCES
register_pressure.h
relax_float_ops_pass.h
remove_duplicates_pass.h
remove_unused_interface_variables_pass.h
replace_invalid_opc.h
scalar_analysis.h
scalar_analysis_nodes.h
Expand Down Expand Up @@ -197,6 +198,7 @@ set(SPIRV_TOOLS_OPT_SOURCES
register_pressure.cpp
relax_float_ops_pass.cpp
remove_duplicates_pass.cpp
remove_unused_interface_variables_pass.cpp
replace_invalid_opc.cpp
scalar_analysis.cpp
scalar_analysis_simplification.cpp
Expand Down
7 changes: 7 additions & 0 deletions source/opt/optimizer.cpp
Expand Up @@ -489,6 +489,8 @@ bool Optimizer::RegisterPassFromFlag(const std::string& flag) {
RegisterSizePasses();
} else if (pass_name == "legalize-hlsl") {
RegisterLegalizationPasses();
} else if (pass_name == "remove-unused-interface-variables") {
RegisterPass(CreateRemoveUnusedInterfaceVariablesPass());
} else if (pass_name == "graphics-robust-access") {
RegisterPass(CreateGraphicsRobustAccessPass());
} else if (pass_name == "wrap-opkill") {
Expand Down Expand Up @@ -729,6 +731,11 @@ Optimizer::PassToken CreateAggressiveDCEPass() {
MakeUnique<opt::AggressiveDCEPass>());
}

Optimizer::PassToken CreateRemoveUnusedInterfaceVariablesPass() {
return MakeUnique<Optimizer::PassToken::Impl>(
MakeUnique<opt::RemoveUnusedInterfaceVariablesPass>());
}

Optimizer::PassToken CreatePropagateLineInfoPass() {
return MakeUnique<Optimizer::PassToken::Impl>(MakeUnique<opt::EmptyPass>());
}
Expand Down
1 change: 1 addition & 0 deletions source/opt/passes.h
Expand Up @@ -64,6 +64,7 @@
#include "source/opt/redundancy_elimination.h"
#include "source/opt/relax_float_ops_pass.h"
#include "source/opt/remove_duplicates_pass.h"
#include "source/opt/remove_unused_interface_variables_pass.h"
#include "source/opt/replace_invalid_opc.h"
#include "source/opt/scalar_replacement_pass.h"
#include "source/opt/set_spec_constant_default_value_pass.h"
Expand Down
93 changes: 93 additions & 0 deletions source/opt/remove_unused_interface_variables_pass.cpp
@@ -0,0 +1,93 @@
// Copyright (c) 2021 ZHOU He
//
// 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 "remove_unused_interface_variables_pass.h"
#include "source/spirv_constant.h"
namespace spvtools {
namespace opt {

class RemoveUnusedInterfaceVariablesContext {
RemoveUnusedInterfaceVariablesPass& parent_;
Instruction& entry_;
std::unordered_set<uint32_t> used_variables_;
IRContext::ProcessFunction pfn_ =
std::bind(&RemoveUnusedInterfaceVariablesContext::processFunction, this,
std::placeholders::_1);

bool processFunction(Function* func) {
for (const auto& basic_block : *func)
for (const auto& instruction : basic_block)
instruction.ForEachInId([&](const uint32_t* id) {
if (used_variables_.count(*id)) return;
auto* var = parent_.get_def_use_mgr()->GetDef(*id);
if (!var || var->opcode() != SpvOpVariable) return;
auto storage_class = var->GetSingleWordInOperand(0);
if (storage_class != SpvStorageClassFunction &&
(parent_.get_module()->version() >=
SPV_SPIRV_VERSION_WORD(1, 4) ||
storage_class == SpvStorageClassInput ||
storage_class == SpvStorageClassOutput))
used_variables_.insert(*id);
});
return false;
}

public:
RemoveUnusedInterfaceVariablesContext(
RemoveUnusedInterfaceVariablesPass& parent, Instruction& entry)
: parent_(parent), entry_(entry) {}

void CollectUsedVariables() {
std::queue<uint32_t> roots;
roots.push(entry_.GetSingleWordInOperand(1));
parent_.context()->ProcessCallTreeFromRoots(pfn_, &roots);
}

bool ShouldModify() {
std::unordered_set<uint32_t> old_variables;
for (int i = entry_.NumInOperands() - 1; i >= 3; --i) {
auto variable = entry_.GetInOperand(i).words[0];
if (!used_variables_.count(variable)) return true; // It is unused.
if (old_variables.count(variable)) return true; // It is duplicate.
old_variables.insert(variable);
}
if (old_variables.size() != used_variables_.size()) // Missing IDs.
return true;
return false;
}

void Modify() {
for (int i = entry_.NumInOperands() - 1; i >= 3; --i)
entry_.RemoveInOperand(i);
for (auto id : used_variables_) {
entry_.AddOperand(Operand(SPV_OPERAND_TYPE_ID, {id}));
}
}
};

RemoveUnusedInterfaceVariablesPass::Status
RemoveUnusedInterfaceVariablesPass::Process() {
bool modified = false;
for (auto& entry : get_module()->entry_points()) {
RemoveUnusedInterfaceVariablesContext context(*this, entry);
context.CollectUsedVariables();
if (context.ShouldModify()) {
context.Modify();
modified = true;
}
}
return (modified ? Status::SuccessWithChange : Status::SuccessWithoutChange);
}
} // namespace opt
} // namespace spvtools
26 changes: 26 additions & 0 deletions source/opt/remove_unused_interface_variables_pass.h
@@ -0,0 +1,26 @@
// Copyright (c) 2021 ZHOU He
//
// 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 "source/opt/pass.h"
namespace spvtools {
namespace opt {

class RemoveUnusedInterfaceVariablesPass : public Pass {
const char* name() const override {
return "remove-unused-interface-variables-pass";
}
Status Process() override;
};
} // namespace opt
} // namespace spvtools
1 change: 1 addition & 0 deletions test/opt/CMakeLists.txt
Expand Up @@ -79,6 +79,7 @@ add_spvtools_unittest(TARGET opt
propagator_test.cpp
reduce_load_size_test.cpp
redundancy_elimination_test.cpp
remove_unused_interface_variables_test.cpp
register_liveness.cpp
relax_float_ops_test.cpp
replace_invalid_opc_test.cpp
Expand Down
185 changes: 185 additions & 0 deletions test/opt/remove_unused_interface_variables_test.cpp
@@ -0,0 +1,185 @@
// Copyright (c) 2021 ZHOU He
//
// 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 "gmock/gmock.h"
#include "test/opt/assembly_builder.h"
#include "test/opt/pass_fixture.h"
#include "test/opt/pass_utils.h"

namespace spvtools {
namespace opt {
namespace {

using RemoveUnusedInterfaceVariablesTest = PassTest<::testing::Test>;

static const std::string expected = R"(OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %_Z5func1v "_Z5func1v" %out_var_SV_TARGET
OpEntryPoint Fragment %_Z5func2v "_Z5func2v" %out_var_SV_TARGET_0
OpExecutionMode %_Z5func1v OriginUpperLeft
OpExecutionMode %_Z5func2v OriginUpperLeft
OpSource HLSL 630
OpName %type_cba "type.cba"
OpMemberName %type_cba 0 "color"
OpName %cba "cba"
OpName %out_var_SV_TARGET "out.var.SV_TARGET"
OpName %out_var_SV_TARGET_0 "out.var.SV_TARGET"
OpName %_Z5func1v "_Z5func1v"
OpName %_Z5func2v "_Z5func2v"
OpDecorate %out_var_SV_TARGET Location 0
OpDecorate %out_var_SV_TARGET_0 Location 0
OpDecorate %cba DescriptorSet 0
OpDecorate %cba Binding 0
OpMemberDecorate %type_cba 0 Offset 0
OpDecorate %type_cba Block
%int = OpTypeInt 32 1
%int_0 = OpConstant %int 0
%float = OpTypeFloat 32
%v4float = OpTypeVector %float 4
%type_cba = OpTypeStruct %v4float
%_ptr_Uniform_type_cba = OpTypePointer Uniform %type_cba
%_ptr_Output_v4float = OpTypePointer Output %v4float
%void = OpTypeVoid
%14 = OpTypeFunction %void
%_ptr_Uniform_v4float = OpTypePointer Uniform %v4float
%cba = OpVariable %_ptr_Uniform_type_cba Uniform
%out_var_SV_TARGET = OpVariable %_ptr_Output_v4float Output
%out_var_SV_TARGET_0 = OpVariable %_ptr_Output_v4float Output
%_Z5func1v = OpFunction %void None %14
%16 = OpLabel
%17 = OpAccessChain %_ptr_Uniform_v4float %cba %int_0
%18 = OpLoad %v4float %17
OpStore %out_var_SV_TARGET %18
OpReturn
OpFunctionEnd
%_Z5func2v = OpFunction %void None %14
%19 = OpLabel
%20 = OpAccessChain %_ptr_Uniform_v4float %cba %int_0
%21 = OpLoad %v4float %20
OpStore %out_var_SV_TARGET_0 %21
OpReturn
OpFunctionEnd
)";

TEST_F(RemoveUnusedInterfaceVariablesTest, RemoveUnusedVariable) {
const std::string text = R"(OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %_Z5func1v "_Z5func1v" %out_var_SV_TARGET %out_var_SV_TARGET_0
OpEntryPoint Fragment %_Z5func2v "_Z5func2v" %out_var_SV_TARGET %out_var_SV_TARGET_0
OpExecutionMode %_Z5func1v OriginUpperLeft
OpExecutionMode %_Z5func2v OriginUpperLeft
OpSource HLSL 630
OpName %type_cba "type.cba"
OpMemberName %type_cba 0 "color"
OpName %cba "cba"
OpName %out_var_SV_TARGET "out.var.SV_TARGET"
OpName %out_var_SV_TARGET_0 "out.var.SV_TARGET"
OpName %_Z5func1v "_Z5func1v"
OpName %_Z5func2v "_Z5func2v"
OpDecorate %out_var_SV_TARGET Location 0
OpDecorate %out_var_SV_TARGET_0 Location 0
OpDecorate %cba DescriptorSet 0
OpDecorate %cba Binding 0
OpMemberDecorate %type_cba 0 Offset 0
OpDecorate %type_cba Block
%int = OpTypeInt 32 1
%int_0 = OpConstant %int 0
%float = OpTypeFloat 32
%v4float = OpTypeVector %float 4
%type_cba = OpTypeStruct %v4float
%_ptr_Uniform_type_cba = OpTypePointer Uniform %type_cba
%_ptr_Output_v4float = OpTypePointer Output %v4float
%void = OpTypeVoid
%14 = OpTypeFunction %void
%_ptr_Uniform_v4float = OpTypePointer Uniform %v4float
%cba = OpVariable %_ptr_Uniform_type_cba Uniform
%out_var_SV_TARGET = OpVariable %_ptr_Output_v4float Output
%out_var_SV_TARGET_0 = OpVariable %_ptr_Output_v4float Output
%_Z5func1v = OpFunction %void None %14
%16 = OpLabel
%17 = OpAccessChain %_ptr_Uniform_v4float %cba %int_0
%18 = OpLoad %v4float %17
OpStore %out_var_SV_TARGET %18
OpReturn
OpFunctionEnd
%_Z5func2v = OpFunction %void None %14
%19 = OpLabel
%20 = OpAccessChain %_ptr_Uniform_v4float %cba %int_0
%21 = OpLoad %v4float %20
OpStore %out_var_SV_TARGET_0 %21
OpReturn
OpFunctionEnd
)";

SinglePassRunAndCheck<RemoveUnusedInterfaceVariablesPass>(text, expected,
true, true);
}

TEST_F(RemoveUnusedInterfaceVariablesTest, FixMissingVariable) {
const std::string text = R"(OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %_Z5func1v "_Z5func1v"
OpEntryPoint Fragment %_Z5func2v "_Z5func2v"
OpExecutionMode %_Z5func1v OriginUpperLeft
OpExecutionMode %_Z5func2v OriginUpperLeft
OpSource HLSL 630
OpName %type_cba "type.cba"
OpMemberName %type_cba 0 "color"
OpName %cba "cba"
OpName %out_var_SV_TARGET "out.var.SV_TARGET"
OpName %out_var_SV_TARGET_0 "out.var.SV_TARGET"
OpName %_Z5func1v "_Z5func1v"
OpName %_Z5func2v "_Z5func2v"
OpDecorate %out_var_SV_TARGET Location 0
OpDecorate %out_var_SV_TARGET_0 Location 0
OpDecorate %cba DescriptorSet 0
OpDecorate %cba Binding 0
OpMemberDecorate %type_cba 0 Offset 0
OpDecorate %type_cba Block
%int = OpTypeInt 32 1
%int_0 = OpConstant %int 0
%float = OpTypeFloat 32
%v4float = OpTypeVector %float 4
%type_cba = OpTypeStruct %v4float
%_ptr_Uniform_type_cba = OpTypePointer Uniform %type_cba
%_ptr_Output_v4float = OpTypePointer Output %v4float
%void = OpTypeVoid
%14 = OpTypeFunction %void
%_ptr_Uniform_v4float = OpTypePointer Uniform %v4float
%cba = OpVariable %_ptr_Uniform_type_cba Uniform
%out_var_SV_TARGET = OpVariable %_ptr_Output_v4float Output
%out_var_SV_TARGET_0 = OpVariable %_ptr_Output_v4float Output
%_Z5func1v = OpFunction %void None %14
%16 = OpLabel
%17 = OpAccessChain %_ptr_Uniform_v4float %cba %int_0
%18 = OpLoad %v4float %17
OpStore %out_var_SV_TARGET %18
OpReturn
OpFunctionEnd
%_Z5func2v = OpFunction %void None %14
%19 = OpLabel
%20 = OpAccessChain %_ptr_Uniform_v4float %cba %int_0
%21 = OpLoad %v4float %20
OpStore %out_var_SV_TARGET_0 %21
OpReturn
OpFunctionEnd
)";

SinglePassRunAndCheck<RemoveUnusedInterfaceVariablesPass>(text, expected,
true, true);
}

} // namespace
} // namespace opt
} // namespace spvtools
6 changes: 6 additions & 0 deletions tools/opt/opt.cpp
Expand Up @@ -406,6 +406,12 @@ Options (in lexicographical order):)",
Removes duplicate types, decorations, capabilities and extension
instructions.)");
printf(R"(
--remove-unused-interface-variables
Removes variables referenced on the |OpEntryPoint| instruction
that are not referenced in the entry point function or any function
in its call tree. Note that this could cause the shader interface
to no longer match other shader stages.)");
printf(R"(
--replace-invalid-opcode
Replaces instructions whose opcode is valid for shader modules,
but not for the current shader stage. To have an effect, all
Expand Down

0 comments on commit f9893c4

Please sign in to comment.