diff --git a/source/reduce/CMakeLists.txt b/source/reduce/CMakeLists.txt index 1a6ead47..46802312 100644 --- a/source/reduce/CMakeLists.txt +++ b/source/reduce/CMakeLists.txt @@ -24,6 +24,8 @@ set(SPIRV_TOOLS_REDUCE_SOURCES reduction_opportunity_finder.h reduction_pass.h reduction_util.h + remove_block_reduction_opportunity.h + remove_block_reduction_opportunity_finder.h remove_instruction_reduction_opportunity.h remove_function_reduction_opportunity.h remove_function_reduction_opportunity_finder.h @@ -43,6 +45,8 @@ set(SPIRV_TOOLS_REDUCE_SOURCES reduction_opportunity.cpp reduction_pass.cpp reduction_util.cpp + remove_block_reduction_opportunity.cpp + remove_block_reduction_opportunity_finder.cpp remove_function_reduction_opportunity.cpp remove_function_reduction_opportunity_finder.cpp remove_instruction_reduction_opportunity.cpp diff --git a/source/reduce/remove_block_reduction_opportunity.cpp b/source/reduce/remove_block_reduction_opportunity.cpp new file mode 100644 index 00000000..9b952c43 --- /dev/null +++ b/source/reduce/remove_block_reduction_opportunity.cpp @@ -0,0 +1,56 @@ +// Copyright (c) 2019 Google LLC +// +// 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 "remove_block_reduction_opportunity.h" + +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace reduce { + +using namespace opt; + +RemoveBlockReductionOpportunity::RemoveBlockReductionOpportunity( + Function* function, BasicBlock* block) + : function_(function), block_(block) { + // precondition: + assert(block_->begin() != block_->end() && + block_->begin()->context()->get_def_use_mgr()->NumUsers( + block_->id()) == 0 && + "RemoveBlockReductionOpportunity block must have 0 references"); +} + +bool RemoveBlockReductionOpportunity::PreconditionHolds() { + // Removing other blocks cannot disable this opportunity. + return true; +} + +void RemoveBlockReductionOpportunity::Apply() { + // We need an iterator pointing to the block, hence the loop. + for (auto bi = function_->begin(); bi != function_->end(); ++bi) { + if (bi->id() == block_->id()) { + bi->KillAllInsts(true); + bi.Erase(); + // Block removal changes the function, but we don't use analyses, so no + // need to invalidate them. + return; + } + } + + assert(false && + "Unreachable: we should have found a block with the desired id."); +} + +} // namespace reduce +} // namespace spvtools diff --git a/source/reduce/remove_block_reduction_opportunity.h b/source/reduce/remove_block_reduction_opportunity.h new file mode 100644 index 00000000..45c0c178 --- /dev/null +++ b/source/reduce/remove_block_reduction_opportunity.h @@ -0,0 +1,46 @@ +// Copyright (c) 2019 Google LLC +// +// 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 SOURCE_REDUCE_REMOVE_BLOCK_REDUCTION_OPPORTUNITY_H_ +#define SOURCE_REDUCE_REMOVE_BLOCK_REDUCTION_OPPORTUNITY_H_ + +#include "reduction_opportunity.h" +#include "source/opt/basic_block.h" +#include "source/opt/function.h" + +namespace spvtools { +namespace reduce { + +// An opportunity to remove an unreferenced block. +// See RemoveBlockReductionOpportunityFinder. +class RemoveBlockReductionOpportunity : public ReductionOpportunity { + public: + // Creates the opportunity to remove |block| in |function| in |context|. + RemoveBlockReductionOpportunity(opt::Function* function, + opt::BasicBlock* block); + + bool PreconditionHolds() override; + + protected: + void Apply() override; + + private: + opt::Function* function_; + opt::BasicBlock* block_; +}; + +} // namespace reduce +} // namespace spvtools + +#endif // SOURCE_REDUCE_REMOVE_BLOCK_REDUCTION_OPPORTUNITY_H_ diff --git a/source/reduce/remove_block_reduction_opportunity_finder.cpp b/source/reduce/remove_block_reduction_opportunity_finder.cpp new file mode 100644 index 00000000..a4ca1f23 --- /dev/null +++ b/source/reduce/remove_block_reduction_opportunity_finder.cpp @@ -0,0 +1,96 @@ +// Copyright (c) 2019 Google LLC +// +// 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 "source/reduce/remove_block_reduction_opportunity_finder.h" +#include "source/reduce/remove_block_reduction_opportunity.h" + +namespace spvtools { +namespace reduce { + +using namespace opt; + +std::string RemoveBlockReductionOpportunityFinder::GetName() const { + return "RemoveBlockReductionOpportunityFinder"; +} + +std::vector> +RemoveBlockReductionOpportunityFinder::GetAvailableOpportunities( + opt::IRContext* context) const { + std::vector> result; + + // Consider every block in every function. + for (auto& function : *context->module()) { + for (auto bi = function.begin(); bi != function.end(); ++bi) { + if (IsBlockValidOpportunity(context, function, bi)) { + result.push_back(spvtools::MakeUnique( + &function, &*bi)); + } + } + } + return result; +} + +bool RemoveBlockReductionOpportunityFinder::IsBlockValidOpportunity( + opt::IRContext* context, opt::Function& function, + opt::Function::iterator& bi) { + assert(bi != function.end() && "Block iterator was out of bounds"); + + // Don't remove first block; we don't want to end up with no blocks. + if (bi == function.begin()) { + return false; + } + + // Don't remove blocks with references. + if (context->get_def_use_mgr()->NumUsers(bi->id()) > 0) { + return false; + } + + // Don't remove blocks whose instructions have outside references. + if (!BlockInstructionsHaveNoOutsideReferences(context, bi)) { + return false; + } + + return true; +} + +bool RemoveBlockReductionOpportunityFinder:: + BlockInstructionsHaveNoOutsideReferences(opt::IRContext* context, + const Function::iterator& bi) { + // Get all instructions in block. + std::unordered_set instructions_in_block; + for (const Instruction& instruction : *bi) { + instructions_in_block.insert(instruction.unique_id()); + } + + // For each instruction... + for (const Instruction& instruction : *bi) { + // For each use of the instruction... + bool no_uses_outside_block = context->get_def_use_mgr()->WhileEachUser( + &instruction, [&instructions_in_block](Instruction* user) -> bool { + // If the use is in this block, continue (return true). Otherwise, we + // found an outside use; return false (and stop). + return instructions_in_block.find(user->unique_id()) != + instructions_in_block.end(); + }); + + if (!no_uses_outside_block) { + return false; + } + } + + return true; +} + +} // namespace reduce +} // namespace spvtools diff --git a/source/reduce/remove_block_reduction_opportunity_finder.h b/source/reduce/remove_block_reduction_opportunity_finder.h new file mode 100644 index 00000000..83cd04b5 --- /dev/null +++ b/source/reduce/remove_block_reduction_opportunity_finder.h @@ -0,0 +1,55 @@ +// Copyright (c) 2019 Google LLC +// +// 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 SOURCE_REDUCE_REMOVE_BLOCK_REDUCTION_OPPORTUNITY_FINDER_H_ +#define SOURCE_REDUCE_REMOVE_BLOCK_REDUCTION_OPPORTUNITY_FINDER_H_ + +#include "source/opt/function.h" +#include "source/reduce/reduction_opportunity_finder.h" + +namespace spvtools { +namespace reduce { + +// A finder of opportunities to remove a block. The optimizer can remove dead +// code. However, the reducer needs to be able to remove at a fine-grained +// level. +class RemoveBlockReductionOpportunityFinder + : public ReductionOpportunityFinder { + public: + RemoveBlockReductionOpportunityFinder() = default; + + ~RemoveBlockReductionOpportunityFinder() override = default; + + std::string GetName() const final; + + std::vector> GetAvailableOpportunities( + opt::IRContext* context) const final; + + private: + // Returns true if the block |bi| in function |function| is a valid + // opportunity according to various restrictions. + static bool IsBlockValidOpportunity(opt::IRContext* context, + opt::Function& function, + opt::Function::iterator& bi); + + // Returns true if the instructions (definitions) in block |bi| have no + // references, except for references from inside the block itself. + static bool BlockInstructionsHaveNoOutsideReferences( + opt::IRContext* context, const opt::Function::iterator& bi); +}; + +} // namespace reduce +} // namespace spvtools + +#endif // SOURCE_REDUCE_REMOVE_BLOCK_REDUCTION_OPPORTUNITY_FINDER_H_ diff --git a/test/reduce/CMakeLists.txt b/test/reduce/CMakeLists.txt index df9542bf..4acab566 100644 --- a/test/reduce/CMakeLists.txt +++ b/test/reduce/CMakeLists.txt @@ -20,6 +20,7 @@ add_spvtools_unittest(TARGET reduce reduce_test_util.cpp reduce_test_util.h reducer_test.cpp + remove_block_test.cpp remove_function_test.cpp remove_opname_instruction_test.cpp remove_unreferenced_instruction_test.cpp diff --git a/test/reduce/remove_block_test.cpp b/test/reduce/remove_block_test.cpp new file mode 100644 index 00000000..71508c89 --- /dev/null +++ b/test/reduce/remove_block_test.cpp @@ -0,0 +1,358 @@ +// Copyright (c) 2019 Google LLC +// +// 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 "reduce_test_util.h" +#include "source/opt/build_module.h" +#include "source/reduce/reduction_opportunity.h" +#include "source/reduce/remove_block_reduction_opportunity.h" +#include "source/reduce/remove_block_reduction_opportunity_finder.h" + +namespace spvtools { +namespace reduce { +namespace { + +TEST(RemoveBlockReductionPassTest, BasicCheck) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %8 "x" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 1 + %10 = OpConstant %6 2 + %11 = OpConstant %6 3 + %12 = OpConstant %6 4 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + OpBranch %14 + %13 = OpLabel ; unreachable + OpStore %8 %9 + OpBranch %14 + %14 = OpLabel + OpStore %8 %10 + OpBranch %16 + %15 = OpLabel ; unreachable + OpStore %8 %11 + OpBranch %16 + %16 = OpLabel + OpStore %8 %12 + OpBranch %17 + %17 = OpLabel + OpReturn + OpFunctionEnd + )"; + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = + BuildModule(env, consumer, shader, kReduceAssembleOption); + const auto ops = + RemoveBlockReductionOpportunityFinder().GetAvailableOpportunities( + context.get()); + ASSERT_EQ(2, ops.size()); + + ASSERT_TRUE(ops[0]->PreconditionHolds()); + ops[0]->TryToApply(); + + std::string after_op_0 = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %8 "x" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 1 + %10 = OpConstant %6 2 + %11 = OpConstant %6 3 + %12 = OpConstant %6 4 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + OpBranch %14 + %14 = OpLabel + OpStore %8 %10 + OpBranch %16 + %15 = OpLabel + OpStore %8 %11 + OpBranch %16 + %16 = OpLabel + OpStore %8 %12 + OpBranch %17 + %17 = OpLabel + OpReturn + OpFunctionEnd + )"; + + CheckEqual(env, after_op_0, context.get()); + + ASSERT_TRUE(ops[1]->PreconditionHolds()); + ops[1]->TryToApply(); + + std::string after_op_1 = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %8 "x" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 1 + %10 = OpConstant %6 2 + %11 = OpConstant %6 3 + %12 = OpConstant %6 4 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + OpBranch %14 + %14 = OpLabel + OpStore %8 %10 + OpBranch %16 + %16 = OpLabel + OpStore %8 %12 + OpBranch %17 + %17 = OpLabel + OpReturn + OpFunctionEnd + )"; + + CheckEqual(env, after_op_1, context.get()); +} + +TEST(RemoveBlockReductionPassTest, UnreachableContinueAndMerge) { + // Loop with unreachable merge and continue target. There should be no + // opportunities. + + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %4 = OpFunction %2 None %3 + %5 = OpLabel + OpBranch %13 + %13 = OpLabel + OpLoopMerge %16 %15 None + OpBranch %14 + %14 = OpLabel + OpReturn + %15 = OpLabel + OpBranch %13 + %16 = OpLabel + OpReturn + OpFunctionEnd + )"; + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = + BuildModule(env, consumer, shader, kReduceAssembleOption); + const auto ops = + RemoveBlockReductionOpportunityFinder().GetAvailableOpportunities( + context.get()); + ASSERT_EQ(0, ops.size()); +} + +TEST(RemoveBlockReductionPassTest, OneBlock) { + // Function with just one block. There should be no opportunities. + + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %4 = OpFunction %2 None %3 + %5 = OpLabel + OpReturn + OpFunctionEnd + )"; + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = + BuildModule(env, consumer, shader, kReduceAssembleOption); + const auto ops = + RemoveBlockReductionOpportunityFinder().GetAvailableOpportunities( + context.get()); + ASSERT_EQ(0, ops.size()); +} + +TEST(RemoveBlockReductionPassTest, UnreachableBlocksWithOutsideIdUses) { + // A function with two unreachable blocks A -> B. A defines ID %9 and B uses + // %9. There are no references to A, but removing A would be invalid because + // of B's use of %9, so there should be no opportunities. + + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + OpName %2 "main" + %3 = OpTypeVoid + %4 = OpTypeInt 32 1 + %5 = OpTypeFunction %3 + %6 = OpConstant %4 1 + %2 = OpFunction %3 None %5 + %7 = OpLabel + OpReturn + %8 = OpLabel ; A + %9 = OpUndef %4 + OpBranch %10 + %10 = OpLabel ; B + %11 = OpIAdd %4 %6 %9 ; uses %9 from A, so A cannot be removed + OpReturn + OpFunctionEnd + )"; + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = + BuildModule(env, consumer, shader, kReduceAssembleOption); + const auto ops = + RemoveBlockReductionOpportunityFinder().GetAvailableOpportunities( + context.get()); + ASSERT_EQ(0, ops.size()); +} + +TEST(RemoveBlockReductionPassTest, UnreachableBlocksWithInsideIdUses) { + // Similar to the above test. + + // A function with two unreachable blocks A -> B. Both blocks create and use + // IDs, but the uses are contained within each block, so A should be removed. + + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + OpName %2 "main" + %3 = OpTypeVoid + %4 = OpTypeInt 32 1 + %5 = OpTypeFunction %3 + %6 = OpConstant %4 1 + %2 = OpFunction %3 None %5 + %7 = OpLabel + OpReturn + %8 = OpLabel ; A + %9 = OpUndef %4 ; define %9 + %10 = OpIAdd %4 %6 %9 ; use %9 + OpBranch %11 + %11 = OpLabel ; B + %12 = OpUndef %4 ; define %12 + %13 = OpIAdd %4 %6 %12 ; use %12 + OpReturn + OpFunctionEnd + )"; + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = + BuildModule(env, consumer, shader, kReduceAssembleOption); + auto ops = RemoveBlockReductionOpportunityFinder().GetAvailableOpportunities( + context.get()); + ASSERT_EQ(1, ops.size()); + + ASSERT_TRUE(ops[0]->PreconditionHolds()); + + ops[0]->TryToApply(); + + // Same as above, but block A is removed. + std::string after_op_0 = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + OpName %2 "main" + %3 = OpTypeVoid + %4 = OpTypeInt 32 1 + %5 = OpTypeFunction %3 + %6 = OpConstant %4 1 + %2 = OpFunction %3 None %5 + %7 = OpLabel + OpReturn + %11 = OpLabel + %12 = OpUndef %4 + %13 = OpIAdd %4 %6 %12 + OpReturn + OpFunctionEnd + )"; + + CheckEqual(env, after_op_0, context.get()); + + // Find opportunities again. There are no reference to B. B should now be + // removed. + + ops = RemoveBlockReductionOpportunityFinder().GetAvailableOpportunities( + context.get()); + + ASSERT_EQ(1, ops.size()); + + ASSERT_TRUE(ops[0]->PreconditionHolds()); + + ops[0]->TryToApply(); + + // Same as above, but block B is removed. + std::string after_op_0_again = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + OpName %2 "main" + %3 = OpTypeVoid + %4 = OpTypeInt 32 1 + %5 = OpTypeFunction %3 + %6 = OpConstant %4 1 + %2 = OpFunction %3 None %5 + %7 = OpLabel + OpReturn + OpFunctionEnd + )"; + + CheckEqual(env, after_op_0_again, context.get()); +} + +} // namespace +} // namespace reduce +} // namespace spvtools