mirror of
https://github.com/KhronosGroup/SPIRV-Tools
synced 2024-12-26 01:31:06 +00:00
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:
parent
851e1ad985
commit
5d602abd66
@ -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 \
|
||||
|
@ -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_
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
58
source/opt/redundancy_elimination.cpp
Normal file
58
source/opt/redundancy_elimination.cpp
Normal 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
|
54
source/opt/redundancy_elimination.h
Normal file
54
source/opt/redundancy_elimination.h
Normal 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_
|
@ -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.");
|
||||
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
)
|
||||
|
||||
|
276
test/opt/redundancy_elimination_test.cpp
Normal file
276
test/opt/redundancy_elimination_test.cpp
Normal 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
|
@ -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")) {
|
||||
|
Loading…
Reference in New Issue
Block a user