// Section 3.32.2 of the SPIR-V spec) of the SPIR-V module to be optimized.
Optimizer::PassToken CreateStripDebugInfoPass();
+// Creates an eliminate-dead-functions pass.
+// An eliminate-dead-functions pass will remove all functions that are not in the
+// call trees rooted at entry points and exported functions. These functions
+// are not needed because they will never be called.
+Optimizer::PassToken CreateEliminateDeadFunctionsPass();
+
// Creates a set-spec-constant-default-value pass from a mapping from spec-ids
// to the default values in the form of string.
// A set-spec-constant-default-value pass sets the default values for the
// this time it does not guarantee all such sequences are eliminated.
//
// Presence of phi instructions can inhibit this optimization. Handling
-// these is left for future improvements.
+// these is left for future improvements.
Optimizer::PassToken CreateBlockMergePass();
// Creates an exhaustive inline pass.
// there is no attempt to optimize for size or runtime performance. Functions
// that are not in the call tree of an entry point are not changed.
Optimizer::PassToken CreateInlineExhaustivePass();
-
+
// Creates an opaque inline pass.
// An opaque inline pass inlines all function calls in all functions in all
// entry point call trees where the called function contains an opaque type
// not legal in Vulkan. Functions that are not in the call tree of an entry
// point are not changed.
Optimizer::PassToken CreateInlineOpaquePass();
-
+
// Creates a single-block local variable load/store elimination pass.
-// For every entry point function, do single block memory optimization of
+// For every entry point function, do single block memory optimization of
// function variables referenced only with non-access-chain loads and stores.
// For each targeted variable load, if previous store to that variable in the
// block, replace the load's result id with the value id of the store.
// The presence of access chain references and function calls can inhibit
// the above optimization.
//
-// Only modules with logical addressing are currently processed.
+// Only modules with logical addressing are currently processed.
//
-// This pass is most effective if preceeded by Inlining and
+// This pass is most effective if preceeded by Inlining and
// LocalAccessChainConvert. This pass will reduce the work needed to be done
// by LocalSingleStoreElim and LocalMultiStoreElim.
//
// Creates an SSA local variable load/store elimination pass.
// For every entry point function, eliminate all loads and stores of function
// scope variables only referenced with non-access-chain loads and stores.
-// Eliminate the variables as well.
+// Eliminate the variables as well.
//
// The presence of access chain references and function calls can inhibit
// the above optimization.
// Currently modules with any extensions enabled are not processed. This
// is left for future work.
//
-// This pass is most effective if preceeded by Inlining and
+// This pass is most effective if preceeded by Inlining and
// LocalAccessChainConvert. LocalSingleStoreElim and LocalSingleBlockElim
// will reduce the work that this pass has to do.
Optimizer::PassToken CreateLocalMultiStoreElimPass();
Optimizer::PassToken CreateAggressiveDCEPass();
// Creates a local single store elimination pass.
-// For each entry point function, this pass eliminates loads and stores for
+// For each entry point function, this pass eliminates loads and stores for
// function scope variable that are stored to only once, where possible. Only
// whole variable loads and stores are eliminated; access-chain references are
// not optimized. Replace all loads of such variables with the value that is
// Creates a pass to consolidate uniform references.
// For each entry point function in the module, first change all constant index
-// access chain loads into equivalent composite extracts. Then consolidate
+// access chain loads into equivalent composite extracts. Then consolidate
// identical uniform loads into one uniform load. Finally, consolidate
// identical uniform extracts into one uniform extract. This may require
// moving a load or extract to a point which dominates all uses.
pass.h
passes.h
pass_manager.h
+ eliminate_dead_functions_pass.h
set_spec_constant_default_value_pass.h
strength_reduction_pass.h
strip_debug_info_pass.h
local_single_store_elim_pass.cpp
local_ssa_elim_pass.cpp
module.cpp
+ eliminate_dead_functions_pass.cpp
set_spec_constant_default_value_pass.cpp
optimizer.cpp
mem_pass.cpp
--- /dev/null
+// Copyright (c) 2017 Google Inc.
+//
+// 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 "eliminate_dead_functions_pass.h"
+
+#include <unordered_set>
+
+namespace spvtools {
+namespace opt {
+
+Pass::Status EliminateDeadFunctionsPass::Process(ir::Module* module) {
+ bool modified = false;
+ module_ = module;
+
+ // Identify live functions first. Those that are not live
+ // are dead.
+ std::unordered_set<const ir::Function*> live_function_set;
+ ProcessFunction mark_live = [&live_function_set](ir::Function* fp) {
+ live_function_set.insert(fp);
+ return false;
+ };
+ ProcessReachableCallTree(mark_live, module);
+
+ def_use_mgr_.reset(new analysis::DefUseManager(consumer(), module));
+ FindNamedOrDecoratedIds();
+ for (auto funcIter = module->begin(); funcIter != module->end();) {
+ if (live_function_set.count(&*funcIter) == 0) {
+ modified = true;
+ EliminateFunction(&*funcIter);
+ funcIter = funcIter.Erase();
+ } else {
+ ++funcIter;
+ }
+ }
+
+ return modified ? Pass::Status::SuccessWithChange
+ : Pass::Status::SuccessWithoutChange;
+}
+
+void EliminateDeadFunctionsPass::EliminateFunction(ir::Function* func) {
+ // Remove all of the instruction in the function body
+ func->ForEachInst(
+ [this](ir::Instruction* inst) {
+ KillNamesAndDecorates(inst);
+ def_use_mgr_->KillInst(inst);
+ },
+ true);
+}
+} // namespace opt
+} // namespace spvtools
--- /dev/null
+// Copyright (c) 2017 Google Inc.
+//
+// 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 LIBSPIRV_OPT_ELIMINATE_DEAD_FUNCTIONS_PASS_H_
+#define LIBSPIRV_OPT_ELIMINATE_DEAD_FUNCTIONS_PASS_H_
+
+#include "def_use_manager.h"
+#include "function.h"
+#include "mem_pass.h"
+#include "module.h"
+
+namespace spvtools {
+namespace opt {
+
+// See optimizer.hpp for documentation.
+class EliminateDeadFunctionsPass : public MemPass {
+ public:
+ const char* name() const override { return "eliminate-dead-functions"; }
+ Status Process(ir::Module*) override;
+
+ private:
+ void EliminateFunction(ir::Function* func);
+};
+
+} // namespace opt
+} // namespace spvtools
+
+#endif // LIBSPIRV_OPT_ELIMINATE_DEAD_FUNCTIONS_PASS_H_
MakeUnique<opt::StripDebugInfoPass>());
}
+
+Optimizer::PassToken CreateEliminateDeadFunctionsPass() {
+ return MakeUnique<Optimizer::PassToken::Impl>(
+ MakeUnique<opt::EliminateDeadFunctionsPass>());
+}
+
+
Optimizer::PassToken CreateSetSpecConstantDefaultValuePass(
const std::unordered_map<uint32_t, std::string>& id_value_map) {
return MakeUnique<Optimizer::PassToken::Impl>(
const uint32_t kEntryPointFunctionIdInIdx = 1;
-} // namespace anonymous
+} // namespace
-void Pass::AddCalls(ir::Function* func,
- std::queue<uint32_t>* todo) {
+void Pass::AddCalls(ir::Function* func, std::queue<uint32_t>* todo) {
for (auto bi = func->begin(); bi != func->end(); ++bi)
for (auto ii = bi->begin(); ii != bi->end(); ++ii)
if (ii->opcode() == SpvOpFunctionCall)
todo->push(ii->GetSingleWordInOperand(0));
}
-bool Pass::ProcessEntryPointCallTree(
- ProcessFunction& pfn, ir::Module* module) {
+bool Pass::ProcessEntryPointCallTree(ProcessFunction& pfn, ir::Module* module) {
// Map from function's result id to function
std::unordered_map<uint32_t, ir::Function*> id2function;
- for (auto& fn : *module)
- id2function[fn.result_id()] = &fn;
+ for (auto& fn : *module) id2function[fn.result_id()] = &fn;
+
+ // Collect all of the entry points as the roots.
+ std::queue<uint32_t> roots;
+ for (auto& e : module->entry_points())
+ roots.push(e.GetSingleWordInOperand(kEntryPointFunctionIdInIdx));
+ return ProcessCallTreeFromRoots(pfn, id2function, &roots);
+}
+
+bool Pass::ProcessReachableCallTree(ProcessFunction& pfn, ir::Module* module) {
+ // Map from function's result id to function
+ std::unordered_map<uint32_t, ir::Function*> id2function;
+ for (auto& fn : *module) id2function[fn.result_id()] = &fn;
+
+ std::queue<uint32_t> roots;
+
+ // Add all entry points since they can be reached from outside the module.
+ for (auto& e : module->entry_points())
+ roots.push(e.GetSingleWordInOperand(kEntryPointFunctionIdInIdx));
+
+ // Add all exported functions since they can be reached from outside the
+ // module.
+ for (auto& a : module->annotations()) {
+ // TODO: Handle group decorations as well. Currently not generate by any
+ // front-end, but could be coming.
+ if (a.opcode() == SpvOp::SpvOpDecorate) {
+ if (a.GetSingleWordOperand(1) ==
+ SpvDecoration::SpvDecorationLinkageAttributes) {
+ uint32_t lastOperand = a.NumOperands() - 1;
+ if (a.GetSingleWordOperand(lastOperand) ==
+ SpvLinkageType::SpvLinkageTypeExport) {
+ uint32_t id = a.GetSingleWordOperand(0);
+ if (id2function.count(id) != 0) roots.push(id);
+ }
+ }
+ }
+ }
+
+ return ProcessCallTreeFromRoots(pfn, id2function, &roots);
+}
+
+bool Pass::ProcessCallTreeFromRoots(
+ ProcessFunction& pfn,
+ const std::unordered_map<uint32_t, ir::Function*>& id2function,
+ std::queue<uint32_t>* roots) {
// Process call tree
bool modified = false;
- std::queue<uint32_t> todo;
std::unordered_set<uint32_t> done;
- for (auto& e : module->entry_points())
- todo.push(e.GetSingleWordInOperand(kEntryPointFunctionIdInIdx));
- while (!todo.empty()) {
- const uint32_t fi = todo.front();
- if (done.find(fi) == done.end()) {
- ir::Function* fn = id2function[fi];
+
+ while (!roots->empty()) {
+ const uint32_t fi = roots->front();
+ roots->pop();
+ if (done.insert(fi).second) {
+ ir::Function* fn = id2function.at(fi);
modified = pfn(fn) || modified;
- done.insert(fi);
- AddCalls(fn, &todo);
+ AddCalls(fn, roots);
}
- todo.pop();
}
return modified;
}
-
} // namespace opt
} // namespace spvtools
// Add to |todo| all ids of functions called in |func|.
void AddCalls(ir::Function* func, std::queue<uint32_t>* todo);
- //
+ // Applies |pfn| to every function in the call trees that are rooted at the
+ // entry points. Returns true if any call |pfn| returns true. By convention
+ // |pfn| should return true if it modified the module.
bool ProcessEntryPointCallTree(ProcessFunction& pfn, ir::Module* module);
+ // Applies |pfn| to every function in the call trees rooted at the entry
+ // points and exported functions. Returns true if any call |pfn| returns
+ // true. By convention |pfn| should return true if it modified the module.
+ bool ProcessReachableCallTree(ProcessFunction& pfn, ir::Module* module);
+
+ // Applies |pfn| to every function in the call trees rooted at the elements of
+ // |roots|. Returns true if any call to |pfn| returns true. By convention
+ // |pfn| should return true if it modified the module. After returning
+ // |roots| will be empty.
+ bool ProcessCallTreeFromRoots(
+ ProcessFunction& pfn,
+ const std::unordered_map<uint32_t, ir::Function*>& id2function,
+ std::queue<uint32_t>* roots);
+
// Processes the given |module|. Returns Status::Failure if errors occur when
// processing. Returns the corresponding Status::Success if processing is
// succesful to indicate whether changes are made to the module.
#include "strength_reduction_pass.h"
#include "strip_debug_info_pass.h"
#include "unify_const_pass.h"
+#include "eliminate_dead_functions_pass.h"
#endif // LIBSPIRV_OPT_PASSES_H_
LIBS SPIRV-Tools-opt
)
+add_spvtools_unittest(TARGET pass_eliminate_dead_functions
+ SRCS eliminate_dead_functions_test.cpp pass_utils.cpp
+ LIBS SPIRV-Tools-opt
+)
+
+add_spvtools_unittest(TARGET pass_pass
+ SRCS pass_test.cpp pass_utils.cpp
+ LIBS SPIRV-Tools-opt
+)
+
add_spvtools_unittest(TARGET pass_utils
SRCS utils_test.cpp pass_utils.cpp
LIBS SPIRV-Tools-opt
--- /dev/null
+// Copyright (c) 2017 Google Inc.
+//
+// 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 <vector>
+
+#include <gmock/gmock.h>
+
+#include "assembly_builder.h"
+#include "pass_fixture.h"
+#include "pass_utils.h"
+
+namespace {
+
+using namespace spvtools;
+using ::testing::HasSubstr;
+
+using EliminateDeadFunctionsBasicTest = PassTest<::testing::Test>;
+
+TEST_F(EliminateDeadFunctionsBasicTest, BasicDeleteDeadFunction) {
+ // The function Dead should be removed because it is never called.
+ const std::vector<const char*> common_code = {
+ // clang-format off
+ "OpCapability Shader",
+ "OpMemoryModel Logical GLSL450",
+ "OpEntryPoint Fragment %main \"main\"",
+ "OpName %main \"main\"",
+ "OpName %Live \"Live\"",
+ "%void = OpTypeVoid",
+ "%7 = OpTypeFunction %void",
+ "%main = OpFunction %void None %7",
+ "%15 = OpLabel",
+ "%16 = OpFunctionCall %void %Live",
+ "%17 = OpFunctionCall %void %Live",
+ "OpReturn",
+ "OpFunctionEnd",
+ "%Live = OpFunction %void None %7",
+ "%20 = OpLabel",
+ "OpReturn",
+ "OpFunctionEnd"
+ // clang-format on
+ };
+
+ const std::vector<const char*> dead_function = {
+ // clang-format off
+ "%Dead = OpFunction %void None %7",
+ "%19 = OpLabel",
+ "OpReturn",
+ "OpFunctionEnd",
+ // clang-format on
+ };
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunAndCheck<opt::EliminateDeadFunctionsPass>(
+ JoinAllInsts(Concat(common_code, dead_function)),
+ JoinAllInsts(common_code), /* skip_nop = */ true);
+}
+
+TEST_F(EliminateDeadFunctionsBasicTest, BasicKeepLiveFunction) {
+ // Everything is reachable from an entry point, so no functions should be
+ // deleted.
+ const std::vector<const char*> text = {
+ // clang-format off
+ "OpCapability Shader",
+ "OpMemoryModel Logical GLSL450",
+ "OpEntryPoint Fragment %main \"main\"",
+ "OpName %main \"main\"",
+ "OpName %Live1 \"Live1\"",
+ "OpName %Live2 \"Live2\"",
+ "%void = OpTypeVoid",
+ "%7 = OpTypeFunction %void",
+ "%main = OpFunction %void None %7",
+ "%15 = OpLabel",
+ "%16 = OpFunctionCall %void %Live2",
+ "%17 = OpFunctionCall %void %Live1",
+ "OpReturn",
+ "OpFunctionEnd",
+ "%Live1 = OpFunction %void None %7",
+ "%19 = OpLabel",
+ "OpReturn",
+ "OpFunctionEnd",
+ "%Live2 = OpFunction %void None %7",
+ "%20 = OpLabel",
+ "OpReturn",
+ "OpFunctionEnd"
+ // clang-format on
+ };
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ std::string assembly = JoinAllInsts(text);
+ auto result = SinglePassRunAndDisassemble<opt::EliminateDeadFunctionsPass>(
+ assembly, /* skip_nop = */ true);
+ EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result));
+ EXPECT_EQ(assembly, std::get<0>(result));
+}
+
+TEST_F(EliminateDeadFunctionsBasicTest, BasicKeepExportFunctions) {
+ // All functions are reachable. In particular, ExportedFunc and Constant are
+ // reachable because ExportedFunc is exported. Nothing should be removed.
+ const std::vector<const char*> text = {
+ // clang-format off
+ "OpCapability Shader",
+ "OpCapability Linkage",
+ "OpMemoryModel Logical GLSL450",
+ "OpEntryPoint Fragment %main \"main\"",
+ "OpName %main \"main\"",
+ "OpName %ExportedFunc \"ExportedFunc\"",
+ "OpName %Live \"Live\"",
+ "OpDecorate %ExportedFunc LinkageAttributes \"ExportedFunc\" Export",
+ "%void = OpTypeVoid",
+ "%7 = OpTypeFunction %void",
+ "%main = OpFunction %void None %7",
+ "%15 = OpLabel",
+ "OpReturn",
+ "OpFunctionEnd",
+"%ExportedFunc = OpFunction %void None %7",
+ "%19 = OpLabel",
+ "%16 = OpFunctionCall %void %Live",
+ "OpReturn",
+ "OpFunctionEnd",
+ "%Live = OpFunction %void None %7",
+ "%20 = OpLabel",
+ "OpReturn",
+ "OpFunctionEnd"
+ // clang-format on
+ };
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ std::string assembly = JoinAllInsts(text);
+ auto result = SinglePassRunAndDisassemble<opt::EliminateDeadFunctionsPass>(
+ assembly, /* skip_nop = */ true);
+ EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result));
+ EXPECT_EQ(assembly, std::get<0>(result));
+}
+
+TEST_F(EliminateDeadFunctionsBasicTest, BasicRemoveDecorationsAndNames) {
+ // We want to remove the names and decorations associated with results that
+ // are removed. This test will check for that.
+ const std::string text = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Vertex %main "main"
+ OpName %main "main"
+ OpName %Dead "Dead"
+ OpName %x "x"
+ OpName %y "y"
+ OpName %z "z"
+ OpDecorate %x RelaxedPrecision
+ OpDecorate %y RelaxedPrecision
+ OpDecorate %z RelaxedPrecision
+ OpDecorate %6 RelaxedPrecision
+ OpDecorate %7 RelaxedPrecision
+ OpDecorate %8 RelaxedPrecision
+ %void = OpTypeVoid
+ %10 = OpTypeFunction %void
+ %float = OpTypeFloat 32
+%_ptr_Function_float = OpTypePointer Function %float
+ %float_1 = OpConstant %float 1
+ %main = OpFunction %void None %10
+ %14 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %Dead = OpFunction %void None %10
+ %15 = OpLabel
+ %x = OpVariable %_ptr_Function_float Function
+ %y = OpVariable %_ptr_Function_float Function
+ %z = OpVariable %_ptr_Function_float Function
+ OpStore %x %float_1
+ OpStore %y %float_1
+ %6 = OpLoad %float %x
+ %7 = OpLoad %float %y
+ %8 = OpFAdd %float %6 %7
+ OpStore %z %8
+ OpReturn
+ OpFunctionEnd)";
+
+ const std::string expected_output = R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main"
+OpName %main "main"
+%void = OpTypeVoid
+%10 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_ptr_Function_float = OpTypePointer Function %float
+%float_1 = OpConstant %float 1
+%main = OpFunction %void None %10
+%14 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunAndCheck<opt::EliminateDeadFunctionsPass>(text, expected_output,
+ /* skip_nop = */ true);
+}
+} // anonymous namespace
--- /dev/null
+// Copyright (c) 2017 Google Inc.
+//
+// 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 <string>
+#include <vector>
+
+#include <gmock/gmock.h>
+
+#include "assembly_builder.h"
+#include "opt/pass.h"
+#include "pass_fixture.h"
+#include "pass_utils.h"
+
+namespace {
+using namespace spvtools;
+class DummyPass : public opt::Pass {
+ public:
+ const char* name() const override { return "dummy-pass"; }
+ Status Process(ir::Module* module) override {
+ return module ? Status::SuccessWithoutChange : Status::Failure;
+ }
+};
+} // namespace
+
+namespace {
+
+using namespace spvtools;
+using ::testing::UnorderedElementsAre;
+
+using PassClassTest = PassTest<::testing::Test>;
+
+TEST_F(PassClassTest, BasicVisitFromEntryPoint) {
+ // Make sure we visit the entry point, and the function it calls.
+ // Do not visit Dead or Exported.
+ const std::string text = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %10 "main"
+ OpName %10 "main"
+ OpName %Dead "Dead"
+ OpName %11 "Constant"
+ OpName %ExportedFunc "ExportedFunc"
+ OpDecorate %ExportedFunc LinkageAttributes "ExportedFunc" Export
+ %void = OpTypeVoid
+ %6 = OpTypeFunction %void
+ %10 = OpFunction %void None %6
+ %14 = OpLabel
+ %15 = OpFunctionCall %void %11
+ %16 = OpFunctionCall %void %11
+ OpReturn
+ OpFunctionEnd
+ %11 = OpFunction %void None %6
+ %18 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %Dead = OpFunction %void None %6
+ %19 = OpLabel
+ OpReturn
+ OpFunctionEnd
+%ExportedFunc = OpFunction %void None %7
+ %20 = OpLabel
+ %21 = OpFunctionCall %void %11
+ OpReturn
+ OpFunctionEnd
+)";
+ // clang-format on
+
+ std::unique_ptr<ir::Module> module =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ DummyPass testPass;
+ std::vector<uint32_t> processed;
+ opt::Pass::ProcessFunction mark_visited = [&processed](ir::Function* fp) {
+ processed.push_back(fp->result_id());
+ return false;
+ };
+ testPass.ProcessEntryPointCallTree(mark_visited, module.get());
+ EXPECT_THAT(processed, UnorderedElementsAre(10, 11));
+}
+
+TEST_F(PassClassTest, BasicVisitReachable) {
+ // Make sure we visit the entry point, exported function, and the function
+ // they call. Do not visit Dead.
+ const std::string text = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %10 "main"
+ OpName %10 "main"
+ OpName %Dead "Dead"
+ OpName %11 "Constant"
+ OpName %12 "ExportedFunc"
+ OpName %13 "Constant2"
+ OpDecorate %12 LinkageAttributes "ExportedFunc" Export
+ %void = OpTypeVoid
+ %6 = OpTypeFunction %void
+ %10 = OpFunction %void None %6
+ %14 = OpLabel
+ %15 = OpFunctionCall %void %11
+ %16 = OpFunctionCall %void %11
+ OpReturn
+ OpFunctionEnd
+ %11 = OpFunction %void None %6
+ %18 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %Dead = OpFunction %void None %6
+ %19 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %12 = OpFunction %void None %9
+ %20 = OpLabel
+ %21 = OpFunctionCall %void %13
+ OpReturn
+ OpFunctionEnd
+ %13 = OpFunction %void None %6
+ %22 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+ // clang-format on
+
+ std::unique_ptr<ir::Module> module =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ DummyPass testPass;
+ std::vector<uint32_t> processed;
+ opt::Pass::ProcessFunction mark_visited = [&processed](ir::Function* fp) {
+ processed.push_back(fp->result_id());
+ return false;
+ };
+ testPass.ProcessReachableCallTree(mark_visited, module.get());
+ EXPECT_THAT(processed, UnorderedElementsAre(10, 11, 12, 13));
+}
+
+TEST_F(PassClassTest, BasicVisitOnlyOnce) {
+ // Make sure we visit %11 only once, even if it is called from two different
+ // functions.
+ const std::string text = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %10 "main" %gl_FragColor
+ OpName %10 "main"
+ OpName %Dead "Dead"
+ OpName %11 "Constant"
+ OpName %12 "ExportedFunc"
+ OpDecorate %12 LinkageAttributes "ExportedFunc" Export
+ %void = OpTypeVoid
+ %6 = OpTypeFunction %void
+ %10 = OpFunction %void None %6
+ %14 = OpLabel
+ %15 = OpFunctionCall %void %11
+ %16 = OpFunctionCall %void %12
+ OpReturn
+ OpFunctionEnd
+ %11 = OpFunction %void None %6
+ %18 = OpLabel
+ %19 = OpFunctionCall %void %12
+ OpReturn
+ OpFunctionEnd
+ %Dead = OpFunction %void None %6
+ %20 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %12 = OpFunction %void None %9
+ %21 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+ // clang-format on
+
+ std::unique_ptr<ir::Module> module =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ DummyPass testPass;
+ std::vector<uint32_t> processed;
+ opt::Pass::ProcessFunction mark_visited = [&processed](ir::Function* fp) {
+ processed.push_back(fp->result_id());
+ return false;
+ };
+ testPass.ProcessReachableCallTree(mark_visited, module.get());
+ EXPECT_THAT(processed, UnorderedElementsAre(10, 11, 12));
+}
+
+TEST_F(PassClassTest, BasicDontVisitExportedVariable) {
+ // Make sure we only visit functions and not exported variables.
+ const std::string text = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %10 "main" %gl_FragColor
+ OpExecutionMode %10 OriginUpperLeft
+ OpSource GLSL 150
+ OpName %10 "main"
+ OpName %Dead "Dead"
+ OpName %11 "Constant"
+ OpName %12 "export_var"
+ OpDecorate %12 LinkageAttributes "export_var" Export
+ %void = OpTypeVoid
+ %6 = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %float_1 = OpConstant %float 1
+ %12 = OpVariable %float Output
+ %10 = OpFunction %void None %6
+ %14 = OpLabel
+ OpStore %12 %float_1
+ OpReturn
+ OpFunctionEnd
+)";
+ // clang-format on
+
+ std::unique_ptr<ir::Module> module =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ DummyPass testPass;
+ std::vector<uint32_t> processed;
+ opt::Pass::ProcessFunction mark_visited = [&processed](ir::Function* fp) {
+ processed.push_back(fp->result_id());
+ return false;
+ };
+ testPass.ProcessReachableCallTree(mark_visited, module.get());
+ EXPECT_THAT(processed, UnorderedElementsAre(10));
+}
+} // namespace
Convert conditional branches with constant condition to the
indicated unconditional brranch. Delete all resulting dead
code. Performed only on entry point call tree functions.
+ --eliminate-dead-functions
+ Deletes functions that cannot be reached from entry points or
+ exported functions.
--merge-blocks
Join two blocks into a single block if the second has the
first as its only predecessor. Performed only on entry point
optimizer.RegisterPass(CreateBlockMergePass());
} else if (0 == strcmp(cur_arg, "--eliminate-dead-branches")) {
optimizer.RegisterPass(CreateDeadBranchElimPass());
+ } else if (0 == strcmp(cur_arg, "--eliminate-dead-functions")) {
+ optimizer.RegisterPass(CreateEliminateDeadFunctionsPass());
} else if (0 == strcmp(cur_arg, "--eliminate-local-multi-store")) {
optimizer.RegisterPass(CreateLocalMultiStoreElimPass());
} else if (0 == strcmp(cur_arg, "--eliminate-common-uniform")) {