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.
diff --git a/Android.mk b/Android.mk
index 29ea52d..1bcd1b1 100644
--- a/Android.mk
+++ b/Android.mk
@@ -90,6 +90,7 @@
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 805132c..eea50a8 100644
--- a/include/spirv-tools/optimizer.hpp
+++ b/include/spirv-tools/optimizer.hpp
@@ -432,6 +432,11 @@
// 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 278adae..4a9f437 100644
--- a/source/opt/CMakeLists.txt
+++ b/source/opt/CMakeLists.txt
@@ -55,6 +55,7 @@
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 @@
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 45ba012..0184883 100644
--- a/source/opt/local_redundancy_elimination.cpp
+++ b/source/opt/local_redundancy_elimination.cpp
@@ -30,8 +30,8 @@
// 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<std::vector<uint32_t>> value_to_ids;
- if (EliminateRedundanciesInBB(&bb, &vnTable, &value_to_ids))
+ std::map<uint32_t, uint32_t> value_to_ids;
+ if (EliminateRedundanciesInBB(&bb, vnTable, &value_to_ids))
modified = true;
}
}
@@ -39,8 +39,8 @@
}
bool LocalRedundancyEliminationPass::EliminateRedundanciesInBB(
- ir::BasicBlock* block, ValueNumberTable* vnTable,
- std::vector<std::vector<uint32_t>>* value_to_ids) {
+ ir::BasicBlock* block, const ValueNumberTable& vnTable,
+ std::map<uint32_t, uint32_t>* value_to_ids) {
bool modified = false;
auto func = [this, vnTable, &modified, value_to_ids](ir::Instruction* inst) {
@@ -48,38 +48,18 @@
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<uint32_t>& 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 e714c17..b599e66 100644
--- a/source/opt/local_redundancy_elimination.h
+++ b/source/opt/local_redundancy_elimination.h
@@ -33,18 +33,29 @@
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<std::vector<uint32_t>>* 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<uint32_t, uint32_t>* value_to_ids);
};
} // namespace opt
diff --git a/source/opt/optimizer.cpp b/source/opt/optimizer.cpp
index 5013e34..2c9fb34 100644
--- a/source/opt/optimizer.cpp
+++ b/source/opt/optimizer.cpp
@@ -94,7 +94,7 @@
.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 @@
.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 @@
return MakeUnique<Optimizer::PassToken::Impl>(
MakeUnique<opt::LocalRedundancyEliminationPass>());
}
+
+Optimizer::PassToken CreateRedundancyEliminationPass() {
+ return MakeUnique<Optimizer::PassToken::Impl>(
+ MakeUnique<opt::RedundancyEliminationPass>());
+}
} // namespace spvtools
diff --git a/source/opt/passes.h b/source/opt/passes.h
index da5f910..9c1c580 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 0000000..7e5c01d
--- /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<uint32_t, uint32_t> 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<uint32_t, uint32_t> 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 0000000..634ecc3
--- /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<uint32_t, uint32_t> 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 d0d2876..7f5b7ce 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 d7651ba..8ad20df 100644
--- a/source/opt/value_number_table.h
+++ b/source/opt/value_number_table.h
@@ -56,13 +56,13 @@
// 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 @@
std::unordered_map<uint32_t, uint32_t> 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 36af223..3273e85 100644
--- a/test/opt/CMakeLists.txt
+++ b/test/opt/CMakeLists.txt
@@ -225,3 +225,9 @@
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 0000000..a257313
--- /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<opt::RedundancyEliminationPass>(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<opt::RedundancyEliminationPass>(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<opt::RedundancyEliminationPass>(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<opt::RedundancyEliminationPass>(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<opt::RedundancyEliminationPass>(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<opt::RedundancyEliminationPass>(
+ 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<opt::RedundancyEliminationPass>(
+ 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 972f153..bd72034 100644
--- a/tools/opt/opt.cpp
+++ b/tools/opt/opt.cpp
@@ -197,6 +197,9 @@
'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 @@
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")) {