CFG cleanup pass - Remove unreachable blocks.

- Adds a new pass CFGCleanupPass.  This serves as an umbrella pass to
  remove unnecessary cruft from a CFG.
- Currently, the only cleanup operation done is the removal of
  unreachable basic blocks.
- Adds unit tests.
- Adds a flag to spirvopt to execute the pass (--cfg-cleanup).
This commit is contained in:
Diego Novillo 2017-09-06 08:56:41 -04:00 committed by David Neto
parent f17326c9bd
commit c75704ec08
13 changed files with 507 additions and 1 deletions

View File

@ -50,6 +50,7 @@ SPVTOOLS_OPT_SRC_FILES := \
source/opt/basic_block.cpp \
source/opt/block_merge_pass.cpp \
source/opt/build_module.cpp \
source/opt/cfg_cleanup_pass.cpp \
source/opt/compact_ids_pass.cpp \
source/opt/common_uniform_elim_pass.cpp \
source/opt/dead_branch_elim_pass.cpp \

View File

@ -393,6 +393,14 @@ Optimizer::PassToken CreateCompactIdsPass();
// Creates a remove duplicate capabilities pass.
Optimizer::PassToken CreateRemoveDuplicatesPass();
// Creates a CFG cleanup pass.
// This pass removes cruft from the control flow graph of functions that are
// reachable from entry points and exported functions. It currently includes the
// following functionality:
//
// - Removal of unreachable basic blocks.
Optimizer::PassToken CreateCFGCleanupPass();
} // namespace spvtools
#endif // SPIRV_TOOLS_OPTIMIZER_HPP_

View File

