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.
This commit is contained in:
Steven Perron 2017-12-04 12:29:51 -05:00
parent 851e1ad985
commit 5d602abd66
14 changed files with 454 additions and 50 deletions

View File

@ -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 \

View File

@ -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_

View File

@ -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

View File

@ -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<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 @@ Pass::Status LocalRedundancyEliminationPass::Process(ir::IRContext* c) {
}
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 @@ 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<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);

View File

@ -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<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

View File

@ -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<Optimizer::PassToken::Impl>(
MakeUnique<opt::LocalRedundancyEliminationPass>());
}
Optimizer::PassToken CreateRedundancyEliminationPass() {
return MakeUnique<Optimizer::PassToken::Impl>(
MakeUnique<opt::RedundancyEliminationPass>());
}
} // namespace spvtools

View File

@ -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"

View File

@ -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

View File

@ -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_

View File

@ -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.");

View File

@ -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<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));
}

View File

@ -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
)

View File

@ -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

View File

@ -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")) {