From 5d602abd66c879382803dbd428729a9799906d86 Mon Sep 17 00:00:00 2001 From: Steven Perron Date: Mon, 4 Dec 2017 12:29:51 -0500 Subject: [PATCH] Add global redundancy elimination Adds a pass that looks for redundant instruction in a function, and removes them. The algorithm is a hash table based value numbering algorithm that traverses the dominator tree. This pass removes completely redundant instructions, not partially redundant ones. --- Android.mk | 1 + include/spirv-tools/optimizer.hpp | 5 + source/opt/CMakeLists.txt | 2 + source/opt/local_redundancy_elimination.cpp | 42 +-- source/opt/local_redundancy_elimination.h | 33 ++- source/opt/optimizer.cpp | 9 +- source/opt/passes.h | 1 + source/opt/redundancy_elimination.cpp | 58 ++++ source/opt/redundancy_elimination.h | 54 ++++ source/opt/value_number_table.cpp | 3 +- source/opt/value_number_table.h | 9 +- test/opt/CMakeLists.txt | 6 + test/opt/redundancy_elimination_test.cpp | 276 ++++++++++++++++++++ tools/opt/opt.cpp | 5 + 14 files changed, 454 insertions(+), 50 deletions(-) create mode 100644 source/opt/redundancy_elimination.cpp create mode 100644 source/opt/redundancy_elimination.h create mode 100644 test/opt/redundancy_elimination_test.cpp diff --git a/Android.mk b/Android.mk index 29ea52d3a..1bcd1b12a 100644 --- a/Android.mk +++ b/Android.mk @@ -90,6 +90,7 @@ SPVTOOLS_OPT_SRC_FILES := \ source/opt/pass.cpp \ source/opt/pass_manager.cpp \ source/opt/propagator.cpp \ + source/opt/redundancy_elimination.cpp \ source/opt/remove_duplicates_pass.cpp \ source/opt/set_spec_constant_default_value_pass.cpp \ source/opt/strength_reduction_pass.cpp \ diff --git a/include/spirv-tools/optimizer.hpp b/include/spirv-tools/optimizer.hpp index 805132c59..eea50a8c7 100644 --- a/include/spirv-tools/optimizer.hpp +++ b/include/spirv-tools/optimizer.hpp @@ -432,6 +432,11 @@ Optimizer::PassToken CreateMergeReturnPass(); // This pass will look for instructions in the same basic block that compute the // same value, and remove the redundant ones. Optimizer::PassToken CreateLocalRedundancyEliminationPass(); + +// Create global value numbering pass. +// This pass will look for instructions where the same value is computed on all +// paths leading to the instruction. Those instructions are deleted. +Optimizer::PassToken CreateRedundancyEliminationPass(); } // namespace spvtools #endif // SPIRV_TOOLS_OPTIMIZER_HPP_ diff --git a/source/opt/CMakeLists.txt b/source/opt/CMakeLists.txt index 278adaede..4a9f437f3 100644 --- a/source/opt/CMakeLists.txt +++ b/source/opt/CMakeLists.txt @@ -55,6 +55,7 @@ add_library(SPIRV-Tools-opt pass.h pass_manager.h propagator.h + redundancy_elimination.h reflect.h remove_duplicates_pass.h set_spec_constant_default_value_pass.h @@ -106,6 +107,7 @@ add_library(SPIRV-Tools-opt pass.cpp pass_manager.cpp propagator.cpp + redundancy_elimination.cpp remove_duplicates_pass.cpp set_spec_constant_default_value_pass.cpp strength_reduction_pass.cpp diff --git a/source/opt/local_redundancy_elimination.cpp b/source/opt/local_redundancy_elimination.cpp index 45ba012c9..01848833e 100644 --- a/source/opt/local_redundancy_elimination.cpp +++ b/source/opt/local_redundancy_elimination.cpp @@ -30,8 +30,8 @@ Pass::Status LocalRedundancyEliminationPass::Process(ir::IRContext* c) { // Keeps track of all ids that contain a given value number. We keep // track of multiple values because they could have the same value, but // different decorations. - std::vector> value_to_ids; - if (EliminateRedundanciesInBB(&bb, &vnTable, &value_to_ids)) + std::map value_to_ids; + if (EliminateRedundanciesInBB(&bb, vnTable, &value_to_ids)) modified = true; } } @@ -39,8 +39,8 @@ Pass::Status LocalRedundancyEliminationPass::Process(ir::IRContext* c) { } bool LocalRedundancyEliminationPass::EliminateRedundanciesInBB( - ir::BasicBlock* block, ValueNumberTable* vnTable, - std::vector>* value_to_ids) { + ir::BasicBlock* block, const ValueNumberTable& vnTable, + std::map* value_to_ids) { bool modified = false; auto func = [this, vnTable, &modified, value_to_ids](ir::Instruction* inst) { @@ -48,38 +48,18 @@ bool LocalRedundancyEliminationPass::EliminateRedundanciesInBB( return; } - uint32_t value = vnTable->GetValueNumber(inst); + uint32_t value = vnTable.GetValueNumber(inst); if (value == 0) { return; } - if (value >= value_to_ids->size()) { - value_to_ids->resize(value + 1); - } - - // Now that we have the value number of the instruction, we use - // |value_to_ids| to get other ids that contain the same value. If we can - // find an id in that set which has the same decorations, we can replace all - // uses of the result of |inst| by that id. - std::vector& candidate_set = (*value_to_ids)[value]; - bool found_replacement = false; - for (uint32_t candidate_id : candidate_set) { - if (get_decoration_mgr()->HaveTheSameDecorations(inst->result_id(), - candidate_id)) { - context()->KillNamesAndDecorates(inst); - context()->ReplaceAllUsesWith(inst->result_id(), candidate_id); - context()->KillInst(inst); - modified = true; - found_replacement = true; - break; - } - } - - // If we did not find a replacement, then add it as a candidate for later - // instructions. - if (!found_replacement) { - candidate_set.push_back(inst->result_id()); + auto candidate = value_to_ids->insert({value, inst->result_id()}); + if (!candidate.second) { + context()->KillNamesAndDecorates(inst); + context()->ReplaceAllUsesWith(inst->result_id(), candidate.first->second); + context()->KillInst(inst); + modified = true; } }; block->ForEachInst(func); diff --git a/source/opt/local_redundancy_elimination.h b/source/opt/local_redundancy_elimination.h index e714c171b..b599e6631 100644 --- a/source/opt/local_redundancy_elimination.h +++ b/source/opt/local_redundancy_elimination.h @@ -33,18 +33,29 @@ class LocalRedundancyEliminationPass : public Pass { public: const char* name() const override { return "local-redundancy-elimination"; } Status Process(ir::IRContext*) override; + virtual ir::IRContext::Analysis GetPreservedAnalyses() override { + return ir::IRContext::kAnalysisDefUse | + ir::IRContext::kAnalysisInstrToBlockMapping | + ir::IRContext::kAnalysisDecorations | + ir::IRContext::kAnalysisCombinators | ir::IRContext::kAnalysisCFG | + ir::IRContext::kAnalysisDominatorAnalysis; + } - private: - // Deletes instructions in |block| whose value is in |vnTable| or is computed - // earlier in |block|. The values computed in |block| are added to |vnTable|. - // |value_to_ids| is a map from a value number to the result ids known to - // contain those values. The definition of the ids in value_to_ids must - // dominate |block|. One value needs to map to multiple ids because the ids - // may contain the same value, but have different decorations. Returns true - // if the module is changed. - bool EliminateRedundanciesInBB( - ir::BasicBlock* block, ValueNumberTable* vnTable, - std::vector>* value_to_ids); + protected: + // Deletes instructions in |block| whose value is in |value_to_ids| or is + // computed earlier in |block|. + // + // |vnTable| must have computed a value number for every result id defined + // in |bb|. + // + // |value_to_ids| is a map from value number to ids. If {vn, id} is in + // |value_to_ids| then vn is the value number of id, and the definition of id + // dominates |bb|. + // + // Returns true if the module is changed. + bool EliminateRedundanciesInBB(ir::BasicBlock* block, + const ValueNumberTable& vnTable, + std::map* value_to_ids); }; } // namespace opt diff --git a/source/opt/optimizer.cpp b/source/opt/optimizer.cpp index 5013e349a..2c9fb3493 100644 --- a/source/opt/optimizer.cpp +++ b/source/opt/optimizer.cpp @@ -94,7 +94,7 @@ Optimizer& Optimizer::RegisterPerformancePasses() { .RegisterPass(CreateBlockMergePass()) .RegisterPass(CreateLocalMultiStoreElimPass()) .RegisterPass(CreateInsertExtractElimPass()) - .RegisterPass(CreateLocalRedundancyEliminationPass()) + .RegisterPass(CreateRedundancyEliminationPass()) // Currently exposing driver bugs resulting in crashes (#946) // .RegisterPass(CreateCommonUniformElimPass()) .RegisterPass(CreateDeadVariableEliminationPass()); @@ -113,7 +113,7 @@ Optimizer& Optimizer::RegisterSizePasses() { .RegisterPass(CreateBlockMergePass()) .RegisterPass(CreateLocalMultiStoreElimPass()) .RegisterPass(CreateInsertExtractElimPass()) - .RegisterPass(CreateLocalRedundancyEliminationPass()) + .RegisterPass(CreateRedundancyEliminationPass()) // Currently exposing driver bugs resulting in crashes (#946) // .RegisterPass(CreateCommonUniformElimPass()) .RegisterPass(CreateDeadVariableEliminationPass()); @@ -282,4 +282,9 @@ Optimizer::PassToken CreateLocalRedundancyEliminationPass() { return MakeUnique( MakeUnique()); } + +Optimizer::PassToken CreateRedundancyEliminationPass() { + return MakeUnique( + MakeUnique()); +} } // namespace spvtools diff --git a/source/opt/passes.h b/source/opt/passes.h index da5f910c1..9c1c580bf 100644 --- a/source/opt/passes.h +++ b/source/opt/passes.h @@ -39,6 +39,7 @@ #include "local_ssa_elim_pass.h" #include "merge_return_pass.h" #include "null_pass.h" +#include "redundancy_elimination.h" #include "set_spec_constant_default_value_pass.h" #include "strength_reduction_pass.h" #include "strip_debug_info_pass.h" diff --git a/source/opt/redundancy_elimination.cpp b/source/opt/redundancy_elimination.cpp new file mode 100644 index 000000000..7e5c01d5f --- /dev/null +++ b/source/opt/redundancy_elimination.cpp @@ -0,0 +1,58 @@ +// 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 "redundancy_elimination.h" + +#include "value_number_table.h" + +namespace spvtools { +namespace opt { + +Pass::Status RedundancyEliminationPass::Process(ir::IRContext* c) { + InitializeProcessing(c); + + bool modified = false; + ValueNumberTable vnTable(context()); + + for (auto& func : *get_module()) { + // Build the dominator tree for this function. It is how the code is + // traversed. + opt::DominatorTree& dom_tree = + context()->GetDominatorAnalysis(&func, *context()->cfg())->GetDomTree(); + + // Keeps track of all ids that contain a given value number. We keep + // track of multiple values because they could have the same value, but + // different decorations. + std::map value_to_ids; + + if (EliminateRedundanciesFrom(dom_tree.GetRoot(), vnTable, value_to_ids)) { + modified = true; + } + } + return (modified ? Status::SuccessWithChange : Status::SuccessWithoutChange); +} + +bool RedundancyEliminationPass::EliminateRedundanciesFrom( + DominatorTreeNode* bb, const ValueNumberTable& vnTable, + std::map value_to_ids) { + bool modified = EliminateRedundanciesInBB(bb->bb_, vnTable, &value_to_ids); + + for (auto dominated_bb : bb->children_) { + modified |= EliminateRedundanciesFrom(dominated_bb, vnTable, value_to_ids); + } + + return modified; +} +} // namespace opt +} // namespace spvtools diff --git a/source/opt/redundancy_elimination.h b/source/opt/redundancy_elimination.h new file mode 100644 index 000000000..634ecc330 --- /dev/null +++ b/source/opt/redundancy_elimination.h @@ -0,0 +1,54 @@ +// 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_REDUNDANCY_ELIMINATION_H_ +#define LIBSPIRV_OPT_REDUNDANCY_ELIMINATION_H_ + +#include "ir_context.h" +#include "local_redundancy_elimination.h" +#include "pass.h" +#include "value_number_table.h" + +namespace spvtools { +namespace opt { + +// This pass implements total redundancy elimination. This is the same as +// local redundancy elimination except it looks across basic block boundaries. +// An instruction, inst, is totally redundant if there is another instruction +// that dominates inst, and also computes the same value. +class RedundancyEliminationPass : public LocalRedundancyEliminationPass { + public: + const char* name() const override { return "redundancy-elimination"; } + Status Process(ir::IRContext*) override; + + protected: + // Removes for all total redundancies in the function starting at |bb|. + // + // |vnTable| must have computed a value number for every result id defined + // in the function containing |bb|. + // + // |value_to_ids| is a map from value number to ids. If {vn, id} is in + // |value_to_ids| then vn is the value number of id, and the defintion of id + // dominates |bb|. + // + // Returns true if at least one instruction is deleted. + bool EliminateRedundanciesFrom(DominatorTreeNode* bb, + const ValueNumberTable& vnTable, + std::map value_to_ids); +}; + +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_OPT_REDUNDANCY_ELIMINATION_H_ diff --git a/source/opt/value_number_table.cpp b/source/opt/value_number_table.cpp index d0d287606..7f5b7ce47 100644 --- a/source/opt/value_number_table.cpp +++ b/source/opt/value_number_table.cpp @@ -21,7 +21,8 @@ namespace spvtools { namespace opt { -uint32_t ValueNumberTable::GetValueNumber(spvtools::ir::Instruction* inst) { +uint32_t ValueNumberTable::GetValueNumber( + spvtools::ir::Instruction* inst) const { assert(inst->result_id() != 0 && "inst must have a result id to get a value number."); diff --git a/source/opt/value_number_table.h b/source/opt/value_number_table.h index d7651bae0..8ad20df34 100644 --- a/source/opt/value_number_table.h +++ b/source/opt/value_number_table.h @@ -56,13 +56,13 @@ class ValueNumberTable { // Returns the value number of the value computed by |inst|. |inst| must have // a result id that will hold the computed value. If no value number has been // assigned to the result id, then the return value is 0. - uint32_t GetValueNumber(spvtools::ir::Instruction* inst); + uint32_t GetValueNumber(spvtools::ir::Instruction* inst) const; // Returns the value number of the value contain in |id|. Returns 0 if it // has not been assigned a value number. - inline uint32_t GetValueNumber(uint32_t id); + inline uint32_t GetValueNumber(uint32_t id) const; - ir::IRContext* context() { return context_; } + ir::IRContext* context() const { return context_; } private: // Assigns a value number to every result id in the module. @@ -82,10 +82,9 @@ class ValueNumberTable { std::unordered_map id_to_value_; ir::IRContext* context_; uint32_t next_value_number_; - }; -uint32_t ValueNumberTable::GetValueNumber(uint32_t id) { +uint32_t ValueNumberTable::GetValueNumber(uint32_t id) const { return GetValueNumber(context()->get_def_use_mgr()->GetDef(id)); } diff --git a/test/opt/CMakeLists.txt b/test/opt/CMakeLists.txt index 36af22316..3273e85ea 100644 --- a/test/opt/CMakeLists.txt +++ b/test/opt/CMakeLists.txt @@ -225,3 +225,9 @@ add_spvtools_unittest(TARGET propagator SRCS propagator_test.cpp LIBS SPIRV-Tools-opt ) + +add_spvtools_unittest(TARGET redundancy_elimination + SRCS redundancy_elimination_test.cpp pass_utils.cpp + LIBS SPIRV-Tools-opt +) + diff --git a/test/opt/redundancy_elimination_test.cpp b/test/opt/redundancy_elimination_test.cpp new file mode 100644 index 000000000..a2573135e --- /dev/null +++ b/test/opt/redundancy_elimination_test.cpp @@ -0,0 +1,276 @@ +// 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 "opt/value_number_table.h" + +#include "assembly_builder.h" +#include "gmock/gmock.h" +#include "opt/build_module.h" +#include "pass_fixture.h" +#include "pass_utils.h" + +namespace { + +using namespace spvtools; + +using ::testing::HasSubstr; +using ::testing::MatchesRegex; + +using RedundancyEliminationTest = PassTest<::testing::Test>; + +#ifdef SPIRV_EFFCEE +// Test that it can get a simple case of local redundancy elimination. +// The rest of the test check for extra functionality. +TEST_F(RedundancyEliminationTest, RemoveRedundantLocalAdd) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource GLSL 430 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeFloat 32 + %6 = OpTypePointer Function %5 + %2 = OpFunction %3 None %4 + %7 = OpLabel + %8 = OpVariable %6 Function + %9 = OpLoad %5 %8 + %10 = OpFAdd %5 %9 %9 +; CHECK: OpFAdd +; CHECK-NOT: OpFAdd + %11 = OpFAdd %5 %9 %9 + OpReturn + OpFunctionEnd + )"; + SinglePassRunAndMatch(text); +} + +// Remove a redundant add across basic blocks. +TEST_F(RedundancyEliminationTest, RemoveRedundantAdd) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource GLSL 430 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeFloat 32 + %6 = OpTypePointer Function %5 + %2 = OpFunction %3 None %4 + %7 = OpLabel + %8 = OpVariable %6 Function + %9 = OpLoad %5 %8 + %10 = OpFAdd %5 %9 %9 + OpBranch %11 + %11 = OpLabel +; CHECK: OpFAdd +; CHECK-NOT: OpFAdd + %12 = OpFAdd %5 %9 %9 + OpReturn + OpFunctionEnd + )"; + SinglePassRunAndMatch(text); +} + +// Remove a redundant add going through a multiple basic blocks. +TEST_F(RedundancyEliminationTest, RemoveRedundantAddDiamond) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource GLSL 430 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeFloat 32 + %6 = OpTypePointer Function %5 + %7 = OpTypeBool + %8 = OpConstantTrue %7 + %2 = OpFunction %3 None %4 + %9 = OpLabel + %10 = OpVariable %6 Function + %11 = OpLoad %5 %10 + %12 = OpFAdd %5 %11 %11 +; CHECK: OpFAdd +; CHECK-NOT: OpFAdd + OpBranchConditional %8 %13 %14 + %13 = OpLabel + OpBranch %15 + %14 = OpLabel + OpBranch %15 + %15 = OpLabel + %16 = OpFAdd %5 %11 %11 + OpReturn + OpFunctionEnd + + )"; + SinglePassRunAndMatch(text); +} + +// Remove a redundant add in a side node. +TEST_F(RedundancyEliminationTest, RemoveRedundantAddInSideNode) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource GLSL 430 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeFloat 32 + %6 = OpTypePointer Function %5 + %7 = OpTypeBool + %8 = OpConstantTrue %7 + %2 = OpFunction %3 None %4 + %9 = OpLabel + %10 = OpVariable %6 Function + %11 = OpLoad %5 %10 + %12 = OpFAdd %5 %11 %11 +; CHECK: OpFAdd +; CHECK-NOT: OpFAdd + OpBranchConditional %8 %13 %14 + %13 = OpLabel + OpBranch %15 + %14 = OpLabel + %16 = OpFAdd %5 %11 %11 + OpBranch %15 + %15 = OpLabel + OpReturn + OpFunctionEnd + + )"; + SinglePassRunAndMatch(text); +} + +// Remove a redundant add whose value is in the result of a phi node. +TEST_F(RedundancyEliminationTest, RemoveRedundantAddWithPhi) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource GLSL 430 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeFloat 32 + %6 = OpTypePointer Function %5 + %7 = OpTypeBool + %8 = OpConstantTrue %7 + %2 = OpFunction %3 None %4 + %9 = OpLabel + %10 = OpVariable %6 Function + %11 = OpLoad %5 %10 + OpBranchConditional %8 %13 %14 + %13 = OpLabel + %add1 = OpFAdd %5 %11 %11 +; CHECK: OpFAdd + OpBranch %15 + %14 = OpLabel + %add2 = OpFAdd %5 %11 %11 +; CHECK: OpFAdd + OpBranch %15 + %15 = OpLabel +; CHECK: OpPhi + %phi = OpPhi %5 %add1 %13 %add2 %14 +; CHECK-NOT: OpFAdd + %16 = OpFAdd %5 %11 %11 + OpReturn + OpFunctionEnd + + )"; + SinglePassRunAndMatch(text); +} + +// Keep the add because it is redundant on some paths, but not all paths. +TEST_F(RedundancyEliminationTest, KeepPartiallyRedundantAdd) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource GLSL 430 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeFloat 32 + %6 = OpTypePointer Function %5 + %7 = OpTypeBool + %8 = OpConstantTrue %7 + %2 = OpFunction %3 None %4 + %9 = OpLabel + %10 = OpVariable %6 Function + %11 = OpLoad %5 %10 + OpBranchConditional %8 %13 %14 + %13 = OpLabel + %add = OpFAdd %5 %11 %11 + OpBranch %15 + %14 = OpLabel + OpBranch %15 + %15 = OpLabel + %16 = OpFAdd %5 %11 %11 + OpReturn + OpFunctionEnd + + )"; + auto result = SinglePassRunAndDisassemble( + text, /* skip_nop = */ true); + EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result)); +} + +// Keep the add. Even if it is redundant on all paths, there is no single id +// whose definition dominates the add and contains the same value. +TEST_F(RedundancyEliminationTest, KeepRedundantAddWithoutPhi) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource GLSL 430 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeFloat 32 + %6 = OpTypePointer Function %5 + %7 = OpTypeBool + %8 = OpConstantTrue %7 + %2 = OpFunction %3 None %4 + %9 = OpLabel + %10 = OpVariable %6 Function + %11 = OpLoad %5 %10 + OpBranchConditional %8 %13 %14 + %13 = OpLabel + %add1 = OpFAdd %5 %11 %11 + OpBranch %15 + %14 = OpLabel + %add2 = OpFAdd %5 %11 %11 + OpBranch %15 + %15 = OpLabel + %16 = OpFAdd %5 %11 %11 + OpReturn + OpFunctionEnd + + )"; + auto result = SinglePassRunAndDisassemble( + text, /* skip_nop = */ true); + EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result)); +} +#endif +} // anonymous namespace diff --git a/tools/opt/opt.cpp b/tools/opt/opt.cpp index 972f15356..bd7203440 100644 --- a/tools/opt/opt.cpp +++ b/tools/opt/opt.cpp @@ -197,6 +197,9 @@ Options (in lexicographical order): 'spirv-opt --merge-blocks -O ...' applies the transformation --merge-blocks followed by all the transformations implied by -O. + --redundancy-elimination + Looks for instructions in the same function that compute the + same value, and deletes the redundant ones. --relax-store-struct Allow store from one struct type to a different type with compatible layout and members. This option is forwarded to the @@ -389,6 +392,8 @@ OptStatus ParseFlags(int argc, const char** argv, Optimizer* optimizer, optimizer->RegisterPass(CreateCFGCleanupPass()); } else if (0 == strcmp(cur_arg, "--local-redundancy-elimination")) { optimizer->RegisterPass(CreateLocalRedundancyEliminationPass()); + } else if (0 == strcmp(cur_arg, "--redundancy-elimination")) { + optimizer->RegisterPass(CreateRedundancyEliminationPass()); } else if (0 == strcmp(cur_arg, "--relax-store-struct")) { options->relax_struct_store = true; } else if (0 == strcmp(cur_arg, "-O")) {