@ -16,6 +16,7 @@ add_library(SPIRV-Tools-opt
basic_block.h
block_merge_pass.h
build_module.h
cfg_cleanup_pass.h
common_uniform_elim_pass.h
compact_ids_pass.h
constants.h
@ -59,6 +60,7 @@ add_library(SPIRV-Tools-opt
basic_block.cpp
block_merge_pass.cpp
build_module.cpp
cfg_cleanup_pass.cpp
common_uniform_elim_pass.cpp
compact_ids_pass.cpp
dead_branch_elim_pass.cpp

View File

@ -0,0 +1,170 @@
// 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.
// This file implements a pass to cleanup the CFG to remove superfluous
// constructs (e.g., unreachable basic blocks, empty control flow structures,
// etc)
#include <queue>
#include <unordered_set>
#include "cfg_cleanup_pass.h"
#include "function.h"
#include "module.h"
namespace spvtools {
namespace opt {
void CFGCleanupPass::RemoveFromReachedPhiOperands(const ir::BasicBlock& block,
ir::Instruction* inst) {
uint32_t inst_id = inst->result_id();
if (inst_id == 0) {
return;
}
analysis::UseList* uses = def_use_mgr_->GetUses(inst_id);
if (uses == nullptr) {
return;
}
for (auto u : *uses) {
if (u.inst->opcode() != SpvOpPhi) {
continue;
}
ir::Instruction* phi_inst = u.inst;
std::vector<ir::Operand> keep_operands;
for (uint32_t i = 0; i < phi_inst->NumOperands(); i++) {
const ir::Operand& var_op = phi_inst->GetOperand(i);
if (i >= 2 && i < phi_inst->NumOperands() - 1) {
// PHI arguments start at index 2. Each argument consists of two
// operands: the variable id and the originating block id.
const ir::Operand& block_op = phi_inst->GetOperand(i + 1);
assert(var_op.words.size() == 1 && block_op.words.size() == 1 &&
"Phi operands should have exactly one word in them.");
uint32_t var_id = var_op.words.front();
uint32_t block_id = block_op.words.front();
if (var_id == inst_id && block_id == block.id()) {
i++;
continue;
}
}
keep_operands.push_back(var_op);
}
phi_inst->ReplaceOperands(keep_operands);
}
}
void CFGCleanupPass::RemoveBlock(ir::Function::iterator* bi) {
auto& block = **bi;
block.ForEachInst([&block, this](ir::Instruction* inst) {
// Note that we do not kill the block label instruction here. The label
// instruction is needed to identify the block, which is needed by the
// removal of PHI operands.
if (inst != block.GetLabelInst()) {
RemoveFromReachedPhiOperands(block, inst);
KillNamesAndDecorates(inst);
def_use_mgr_->KillInst(inst);
}
});
// Remove the label instruction last.
def_use_mgr_->KillInst(block.GetLabelInst());
*bi = bi->Erase();
}
bool CFGCleanupPass::RemoveUnreachableBlocks(ir::Function* func) {
bool modified = false;
// Mark reachable all blocks reachable from the function's entry block.
std::unordered_set<ir::BasicBlock*> reachable_blocks;
std::unordered_set<ir::BasicBlock*> visited_blocks;
std::queue<ir::BasicBlock*> worklist;
reachable_blocks.insert(func->entry().get());
// Initially mark the function entry point as reachable.
worklist.push(func->entry().get());
auto mark_reachable = [&reachable_blocks, &visited_blocks, &worklist,
this](uint32_t label_id) {
auto successor = label2block_[label_id];
if (visited_blocks.count(successor) == 0) {
reachable_blocks.insert(successor);
worklist.push(successor);
visited_blocks.insert(successor);
}
};
// Transitively mark all blocks reachable from the entry as reachable.
while (!worklist.empty()) {
ir::BasicBlock* block = worklist.front();
worklist.pop();
// All the successors of a live block are also live.
block->ForEachSuccessorLabel(mark_reachable);
// All the Merge and ContinueTarget blocks of a live block are also live.
block->ForMergeAndContinueLabel(mark_reachable);
}
// Erase unreachable blocks.
for (auto ebi = func->begin(); ebi != func->end();) {
if (reachable_blocks.count(&*ebi) == 0) {
RemoveBlock(&ebi);
modified = true;
} else {
++ebi;
}
}
return modified;
}
bool CFGCleanupPass::CFGCleanup(ir::Function* func) {
bool modified = false;
modified |= RemoveUnreachableBlocks(func);
return modified;
}
void CFGCleanupPass::Initialize(ir::Module* module) {
// Initialize the DefUse manager.
module_ = module;
def_use_mgr_.reset(new analysis::DefUseManager(consumer(), module));
FindNamedOrDecoratedIds();
// Initialize block lookup map.
label2block_.clear();
for (auto& fn : *module) {
for (auto& block : fn) {
label2block_[block.id()] = &block;
}
}
}
Pass::Status CFGCleanupPass::Process(ir::Module* module) {
Initialize(module);
// Process all entry point functions.
ProcessFunction pfn = [this](ir::Function* fp) { return CFGCleanup(fp); };
bool modified = ProcessReachableCallTree(pfn, module);
return modified ? Pass::Status::SuccessWithChange
: Pass::Status::SuccessWithoutChange;
}
} // namespace opt
} // namespace spvtools

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.
#ifndef LIBSPIRV_OPT_CFG_CLEANUP_PASS_H_
#define LIBSPIRV_OPT_CFG_CLEANUP_PASS_H_
#include "function.h"
#include "module.h"
#include "mem_pass.h"
namespace spvtools {
namespace opt {
class CFGCleanupPass : public MemPass {
public:
CFGCleanupPass() = default;
const char* name() const override { return "cfg-cleanup"; }
Status Process(ir::Module*) override;
private:
// Call all the cleanup helper functions on |func|.
bool CFGCleanup(ir::Function* func);
// Remove all the unreachable basic blocks in |func|.
bool RemoveUnreachableBlocks(ir::Function* func);
// Remove the block pointed by the iterator |*bi|. This also removes
// all the instructions in the pointed-to block.
void RemoveBlock(ir::Function::iterator *bi);
// Initialize the pass.
void Initialize(ir::Module* module);
// Remove the result from |inst| from every Phi instruction reached by
// |inst|. The instruction is coming from basic block |block|.
void RemoveFromReachedPhiOperands(const ir::BasicBlock& block,
ir::Instruction* inst);
// Map from block's label id to block. TODO(dnovillo): Basic blocks ought to
// have basic blocks in their pred/succ list.
std::unordered_map<uint32_t, ir::BasicBlock*> label2block_;
};
} // namespace opt
} // namespace spvtools
#endif

View File

@ -70,6 +70,9 @@ class Function {
// Returns function's return type id
inline uint32_t type_id() const { return def_inst_->type_id(); }
// Returns the entry basic block for this function.
const std::unique_ptr<BasicBlock>& entry() const { return blocks_.front(); }
iterator begin() { return iterator(&blocks_, blocks_.begin()); }
iterator end() { return iterator(&blocks_, blocks_.end()); }
const_iterator cbegin() const {

View File

@ -89,5 +89,11 @@ void Instruction::ToBinaryWithoutAttachedDebugInsts(
binary->insert(binary->end(), operand.words.begin(), operand.words.end());
}
void Instruction::ReplaceOperands(const std::vector<Operand>& new_operands) {
operands_.clear();
operands_.insert(operands_.begin(), new_operands.begin(), new_operands.end());
operands_.shrink_to_fit();
}
} // namespace ir
} // namespace spvtools

View File

@ -202,8 +202,13 @@ class Instruction {
// Pushes the binary segments for this instruction into the back of *|binary|.
void ToBinaryWithoutAttachedDebugInsts(std::vector<uint32_t>* binary) const;
// Replaces the operands to the instruction with |new_operands|. The caller
// is responsible for building a complete and valid list of operands for
// this instruction.
void ReplaceOperands(const std::vector<Operand>& new_operands);
private:
// Returns the toal count of result type id and result id.
// Returns the total count of result type id and result id.
uint32_t TypeResultIdCount() const {
return (type_id_ != 0) + (result_id_ != 0);
}

View File

@ -230,6 +230,11 @@ Optimizer::PassToken CreateCompactIdsPass() {
MakeUnique<opt::CompactIdsPass>());
}
Optimizer::PassToken CreateCFGCleanupPass() {
return MakeUnique<Optimizer::PassToken::Impl>(
MakeUnique<opt::CFGCleanupPass>());
}
std::vector<const char*> Optimizer::GetPassNames() const {
std::vector<const char*> v;
for (uint32_t i = 0; i < impl_->pass_manager.NumPasses(); i++) {

View File

@ -18,6 +18,7 @@
// A single header to include all passes.
#include "block_merge_pass.h"
#include "cfg_cleanup_pass.h"
#include "common_uniform_elim_pass.h"
#include "compact_ids_pass.h"
#include "dead_branch_elim_pass.h"

View File

@ -183,3 +183,8 @@ add_spvtools_unittest(TARGET pass_strength_reduction
SRCS strength_reduction_test.cpp pass_utils.cpp
LIBS SPIRV-Tools-opt
)
add_spvtools_unittest(TARGET cfg_cleanup
SRCS cfg_cleanup_test.cpp pass_utils.cpp
LIBS SPIRV-Tools-opt
)

View File

@ -0,0 +1,236 @@
// 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 "pass_fixture.h"
#include "pass_utils.h"
namespace {
using namespace spvtools;
using CFGCleanupTest = PassTest<::testing::Test>;
TEST_F(CFGCleanupTest, RemoveUnreachableBlocks) {
const std::string declarations = R"(OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main" %inf %outf4
OpExecutionMode %main OriginUpperLeft
OpSource GLSL 450
OpName %main "main"
OpName %inf "inf"
OpName %outf4 "outf4"
OpDecorate %inf Location 0
OpDecorate %outf4 Location 0
%void = OpTypeVoid
%6 = OpTypeFunction %void
%float = OpTypeFloat 32
%_ptr_Input_float = OpTypePointer Input %float
%inf = OpVariable %_ptr_Input_float Input
%float_2 = OpConstant %float 2
%bool = OpTypeBool
%v4float = OpTypeVector %float 4
%_ptr_Output_v4float = OpTypePointer Output %v4float
%outf4 = OpVariable %_ptr_Output_v4float Output
%float_n0_5 = OpConstant %float -0.5
)";
const std::string body_before = R"(%main = OpFunction %void None %6
%14 = OpLabel
OpSelectionMerge %17 None
OpBranch %18
%19 = OpLabel
%20 = OpLoad %float %inf
%21 = OpCompositeConstruct %v4float %20 %20 %20 %20
OpStore %outf4 %21
OpBranch %17
%18 = OpLabel
%22 = OpLoad %float %inf
%23 = OpFAdd %float %22 %float_n0_5
%24 = OpCompositeConstruct %v4float %23 %23 %23 %23
OpStore %outf4 %24
OpBranch %17
%17 = OpLabel
OpReturn
OpFunctionEnd
)";
const std::string body_after = R"(%main = OpFunction %void None %6
%14 = OpLabel
OpSelectionMerge %15 None
OpBranch %16
%16 = OpLabel
%20 = OpLoad %float %inf
%21 = OpFAdd %float %20 %float_n0_5
%22 = OpCompositeConstruct %v4float %21 %21 %21 %21
OpStore %outf4 %22
OpBranch %15
%15 = OpLabel
OpReturn
OpFunctionEnd
)";
SinglePassRunAndCheck<opt::CFGCleanupPass>(
declarations + body_before, declarations + body_after, true, true);
}
TEST_F(CFGCleanupTest, RemoveDecorations) {
const std::string before = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpName %main "main"
OpName %x "x"
OpName %dead "dead"
OpDecorate %x RelaxedPrecision
OpDecorate %dead RelaxedPrecision
%void = OpTypeVoid
%6 = OpTypeFunction %void
%float = OpTypeFloat 32
%_ptr_Function_float = OpTypePointer Function %float
%float_2 = OpConstant %float 2
%float_4 = OpConstant %float 4
%main = OpFunction %void None %6
%14 = OpLabel
%x = OpVariable %_ptr_Function_float Function
OpSelectionMerge %17 None
OpBranch %18
%19 = OpLabel
%dead = OpVariable %_ptr_Function_float Function
OpStore %dead %float_2
OpBranch %17
%18 = OpLabel
OpStore %x %float_4
OpBranch %17
%17 = OpLabel
OpReturn
OpFunctionEnd
)";
const std::string after = R"(OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpName %main "main"
OpName %x "x"
OpDecorate %x RelaxedPrecision
%void = OpTypeVoid
%6 = OpTypeFunction %void
%float = OpTypeFloat 32
%_ptr_Function_float = OpTypePointer Function %float
%float_2 = OpConstant %float 2
%float_4 = OpConstant %float 4
%main = OpFunction %void None %6
%11 = OpLabel
%x = OpVariable %_ptr_Function_float Function
OpSelectionMerge %12 None
OpBranch %13
%13 = OpLabel
OpStore %x %float_4
OpBranch %12
%12 = OpLabel
OpReturn
OpFunctionEnd
)";
SinglePassRunAndCheck<opt::CFGCleanupPass>(before, after, true, true);
}
TEST_F(CFGCleanupTest, UpdatePhis) {
const std::string before = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main" %y %outparm
OpName %main "main"
OpName %y "y"
OpName %outparm "outparm"
OpDecorate %y Flat
OpDecorate %y Location 0
OpDecorate %outparm Location 0
%void = OpTypeVoid
%3 = OpTypeFunction %void
%int = OpTypeInt 32 1
%_ptr_Function_int = OpTypePointer Function %int
%_ptr_Input_int = OpTypePointer Input %int
%y = OpVariable %_ptr_Input_int Input
%int_10 = OpConstant %int 10
%bool = OpTypeBool
%int_42 = OpConstant %int 42
%int_23 = OpConstant %int 23
%int_5 = OpConstant %int 5
%_ptr_Output_int = OpTypePointer Output %int
%outparm = OpVariable %_ptr_Output_int Output
%main = OpFunction %void None %3
%5 = OpLabel
%11 = OpLoad %int %y
OpBranch %21
%16 = OpLabel
%20 = OpIAdd %int %11 %int_42
OpBranch %17
%21 = OpLabel
%24 = OpISub %int %11 %int_23
OpBranch %17
%17 = OpLabel
%31 = OpPhi %int %20 %16 %24 %21
%27 = OpIAdd %int %31 %int_5
OpStore %outparm %27
OpReturn
OpFunctionEnd
)";
const std::string after = R"(OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main" %y %outparm
OpName %main "main"
OpName %y "y"
OpName %outparm "outparm"
OpDecorate %y Flat
OpDecorate %y Location 0
OpDecorate %outparm Location 0
%void = OpTypeVoid
%6 = OpTypeFunction %void
%int = OpTypeInt 32 1
%_ptr_Function_int = OpTypePointer Function %int
%_ptr_Input_int = OpTypePointer Input %int
%y = OpVariable %_ptr_Input_int Input
%int_10 = OpConstant %int 10
%bool = OpTypeBool
%int_42 = OpConstant %int 42
%int_23 = OpConstant %int 23
%int_5 = OpConstant %int 5
%_ptr_Output_int = OpTypePointer Output %int
%outparm = OpVariable %_ptr_Output_int Output
%main = OpFunction %void None %6
%16 = OpLabel
%17 = OpLoad %int %y
OpBranch %18
%18 = OpLabel
%22 = OpISub %int %17 %int_23
OpBranch %21
%21 = OpLabel
%23 = OpPhi %int %22 %18
%24 = OpIAdd %int %23 %int_5
OpStore %outparm %24
OpReturn
OpFunctionEnd
)";
SinglePassRunAndCheck<opt::CFGCleanupPass>(before, after, true, true);
}
} // anonymous namespace

View File

@ -99,6 +99,10 @@ Options:
--compact-ids
Remap result ids to a compact range starting from %%1 and without
any gaps.
--cfg-cleanup
Cleanup the control flow graph. This will remove any unnecessary
code from the CFG like unreachable code. Performed on entry
point call tree functions and exported functions.
--inline-entry-points-exhaustive
Exhaustively inline all function calls in entry point call tree
functions. Currently does not inline calls to functions with
@ -359,6 +363,8 @@ OptStatus ParseFlags(int argc, const char** argv, Optimizer* optimizer,
optimizer->RegisterPass(CreateFlattenDecorationPass());
} else if (0 == strcmp(cur_arg, "--compact-ids")) {
optimizer->RegisterPass(CreateCompactIdsPass());
} else if (0 == strcmp(cur_arg, "--cfg-cleanup")) {
optimizer->RegisterPass(CreateCFGCleanupPass());
} else if (0 == strcmp(cur_arg, "-O")) {
optimizer->RegisterPerformancePasses();
} else if (0 == strcmp(cur_arg, "-Os")) {