diff --git a/Android.mk b/Android.mk index 4387d4fe..ed521516 100644 --- a/Android.mk +++ b/Android.mk @@ -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 \ diff --git a/include/spirv-tools/optimizer.hpp b/include/spirv-tools/optimizer.hpp index a7611d50..32800e62 100644 --- a/include/spirv-tools/optimizer.hpp +++ b/include/spirv-tools/optimizer.hpp @@ -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_ diff --git a/source/opt/CMakeLists.txt b/source/opt/CMakeLists.txt index 33d1e138..1c78dade 100644 --- a/source/opt/CMakeLists.txt +++ b/source/opt/CMakeLists.txt @@ -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 diff --git a/source/opt/cfg_cleanup_pass.cpp b/source/opt/cfg_cleanup_pass.cpp new file mode 100644 index 00000000..de6a4136 --- /dev/null +++ b/source/opt/cfg_cleanup_pass.cpp @@ -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 +#include + +#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 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 reachable_blocks; + std::unordered_set visited_blocks; + std::queue 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()] = █ + } + } +} + +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 diff --git a/source/opt/cfg_cleanup_pass.h b/source/opt/cfg_cleanup_pass.h new file mode 100644 index 00000000..27a6ba37 --- /dev/null +++ b/source/opt/cfg_cleanup_pass.h @@ -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 label2block_; +}; + +} // namespace opt +} // namespace spvtools + +#endif diff --git a/source/opt/function.h b/source/opt/function.h index aa15c502..a67c3299 100644 --- a/source/opt/function.h +++ b/source/opt/function.h @@ -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& entry() const { return blocks_.front(); } + iterator begin() { return iterator(&blocks_, blocks_.begin()); } iterator end() { return iterator(&blocks_, blocks_.end()); } const_iterator cbegin() const { diff --git a/source/opt/instruction.cpp b/source/opt/instruction.cpp index bccac121..699e7285 100644 --- a/source/opt/instruction.cpp +++ b/source/opt/instruction.cpp @@ -89,5 +89,11 @@ void Instruction::ToBinaryWithoutAttachedDebugInsts( binary->insert(binary->end(), operand.words.begin(), operand.words.end()); } +void Instruction::ReplaceOperands(const std::vector& new_operands) { + operands_.clear(); + operands_.insert(operands_.begin(), new_operands.begin(), new_operands.end()); + operands_.shrink_to_fit(); +} + } // namespace ir } // namespace spvtools diff --git a/source/opt/instruction.h b/source/opt/instruction.h index 9d49550a..a1c236e1 100644 --- a/source/opt/instruction.h +++ b/source/opt/instruction.h @@ -202,8 +202,13 @@ class Instruction { // Pushes the binary segments for this instruction into the back of *|binary|. void ToBinaryWithoutAttachedDebugInsts(std::vector* 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& 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); } diff --git a/source/opt/optimizer.cpp b/source/opt/optimizer.cpp index 546bb3d0..462519ea 100644 --- a/source/opt/optimizer.cpp +++ b/source/opt/optimizer.cpp @@ -230,6 +230,11 @@ Optimizer::PassToken CreateCompactIdsPass() { MakeUnique()); } +Optimizer::PassToken CreateCFGCleanupPass() { + return MakeUnique( + MakeUnique()); +} + std::vector Optimizer::GetPassNames() const { std::vector v; for (uint32_t i = 0; i < impl_->pass_manager.NumPasses(); i++) { diff --git a/source/opt/passes.h b/source/opt/passes.h index fb5c3ecc..98b9424f 100644 --- a/source/opt/passes.h +++ b/source/opt/passes.h @@ -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" diff --git a/test/opt/CMakeLists.txt b/test/opt/CMakeLists.txt index 16f50ed3..cf6df9dd 100644 --- a/test/opt/CMakeLists.txt +++ b/test/opt/CMakeLists.txt @@ -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 +) diff --git a/test/opt/cfg_cleanup_test.cpp b/test/opt/cfg_cleanup_test.cpp new file mode 100644 index 00000000..d4933ee0 --- /dev/null +++ b/test/opt/cfg_cleanup_test.cpp @@ -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( + 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(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(before, after, true, true); +} +} // anonymous namespace diff --git a/tools/opt/opt.cpp b/tools/opt/opt.cpp index 956f76c4..2a187f04 100644 --- a/tools/opt/opt.cpp +++ b/tools/opt/opt.cpp @@ -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")) {