From 5dcb576b6991b3e6b15e6dadb850924af92c7619 Mon Sep 17 00:00:00 2001 From: Alastair Donaldson Date: Fri, 11 Sep 2020 06:29:43 +0100 Subject: [PATCH] spirv-reduce: Support reducing a specific function (#3774) Motivated by integrating spirv-reduce into spirv-fuzz, so that an added function can be made smaller during shrinking, this adds support in spirv-reduce for asking reduction to be restricted to the instructions of a single specified function. --- include/spirv-tools/libspirv.h | 7 + include/spirv-tools/libspirv.hpp | 5 + source/reduce/CMakeLists.txt | 1 + ..._conditional_branch_opportunity_finder.cpp | 9 +- ...le_conditional_branch_opportunity_finder.h | 2 +- ...ge_blocks_reduction_opportunity_finder.cpp | 8 +- ...erge_blocks_reduction_opportunity_finder.h | 2 +- ..._to_const_reduction_opportunity_finder.cpp | 6 +- ...nd_to_const_reduction_opportunity_finder.h | 2 +- ...nating_id_reduction_opportunity_finder.cpp | 10 +- ...minating_id_reduction_opportunity_finder.h | 2 +- ..._to_undef_reduction_opportunity_finder.cpp | 6 +- ...nd_to_undef_reduction_opportunity_finder.h | 2 +- source/reduce/reducer.cpp | 3 +- .../reduce/reduction_opportunity_finder.cpp | 34 +++ source/reduce/reduction_opportunity_finder.h | 19 +- source/reduce/reduction_pass.cpp | 4 +- source/reduce/reduction_pass.h | 7 +- ...ove_block_reduction_opportunity_finder.cpp | 26 +- ...emove_block_reduction_opportunity_finder.h | 6 +- ..._function_reduction_opportunity_finder.cpp | 9 +- ...ve_function_reduction_opportunity_finder.h | 2 +- ...selection_reduction_opportunity_finder.cpp | 6 +- ...e_selection_reduction_opportunity_finder.h | 2 +- ...struction_reduction_opportunity_finder.cpp | 111 +++++---- ...instruction_reduction_opportunity_finder.h | 2 +- ...ct_member_reduction_opportunity_finder.cpp | 9 +- ...ruct_member_reduction_opportunity_finder.h | 2 +- ...al_branch_to_branch_opportunity_finder.cpp | 6 +- ...onal_branch_to_branch_opportunity_finder.h | 2 +- ...selection_reduction_opportunity_finder.cpp | 18 +- ...o_selection_reduction_opportunity_finder.h | 2 +- source/spirv_reducer_options.cpp | 9 +- source/spirv_reducer_options.h | 3 + ...anch_to_simple_conditional_branch_test.cpp | 20 +- test/reduce/merge_blocks_test.cpp | 8 +- test/reduce/operand_to_constant_test.cpp | 152 +++++++++++- test/reduce/operand_to_dominating_id_test.cpp | 2 +- test/reduce/operand_to_undef_test.cpp | 4 +- test/reduce/reducer_test.cpp | 225 +++++++++++++++++- test/reduce/remove_block_test.cpp | 12 +- test/reduce/remove_function_test.cpp | 12 +- test/reduce/remove_selection_test.cpp | 22 +- .../reduce/remove_unused_instruction_test.cpp | 28 +-- .../remove_unused_struct_member_test.cpp | 6 +- ...mple_conditional_branch_to_branch_test.cpp | 20 +- .../structured_loop_to_selection_test.cpp | 54 ++--- .../validation_during_reduction_test.cpp | 6 +- tools/reduce/reduce.cpp | 41 ++++ 49 files changed, 727 insertions(+), 229 deletions(-) create mode 100644 source/reduce/reduction_opportunity_finder.cpp diff --git a/include/spirv-tools/libspirv.h b/include/spirv-tools/libspirv.h index 7b3f38d8..09dae224 100644 --- a/include/spirv-tools/libspirv.h +++ b/include/spirv-tools/libspirv.h @@ -674,6 +674,13 @@ SPIRV_TOOLS_EXPORT void spvReducerOptionsSetStepLimit( SPIRV_TOOLS_EXPORT void spvReducerOptionsSetFailOnValidationError( spv_reducer_options options, bool fail_on_validation_error); +// Sets the function that the reducer should target. If set to zero the reducer +// will target all functions as well as parts of the module that lie outside +// functions. Otherwise the reducer will restrict reduction to the function +// with result id |target_function|, which is required to exist. +SPIRV_TOOLS_EXPORT void spvReducerOptionsSetTargetFunction( + spv_reducer_options options, uint32_t target_function); + // Creates a fuzzer options object with default options. Returns a valid // options object. The object remains valid until it is passed into // |spvFuzzerOptionsDestroy|. diff --git a/include/spirv-tools/libspirv.hpp b/include/spirv-tools/libspirv.hpp index 6b31a07e..5dddf8bf 100644 --- a/include/spirv-tools/libspirv.hpp +++ b/include/spirv-tools/libspirv.hpp @@ -202,6 +202,11 @@ class ReducerOptions { fail_on_validation_error); } + // See spvReducerOptionsSetTargetFunction. + void set_target_function(uint32_t target_function) { + spvReducerOptionsSetTargetFunction(options_, target_function); + } + private: spv_reducer_options options_; }; diff --git a/source/reduce/CMakeLists.txt b/source/reduce/CMakeLists.txt index 9480a43c..e113ca25 100644 --- a/source/reduce/CMakeLists.txt +++ b/source/reduce/CMakeLists.txt @@ -50,6 +50,7 @@ set(SPIRV_TOOLS_REDUCE_SOURCES operand_to_dominating_id_reduction_opportunity_finder.cpp reducer.cpp reduction_opportunity.cpp + reduction_opportunity_finder.cpp reduction_pass.cpp reduction_util.cpp remove_block_reduction_opportunity.cpp diff --git a/source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.cpp b/source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.cpp index ab971db7..2cd779a2 100644 --- a/source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.cpp +++ b/source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.cpp @@ -22,7 +22,8 @@ namespace reduce { std::vector> ConditionalBranchToSimpleConditionalBranchOpportunityFinder:: - GetAvailableOpportunities(opt::IRContext* context) const { + GetAvailableOpportunities(opt::IRContext* context, + uint32_t target_function) const { std::vector> result; // Find the opportunities for redirecting all false targets before the @@ -31,10 +32,10 @@ ConditionalBranchToSimpleConditionalBranchOpportunityFinder:: // reducer is improved by avoiding contiguous opportunities that disable one // another. for (bool redirect_to_true : {true, false}) { - // Consider every function. - for (auto& function : *context->module()) { + // Consider every relevant function. + for (auto* function : GetTargetFunctions(context, target_function)) { // Consider every block in the function. - for (auto& block : function) { + for (auto& block : *function) { // The terminator must be SpvOpBranchConditional. opt::Instruction* terminator = block.terminator(); if (terminator->opcode() != SpvOpBranchConditional) { diff --git a/source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.h b/source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.h index c582a889..17af9b00 100644 --- a/source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.h +++ b/source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.h @@ -26,7 +26,7 @@ class ConditionalBranchToSimpleConditionalBranchOpportunityFinder : public ReductionOpportunityFinder { public: std::vector> GetAvailableOpportunities( - opt::IRContext* context) const override; + opt::IRContext* context, uint32_t target_function) const override; std::string GetName() const override; }; diff --git a/source/reduce/merge_blocks_reduction_opportunity_finder.cpp b/source/reduce/merge_blocks_reduction_opportunity_finder.cpp index a853f0a5..ea5e9dac 100644 --- a/source/reduce/merge_blocks_reduction_opportunity_finder.cpp +++ b/source/reduce/merge_blocks_reduction_opportunity_finder.cpp @@ -25,17 +25,17 @@ std::string MergeBlocksReductionOpportunityFinder::GetName() const { std::vector> MergeBlocksReductionOpportunityFinder::GetAvailableOpportunities( - opt::IRContext* context) const { + opt::IRContext* context, uint32_t target_function) const { std::vector> result; // Consider every block in every function. - for (auto& function : *context->module()) { - for (auto& block : function) { + for (auto* function : GetTargetFunctions(context, target_function)) { + for (auto& block : *function) { // See whether it is possible to merge this block with its successor. if (opt::blockmergeutil::CanMergeWithSuccessor(context, &block)) { // It is, so record an opportunity to do this. result.push_back(spvtools::MakeUnique( - context, &function, &block)); + context, function, &block)); } } } diff --git a/source/reduce/merge_blocks_reduction_opportunity_finder.h b/source/reduce/merge_blocks_reduction_opportunity_finder.h index dbf82fec..df7a8bf1 100644 --- a/source/reduce/merge_blocks_reduction_opportunity_finder.h +++ b/source/reduce/merge_blocks_reduction_opportunity_finder.h @@ -31,7 +31,7 @@ class MergeBlocksReductionOpportunityFinder std::string GetName() const final; std::vector> GetAvailableOpportunities( - opt::IRContext* context) const final; + opt::IRContext* context, uint32_t target_function) const final; private: }; diff --git a/source/reduce/operand_to_const_reduction_opportunity_finder.cpp b/source/reduce/operand_to_const_reduction_opportunity_finder.cpp index 52d244f8..eb7498ad 100644 --- a/source/reduce/operand_to_const_reduction_opportunity_finder.cpp +++ b/source/reduce/operand_to_const_reduction_opportunity_finder.cpp @@ -22,7 +22,7 @@ namespace reduce { std::vector> OperandToConstReductionOpportunityFinder::GetAvailableOpportunities( - opt::IRContext* context) const { + opt::IRContext* context, uint32_t target_function) const { std::vector> result; assert(result.empty()); @@ -35,8 +35,8 @@ OperandToConstReductionOpportunityFinder::GetAvailableOpportunities( // contiguous blocks of opportunities early on, and we want to avoid having a // large block of incompatible opportunities if possible. for (const auto& constant : context->GetConstants()) { - for (auto& function : *context->module()) { - for (auto& block : function) { + for (auto* function : GetTargetFunctions(context, target_function)) { + for (auto& block : *function) { for (auto& inst : block) { // We iterate through the operands using an explicit index (rather // than using a lambda) so that we use said index in the construction diff --git a/source/reduce/operand_to_const_reduction_opportunity_finder.h b/source/reduce/operand_to_const_reduction_opportunity_finder.h index 93c0dcd3..67267468 100644 --- a/source/reduce/operand_to_const_reduction_opportunity_finder.h +++ b/source/reduce/operand_to_const_reduction_opportunity_finder.h @@ -33,7 +33,7 @@ class OperandToConstReductionOpportunityFinder std::string GetName() const final; std::vector> GetAvailableOpportunities( - opt::IRContext* context) const final; + opt::IRContext* context, uint32_t target_function) const final; private: }; diff --git a/source/reduce/operand_to_dominating_id_reduction_opportunity_finder.cpp b/source/reduce/operand_to_dominating_id_reduction_opportunity_finder.cpp index 744218c5..ca3a99e9 100644 --- a/source/reduce/operand_to_dominating_id_reduction_opportunity_finder.cpp +++ b/source/reduce/operand_to_dominating_id_reduction_opportunity_finder.cpp @@ -22,7 +22,7 @@ namespace reduce { std::vector> OperandToDominatingIdReductionOpportunityFinder::GetAvailableOpportunities( - opt::IRContext* context) const { + opt::IRContext* context, uint32_t target_function) const { std::vector> result; // Go through every instruction in every block, considering it as a potential @@ -38,15 +38,15 @@ OperandToDominatingIdReductionOpportunityFinder::GetAvailableOpportunities( // to prioritise replacing e with its smallest sub-expressions; generalising // this idea to dominating ids this roughly corresponds to more distant // dominators. - for (auto& function : *context->module()) { - for (auto dominating_block = function.begin(); - dominating_block != function.end(); ++dominating_block) { + for (auto* function : GetTargetFunctions(context, target_function)) { + for (auto dominating_block = function->begin(); + dominating_block != function->end(); ++dominating_block) { for (auto& dominating_inst : *dominating_block) { if (dominating_inst.HasResultId() && dominating_inst.type_id()) { // Consider replacing any operand with matching type in a dominated // instruction with the id generated by this instruction. GetOpportunitiesForDominatingInst( - &result, &dominating_inst, dominating_block, &function, context); + &result, &dominating_inst, dominating_block, function, context); } } } diff --git a/source/reduce/operand_to_dominating_id_reduction_opportunity_finder.h b/source/reduce/operand_to_dominating_id_reduction_opportunity_finder.h index 7745ff70..5f333705 100644 --- a/source/reduce/operand_to_dominating_id_reduction_opportunity_finder.h +++ b/source/reduce/operand_to_dominating_id_reduction_opportunity_finder.h @@ -40,7 +40,7 @@ class OperandToDominatingIdReductionOpportunityFinder std::string GetName() const final; std::vector> GetAvailableOpportunities( - opt::IRContext* context) const final; + opt::IRContext* context, uint32_t target_function) const final; private: void GetOpportunitiesForDominatingInst( diff --git a/source/reduce/operand_to_undef_reduction_opportunity_finder.cpp b/source/reduce/operand_to_undef_reduction_opportunity_finder.cpp index afee3c88..06bf9550 100644 --- a/source/reduce/operand_to_undef_reduction_opportunity_finder.cpp +++ b/source/reduce/operand_to_undef_reduction_opportunity_finder.cpp @@ -22,11 +22,11 @@ namespace reduce { std::vector> OperandToUndefReductionOpportunityFinder::GetAvailableOpportunities( - opt::IRContext* context) const { + opt::IRContext* context, uint32_t target_function) const { std::vector> result; - for (auto& function : *context->module()) { - for (auto& block : function) { + for (auto* function : GetTargetFunctions(context, target_function)) { + for (auto& block : *function) { for (auto& inst : block) { // Skip instructions that result in a pointer type. auto type_id = inst.type_id(); diff --git a/source/reduce/operand_to_undef_reduction_opportunity_finder.h b/source/reduce/operand_to_undef_reduction_opportunity_finder.h index 9cdd8cd5..a5c759e9 100644 --- a/source/reduce/operand_to_undef_reduction_opportunity_finder.h +++ b/source/reduce/operand_to_undef_reduction_opportunity_finder.h @@ -32,7 +32,7 @@ class OperandToUndefReductionOpportunityFinder std::string GetName() const final; std::vector> GetAvailableOpportunities( - opt::IRContext* context) const final; + opt::IRContext* context, uint32_t target_function) const final; private: }; diff --git a/source/reduce/reducer.cpp b/source/reduce/reducer.cpp index 092d4090..18eeaeb6 100644 --- a/source/reduce/reducer.cpp +++ b/source/reduce/reducer.cpp @@ -183,7 +183,8 @@ Reducer::ReductionResultStatus Reducer::RunPasses( consumer_(SPV_MSG_INFO, nullptr, {}, ("Trying pass " + pass->GetName() + ".").c_str()); do { - auto maybe_result = pass->TryApplyReduction(*current_binary); + auto maybe_result = + pass->TryApplyReduction(*current_binary, options->target_function); if (maybe_result.empty()) { // For this round, the pass has no more opportunities (chunks) to // apply, so move on to the next pass. diff --git a/source/reduce/reduction_opportunity_finder.cpp b/source/reduce/reduction_opportunity_finder.cpp new file mode 100644 index 00000000..0bd253b8 --- /dev/null +++ b/source/reduce/reduction_opportunity_finder.cpp @@ -0,0 +1,34 @@ +// Copyright (c) 2020 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 "reduction_opportunity_finder.h" + +namespace spvtools { +namespace reduce { + +std::vector ReductionOpportunityFinder::GetTargetFunctions( + opt::IRContext* ir_context, uint32_t target_function) { + std::vector result; + for (auto& function : *ir_context->module()) { + if (!target_function || function.result_id() == target_function) { + result.push_back(&function); + } + } + assert((!target_function || !result.empty()) && + "Requested target function must exist."); + return result; +} + +} // namespace reduce +} // namespace spvtools diff --git a/source/reduce/reduction_opportunity_finder.h b/source/reduce/reduction_opportunity_finder.h index 1837484d..d95c832b 100644 --- a/source/reduce/reduction_opportunity_finder.h +++ b/source/reduce/reduction_opportunity_finder.h @@ -15,6 +15,8 @@ #ifndef SOURCE_REDUCE_REDUCTION_OPPORTUNITY_FINDER_H_ #define SOURCE_REDUCE_REDUCTION_OPPORTUNITY_FINDER_H_ +#include + #include "source/opt/ir_context.h" #include "source/reduce/reduction_opportunity.h" @@ -29,12 +31,25 @@ class ReductionOpportunityFinder { virtual ~ReductionOpportunityFinder() = default; // Finds and returns the reduction opportunities relevant to this pass that - // could be applied to the given SPIR-V module. + // could be applied to SPIR-V module |context|. + // + // If |target_function| is non-zero then the available opportunities will be + // restricted to only those opportunities that modify the function with result + // id |target_function|. virtual std::vector> - GetAvailableOpportunities(opt::IRContext* context) const = 0; + GetAvailableOpportunities(opt::IRContext* context, + uint32_t target_function) const = 0; // Provides a name for the finder. virtual std::string GetName() const = 0; + + protected: + // Requires that |target_function| is zero or the id of a function in + // |ir_context|. If |target_function| is zero, returns all the functions in + // |ir_context|. Otherwise, returns the function with id |target_function|. + // This allows fuzzer passes to restrict attention to a single function. + static std::vector GetTargetFunctions( + opt::IRContext* ir_context, uint32_t target_function); }; } // namespace reduce diff --git a/source/reduce/reduction_pass.cpp b/source/reduce/reduction_pass.cpp index 2cb986de..c6d1ebfd 100644 --- a/source/reduce/reduction_pass.cpp +++ b/source/reduce/reduction_pass.cpp @@ -22,7 +22,7 @@ namespace spvtools { namespace reduce { std::vector ReductionPass::TryApplyReduction( - const std::vector& binary) { + const std::vector& binary, uint32_t target_function) { // We represent modules as binaries because (a) attempts at reduction need to // end up in binary form to be passed on to SPIR-V-consuming tools, and (b) // when we apply a reduction step we need to do it on a fresh version of the @@ -34,7 +34,7 @@ std::vector ReductionPass::TryApplyReduction( assert(context); std::vector> opportunities = - finder_->GetAvailableOpportunities(context.get()); + finder_->GetAvailableOpportunities(context.get(), target_function); // There is no point in having a granularity larger than the number of // opportunities, so reduce the granularity in this case. diff --git a/source/reduce/reduction_pass.h b/source/reduce/reduction_pass.h index f2d937ba..18361824 100644 --- a/source/reduce/reduction_pass.h +++ b/source/reduce/reduction_pass.h @@ -49,7 +49,12 @@ class ReductionPass { // Returns an empty vector if there are no more chunks left to apply; in this // case, the index will be reset and the granularity lowered for the next // round. - std::vector TryApplyReduction(const std::vector& binary); + // + // If |target_function| is non-zero, only reduction opportunities that + // simplify the internals of the function with result id |target_function| + // will be applied. + std::vector TryApplyReduction(const std::vector& binary, + uint32_t target_function); // Notifies the reduction pass whether the binary returned from // TryApplyReduction is interesting, so that the next call to diff --git a/source/reduce/remove_block_reduction_opportunity_finder.cpp b/source/reduce/remove_block_reduction_opportunity_finder.cpp index a4bc4638..27a4570c 100644 --- a/source/reduce/remove_block_reduction_opportunity_finder.cpp +++ b/source/reduce/remove_block_reduction_opportunity_finder.cpp @@ -25,15 +25,15 @@ std::string RemoveBlockReductionOpportunityFinder::GetName() const { std::vector> RemoveBlockReductionOpportunityFinder::GetAvailableOpportunities( - opt::IRContext* context) const { + opt::IRContext* context, uint32_t target_function) 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)); + // Consider every block in every relevant function. + for (auto* function : GetTargetFunctions(context, target_function)) { + for (auto bi = function->begin(); bi != function->end(); ++bi) { + if (IsBlockValidOpportunity(context, function, &bi)) { + result.push_back( + MakeUnique(function, &*bi)); } } } @@ -41,22 +41,22 @@ RemoveBlockReductionOpportunityFinder::GetAvailableOpportunities( } bool RemoveBlockReductionOpportunityFinder::IsBlockValidOpportunity( - opt::IRContext* context, opt::Function& function, - opt::Function::iterator& bi) { - assert(bi != function.end() && "Block iterator was out of bounds"); + 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()) { + if (*bi == function->begin()) { return false; } // Don't remove blocks with references. - if (context->get_def_use_mgr()->NumUsers(bi->id()) > 0) { + 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)) { + if (!BlockInstructionsHaveNoOutsideReferences(context, *bi)) { return false; } diff --git a/source/reduce/remove_block_reduction_opportunity_finder.h b/source/reduce/remove_block_reduction_opportunity_finder.h index 83cd04b5..d347bf91 100644 --- a/source/reduce/remove_block_reduction_opportunity_finder.h +++ b/source/reduce/remove_block_reduction_opportunity_finder.h @@ -34,14 +34,14 @@ class RemoveBlockReductionOpportunityFinder std::string GetName() const final; std::vector> GetAvailableOpportunities( - opt::IRContext* context) const final; + opt::IRContext* context, uint32_t target_function) 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); + 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. diff --git a/source/reduce/remove_function_reduction_opportunity_finder.cpp b/source/reduce/remove_function_reduction_opportunity_finder.cpp index 1edb9733..1d8d9726 100644 --- a/source/reduce/remove_function_reduction_opportunity_finder.cpp +++ b/source/reduce/remove_function_reduction_opportunity_finder.cpp @@ -21,7 +21,14 @@ namespace reduce { std::vector> RemoveFunctionReductionOpportunityFinder::GetAvailableOpportunities( - opt::IRContext* context) const { + opt::IRContext* context, uint32_t target_function) const { + if (target_function) { + // If we are targeting a specific function then we are only interested in + // opportunities that simplify the internals of that function; removing + // whole functions does not fit the bill. + return {}; + } + std::vector> result; // Consider each function. for (auto& function : *context->module()) { diff --git a/source/reduce/remove_function_reduction_opportunity_finder.h b/source/reduce/remove_function_reduction_opportunity_finder.h index 7952a229..6fcfb779 100644 --- a/source/reduce/remove_function_reduction_opportunity_finder.h +++ b/source/reduce/remove_function_reduction_opportunity_finder.h @@ -31,7 +31,7 @@ class RemoveFunctionReductionOpportunityFinder std::string GetName() const final; std::vector> GetAvailableOpportunities( - opt::IRContext* context) const final; + opt::IRContext* context, uint32_t target_function) const final; private: }; diff --git a/source/reduce/remove_selection_reduction_opportunity_finder.cpp b/source/reduce/remove_selection_reduction_opportunity_finder.cpp index 327f73eb..74df1b8d 100644 --- a/source/reduce/remove_selection_reduction_opportunity_finder.cpp +++ b/source/reduce/remove_selection_reduction_opportunity_finder.cpp @@ -30,11 +30,11 @@ std::string RemoveSelectionReductionOpportunityFinder::GetName() const { std::vector> RemoveSelectionReductionOpportunityFinder::GetAvailableOpportunities( - opt::IRContext* context) const { + opt::IRContext* context, uint32_t target_function) const { // Get all loop merge and continue blocks so we can check for these later. std::unordered_set merge_and_continue_blocks_from_loops; - for (auto& function : *context->module()) { - for (auto& block : function) { + for (auto* function : GetTargetFunctions(context, target_function)) { + for (auto& block : *function) { if (auto merge_instruction = block.GetMergeInst()) { if (merge_instruction->opcode() == SpvOpLoopMerge) { uint32_t merge_block_id = diff --git a/source/reduce/remove_selection_reduction_opportunity_finder.h b/source/reduce/remove_selection_reduction_opportunity_finder.h index 848122b8..1a174933 100644 --- a/source/reduce/remove_selection_reduction_opportunity_finder.h +++ b/source/reduce/remove_selection_reduction_opportunity_finder.h @@ -33,7 +33,7 @@ class RemoveSelectionReductionOpportunityFinder std::string GetName() const final; std::vector> GetAvailableOpportunities( - opt::IRContext* context) const final; + opt::IRContext* context, uint32_t target_function) const final; // Returns true if the OpSelectionMerge instruction |merge_instruction| in // block |header_block| can be removed. diff --git a/source/reduce/remove_unused_instruction_reduction_opportunity_finder.cpp b/source/reduce/remove_unused_instruction_reduction_opportunity_finder.cpp index 91ec542c..d7bb3a82 100644 --- a/source/reduce/remove_unused_instruction_reduction_opportunity_finder.cpp +++ b/source/reduce/remove_unused_instruction_reduction_opportunity_finder.cpp @@ -28,61 +28,72 @@ RemoveUnusedInstructionReductionOpportunityFinder:: std::vector> RemoveUnusedInstructionReductionOpportunityFinder::GetAvailableOpportunities( - opt::IRContext* context) const { + opt::IRContext* context, uint32_t target_function) const { std::vector> result; - for (auto& inst : context->module()->debugs1()) { - if (context->get_def_use_mgr()->NumUses(&inst) > 0) { - continue; + if (!target_function) { + // We are not restricting reduction to a specific function, so we consider + // unused instructions defined outside functions. + + for (auto& inst : context->module()->debugs1()) { + if (context->get_def_use_mgr()->NumUses(&inst) > 0) { + continue; + } + result.push_back( + MakeUnique(&inst)); + } + + for (auto& inst : context->module()->debugs2()) { + if (context->get_def_use_mgr()->NumUses(&inst) > 0) { + continue; + } + result.push_back( + MakeUnique(&inst)); + } + + for (auto& inst : context->module()->debugs3()) { + if (context->get_def_use_mgr()->NumUses(&inst) > 0) { + continue; + } + result.push_back( + MakeUnique(&inst)); + } + + for (auto& inst : context->module()->ext_inst_debuginfo()) { + if (context->get_def_use_mgr()->NumUses(&inst) > 0) { + continue; + } + result.push_back( + MakeUnique(&inst)); + } + + for (auto& inst : context->module()->types_values()) { + if (!remove_constants_and_undefs_ && + spvOpcodeIsConstantOrUndef(inst.opcode())) { + continue; + } + if (!OnlyReferencedByIntimateDecorationOrEntryPointInterface(context, + inst)) { + continue; + } + result.push_back( + MakeUnique(&inst)); + } + + for (auto& inst : context->module()->annotations()) { + if (context->get_def_use_mgr()->NumUsers(&inst) > 0) { + continue; + } + if (!IsIndependentlyRemovableDecoration(inst)) { + continue; + } + result.push_back( + MakeUnique(&inst)); } - result.push_back(MakeUnique(&inst)); } - for (auto& inst : context->module()->debugs2()) { - if (context->get_def_use_mgr()->NumUses(&inst) > 0) { - continue; - } - result.push_back(MakeUnique(&inst)); - } - - for (auto& inst : context->module()->debugs3()) { - if (context->get_def_use_mgr()->NumUses(&inst) > 0) { - continue; - } - result.push_back(MakeUnique(&inst)); - } - - for (auto& inst : context->module()->ext_inst_debuginfo()) { - if (context->get_def_use_mgr()->NumUses(&inst) > 0) { - continue; - } - result.push_back(MakeUnique(&inst)); - } - - for (auto& inst : context->module()->types_values()) { - if (!remove_constants_and_undefs_ && - spvOpcodeIsConstantOrUndef(inst.opcode())) { - continue; - } - if (!OnlyReferencedByIntimateDecorationOrEntryPointInterface(context, - inst)) { - continue; - } - result.push_back(MakeUnique(&inst)); - } - - for (auto& inst : context->module()->annotations()) { - if (context->get_def_use_mgr()->NumUsers(&inst) > 0) { - continue; - } - if (!IsIndependentlyRemovableDecoration(inst)) { - continue; - } - result.push_back(MakeUnique(&inst)); - } - - for (auto& function : *context->module()) { - for (auto& block : function) { + for (auto* function : GetTargetFunctions(context, target_function)) { + for (auto& block : *function) { for (auto& inst : block) { if (context->get_def_use_mgr()->NumUses(&inst) > 0) { continue; diff --git a/source/reduce/remove_unused_instruction_reduction_opportunity_finder.h b/source/reduce/remove_unused_instruction_reduction_opportunity_finder.h index cbf6a5bd..03236400 100644 --- a/source/reduce/remove_unused_instruction_reduction_opportunity_finder.h +++ b/source/reduce/remove_unused_instruction_reduction_opportunity_finder.h @@ -38,7 +38,7 @@ class RemoveUnusedInstructionReductionOpportunityFinder std::string GetName() const final; std::vector> GetAvailableOpportunities( - opt::IRContext* context) const final; + opt::IRContext* context, uint32_t target_function) const final; private: // Returns true if and only if the only uses of |inst| are by decorations that diff --git a/source/reduce/remove_unused_struct_member_reduction_opportunity_finder.cpp b/source/reduce/remove_unused_struct_member_reduction_opportunity_finder.cpp index 39ce47f3..e72be625 100644 --- a/source/reduce/remove_unused_struct_member_reduction_opportunity_finder.cpp +++ b/source/reduce/remove_unused_struct_member_reduction_opportunity_finder.cpp @@ -24,7 +24,14 @@ namespace reduce { std::vector> RemoveUnusedStructMemberReductionOpportunityFinder::GetAvailableOpportunities( - opt::IRContext* context) const { + opt::IRContext* context, uint32_t target_function) const { + if (target_function) { + // Removing an unused struct member is a global change, as struct types are + // global. We thus do not consider such opportunities if we are targeting + // a specific function. + return {}; + } + std::vector> result; // We track those struct members that are never accessed. We do this by diff --git a/source/reduce/remove_unused_struct_member_reduction_opportunity_finder.h b/source/reduce/remove_unused_struct_member_reduction_opportunity_finder.h index 13f40172..98f9c019 100644 --- a/source/reduce/remove_unused_struct_member_reduction_opportunity_finder.h +++ b/source/reduce/remove_unused_struct_member_reduction_opportunity_finder.h @@ -32,7 +32,7 @@ class RemoveUnusedStructMemberReductionOpportunityFinder std::string GetName() const final; std::vector> GetAvailableOpportunities( - opt::IRContext* context) const final; + opt::IRContext* context, uint32_t target_function) const final; private: // A helper method to update |unused_members_to_structs| by removing from it diff --git a/source/reduce/simple_conditional_branch_to_branch_opportunity_finder.cpp b/source/reduce/simple_conditional_branch_to_branch_opportunity_finder.cpp index f347abf2..d867c3ad 100644 --- a/source/reduce/simple_conditional_branch_to_branch_opportunity_finder.cpp +++ b/source/reduce/simple_conditional_branch_to_branch_opportunity_finder.cpp @@ -22,13 +22,13 @@ namespace reduce { std::vector> SimpleConditionalBranchToBranchOpportunityFinder::GetAvailableOpportunities( - opt::IRContext* context) const { + opt::IRContext* context, uint32_t target_function) const { std::vector> result; // Consider every function. - for (auto& function : *context->module()) { + for (auto* function : GetTargetFunctions(context, target_function)) { // Consider every block in the function. - for (auto& block : function) { + for (auto& block : *function) { // The terminator must be SpvOpBranchConditional. opt::Instruction* terminator = block.terminator(); if (terminator->opcode() != SpvOpBranchConditional) { diff --git a/source/reduce/simple_conditional_branch_to_branch_opportunity_finder.h b/source/reduce/simple_conditional_branch_to_branch_opportunity_finder.h index 10b9dce4..8869908b 100644 --- a/source/reduce/simple_conditional_branch_to_branch_opportunity_finder.h +++ b/source/reduce/simple_conditional_branch_to_branch_opportunity_finder.h @@ -26,7 +26,7 @@ class SimpleConditionalBranchToBranchOpportunityFinder : public ReductionOpportunityFinder { public: std::vector> GetAvailableOpportunities( - opt::IRContext* context) const override; + opt::IRContext* context, uint32_t target_function) const override; std::string GetName() const override; }; diff --git a/source/reduce/structured_loop_to_selection_reduction_opportunity_finder.cpp b/source/reduce/structured_loop_to_selection_reduction_opportunity_finder.cpp index 47505aa3..fdf3ab04 100644 --- a/source/reduce/structured_loop_to_selection_reduction_opportunity_finder.cpp +++ b/source/reduce/structured_loop_to_selection_reduction_opportunity_finder.cpp @@ -26,12 +26,12 @@ const uint32_t kContinueNodeIndex = 1; std::vector> StructuredLoopToSelectionReductionOpportunityFinder::GetAvailableOpportunities( - opt::IRContext* context) const { + opt::IRContext* context, uint32_t target_function) const { std::vector> result; std::set merge_block_ids; - for (auto& function : *context->module()) { - for (auto& block : function) { + for (auto* function : GetTargetFunctions(context, target_function)) { + for (auto& block : *function) { auto merge_block_id = block.MergeBlockIdIfAny(); if (merge_block_id) { merge_block_ids.insert(merge_block_id); @@ -40,8 +40,8 @@ StructuredLoopToSelectionReductionOpportunityFinder::GetAvailableOpportunities( } // Consider each loop construct header in the module. - for (auto& function : *context->module()) { - for (auto& block : function) { + for (auto* function : GetTargetFunctions(context, target_function)) { + for (auto& block : *function) { auto loop_merge_inst = block.GetLoopMergeInst(); if (!loop_merge_inst) { // This is not a loop construct header. @@ -69,8 +69,8 @@ StructuredLoopToSelectionReductionOpportunityFinder::GetAvailableOpportunities( // so we cautiously do not consider applying a transformation. auto merge_block_id = loop_merge_inst->GetSingleWordInOperand(kMergeNodeIndex); - if (!context->GetDominatorAnalysis(&function)->Dominates( - block.id(), merge_block_id)) { + if (!context->GetDominatorAnalysis(function)->Dominates(block.id(), + merge_block_id)) { continue; } @@ -78,7 +78,7 @@ StructuredLoopToSelectionReductionOpportunityFinder::GetAvailableOpportunities( // construct header. If not (e.g. because the loop contains OpReturn, // OpKill or OpUnreachable), we cautiously do not consider applying // a transformation. - if (!context->GetPostDominatorAnalysis(&function)->Dominates( + if (!context->GetPostDominatorAnalysis(function)->Dominates( merge_block_id, block.id())) { continue; } @@ -87,7 +87,7 @@ StructuredLoopToSelectionReductionOpportunityFinder::GetAvailableOpportunities( // opportunity to do so. result.push_back( MakeUnique( - context, &block, &function)); + context, &block, function)); } } return result; diff --git a/source/reduce/structured_loop_to_selection_reduction_opportunity_finder.h b/source/reduce/structured_loop_to_selection_reduction_opportunity_finder.h index d63d4340..6166af38 100644 --- a/source/reduce/structured_loop_to_selection_reduction_opportunity_finder.h +++ b/source/reduce/structured_loop_to_selection_reduction_opportunity_finder.h @@ -46,7 +46,7 @@ class StructuredLoopToSelectionReductionOpportunityFinder std::string GetName() const final; std::vector> GetAvailableOpportunities( - opt::IRContext* context) const final; + opt::IRContext* context, uint32_t target_function) const final; private: }; diff --git a/source/spirv_reducer_options.cpp b/source/spirv_reducer_options.cpp index e8078753..9086433e 100644 --- a/source/spirv_reducer_options.cpp +++ b/source/spirv_reducer_options.cpp @@ -23,7 +23,9 @@ const uint32_t kDefaultStepLimit = 2500; } // namespace spv_reducer_options_t::spv_reducer_options_t() - : step_limit(kDefaultStepLimit), fail_on_validation_error(false) {} + : step_limit(kDefaultStepLimit), + fail_on_validation_error(false), + target_function(0) {} SPIRV_TOOLS_EXPORT spv_reducer_options spvReducerOptionsCreate() { return new spv_reducer_options_t(); @@ -42,3 +44,8 @@ SPIRV_TOOLS_EXPORT void spvReducerOptionsSetFailOnValidationError( spv_reducer_options options, bool fail_on_validation_error) { options->fail_on_validation_error = fail_on_validation_error; } + +SPIRV_TOOLS_EXPORT void spvReducerOptionsSetTargetFunction( + spv_reducer_options options, uint32_t target_function) { + options->target_function = target_function; +} diff --git a/source/spirv_reducer_options.h b/source/spirv_reducer_options.h index 1a431cce..911747dd 100644 --- a/source/spirv_reducer_options.h +++ b/source/spirv_reducer_options.h @@ -30,6 +30,9 @@ struct spv_reducer_options_t { // See spvReducerOptionsSetFailOnValidationError. bool fail_on_validation_error; + + // See spvReducerOptionsSetTargetFunction. + uint32_t target_function; }; #endif // SOURCE_SPIRV_REDUCER_OPTIONS_H_ diff --git a/test/reduce/conditional_branch_to_simple_conditional_branch_test.cpp b/test/reduce/conditional_branch_to_simple_conditional_branch_test.cpp index 69ef1f48..0e461140 100644 --- a/test/reduce/conditional_branch_to_simple_conditional_branch_test.cpp +++ b/test/reduce/conditional_branch_to_simple_conditional_branch_test.cpp @@ -80,7 +80,7 @@ TEST(ConditionalBranchToSimpleConditionalBranchTest, Diamond) { CheckValid(kEnv, context.get()); auto ops = ConditionalBranchToSimpleConditionalBranchOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(2, ops.size()); @@ -125,7 +125,7 @@ TEST(ConditionalBranchToSimpleConditionalBranchTest, Diamond) { } ops = ConditionalBranchToSimpleConditionalBranchOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(0, ops.size()); // Start again, and apply the other op. @@ -134,7 +134,7 @@ TEST(ConditionalBranchToSimpleConditionalBranchTest, Diamond) { CheckValid(kEnv, context.get()); ops = ConditionalBranchToSimpleConditionalBranchOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(2, ops.size()); ASSERT_TRUE(ops[0]->PreconditionHolds()); @@ -225,7 +225,7 @@ TEST(ConditionalBranchToSimpleConditionalBranchTest, AlreadySimplified) { CheckValid(kEnv, context.get()); auto ops = ConditionalBranchToSimpleConditionalBranchOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(0, ops.size()); } @@ -276,7 +276,7 @@ TEST(ConditionalBranchToSimpleConditionalBranchTest, DontRemoveBackEdge) { CheckValid(kEnv, context.get()); auto ops = ConditionalBranchToSimpleConditionalBranchOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(1, ops.size()); @@ -313,7 +313,7 @@ TEST(ConditionalBranchToSimpleConditionalBranchTest, DontRemoveBackEdge) { CheckEqual(kEnv, after, context.get()); ops = ConditionalBranchToSimpleConditionalBranchOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(0, ops.size()); } @@ -361,7 +361,7 @@ TEST(ConditionalBranchToSimpleConditionalBranchTest, CheckValid(kEnv, context.get()); auto ops = ConditionalBranchToSimpleConditionalBranchOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(1, ops.size()); @@ -396,7 +396,7 @@ TEST(ConditionalBranchToSimpleConditionalBranchTest, CheckEqual(kEnv, after, context.get()); ops = ConditionalBranchToSimpleConditionalBranchOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(0, ops.size()); } @@ -453,7 +453,7 @@ TEST(ConditionalBranchToSimpleConditionalBranchTest, BackEdgeUnreachable) { CheckValid(kEnv, context.get()); auto ops = ConditionalBranchToSimpleConditionalBranchOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(1, ops.size()); @@ -492,7 +492,7 @@ TEST(ConditionalBranchToSimpleConditionalBranchTest, BackEdgeUnreachable) { CheckEqual(kEnv, after, context.get()); ops = ConditionalBranchToSimpleConditionalBranchOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(0, ops.size()); } diff --git a/test/reduce/merge_blocks_test.cpp b/test/reduce/merge_blocks_test.cpp index dfb614ef..8506ee08 100644 --- a/test/reduce/merge_blocks_test.cpp +++ b/test/reduce/merge_blocks_test.cpp @@ -66,7 +66,7 @@ TEST(MergeBlocksReductionPassTest, BasicCheck) { BuildModule(env, consumer, shader, kReduceAssembleOption); const auto ops = MergeBlocksReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); ASSERT_EQ(5, ops.size()); // Try order 3, 0, 2, 4, 1 @@ -356,7 +356,7 @@ TEST(MergeBlocksReductionPassTest, Loops) { BuildModule(env, consumer, shader, kReduceAssembleOption); const auto ops = MergeBlocksReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); ASSERT_EQ(11, ops.size()); for (auto& ri : ops) { @@ -474,7 +474,7 @@ TEST(MergeBlocksReductionPassTest, MergeWithOpPhi) { BuildModule(env, consumer, shader, kReduceAssembleOption); const auto ops = MergeBlocksReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); ASSERT_EQ(1, ops.size()); ASSERT_TRUE(ops[0]->PreconditionHolds()); @@ -556,7 +556,7 @@ void MergeBlocksReductionPassTest_LoopReturn_Helper(bool reverse) { ASSERT_NE(context.get(), nullptr); auto opportunities = MergeBlocksReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); // A->B and B->C ASSERT_EQ(opportunities.size(), 2); diff --git a/test/reduce/operand_to_constant_test.cpp b/test/reduce/operand_to_constant_test.cpp index b2f67ee1..f44de51c 100644 --- a/test/reduce/operand_to_constant_test.cpp +++ b/test/reduce/operand_to_constant_test.cpp @@ -101,7 +101,7 @@ TEST(OperandToConstantReductionPassTest, BasicCheck) { BuildModule(env, consumer, original, kReduceAssembleOption); const auto ops = OperandToConstReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); ASSERT_EQ(17, ops.size()); ASSERT_TRUE(ops[0]->PreconditionHolds()); ops[0]->TryToApply(); @@ -151,10 +151,158 @@ TEST(OperandToConstantReductionPassTest, WithCalledFunction) { BuildModule(env, consumer, shader, kReduceAssembleOption); const auto ops = OperandToConstReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); ASSERT_EQ(0, ops.size()); } +TEST(OperandToConstantReductionPassTest, TargetSpecificFunction) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 320 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %8 = OpTypeFunction %6 %7 + %17 = OpConstant %6 1 + %20 = OpConstant %6 2 + %23 = OpConstant %6 0 + %24 = OpTypeBool + %35 = OpConstant %6 3 + %53 = OpConstant %6 10 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %65 = OpVariable %7 Function + %68 = OpVariable %7 Function + %73 = OpVariable %7 Function + OpStore %65 %35 + %66 = OpLoad %6 %65 + %67 = OpIAdd %6 %66 %17 + OpStore %65 %67 + %69 = OpLoad %6 %65 + OpStore %68 %69 + %70 = OpFunctionCall %6 %13 %68 + %71 = OpLoad %6 %65 + %72 = OpIAdd %6 %71 %70 + OpStore %65 %72 + %74 = OpLoad %6 %65 + OpStore %73 %74 + %75 = OpFunctionCall %6 %10 %73 + %76 = OpLoad %6 %65 + %77 = OpIAdd %6 %76 %75 + OpStore %65 %77 + OpReturn + OpFunctionEnd + %10 = OpFunction %6 None %8 + %9 = OpFunctionParameter %7 + %11 = OpLabel + %15 = OpVariable %7 Function + %16 = OpLoad %6 %9 + %18 = OpIAdd %6 %16 %17 + OpStore %15 %18 + %19 = OpLoad %6 %15 + %21 = OpIAdd %6 %19 %20 + OpStore %15 %21 + %22 = OpLoad %6 %15 + %25 = OpSGreaterThan %24 %22 %23 + OpSelectionMerge %27 None + OpBranchConditional %25 %26 %27 + %26 = OpLabel + %28 = OpLoad %6 %9 + OpReturnValue %28 + %27 = OpLabel + %30 = OpLoad %6 %9 + %31 = OpIAdd %6 %30 %17 + OpReturnValue %31 + OpFunctionEnd + %13 = OpFunction %6 None %8 + %12 = OpFunctionParameter %7 + %14 = OpLabel + %41 = OpVariable %7 Function + %46 = OpVariable %7 Function + %55 = OpVariable %7 Function + %34 = OpLoad %6 %12 + %36 = OpIEqual %24 %34 %35 + OpSelectionMerge %38 None + OpBranchConditional %36 %37 %38 + %37 = OpLabel + %39 = OpLoad %6 %12 + %40 = OpIMul %6 %20 %39 + OpStore %41 %40 + %42 = OpFunctionCall %6 %10 %41 + OpReturnValue %42 + %38 = OpLabel + %44 = OpLoad %6 %12 + %45 = OpIAdd %6 %44 %17 + OpStore %12 %45 + OpStore %46 %23 + OpBranch %47 + %47 = OpLabel + OpLoopMerge %49 %50 None + OpBranch %51 + %51 = OpLabel + %52 = OpLoad %6 %46 + %54 = OpSLessThan %24 %52 %53 + OpBranchConditional %54 %48 %49 + %48 = OpLabel + %56 = OpLoad %6 %12 + OpStore %55 %56 + %57 = OpFunctionCall %6 %10 %55 + %58 = OpLoad %6 %12 + %59 = OpIAdd %6 %58 %57 + OpStore %12 %59 + OpBranch %50 + %50 = OpLabel + %60 = OpLoad %6 %46 + %61 = OpIAdd %6 %60 %17 + OpStore %46 %61 + OpBranch %47 + %49 = OpLabel + %62 = OpLoad %6 %12 + OpReturnValue %62 + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = + BuildModule(env, consumer, shader, kReduceAssembleOption); + + // Targeting all functions, there are quite a few opportunities. To avoid + // making the test too sensitive, we check that there are more than a number + // somewhat lower than the real number. + const auto all_ops = + OperandToConstReductionOpportunityFinder().GetAvailableOpportunities( + context.get(), 0); + ASSERT_TRUE(all_ops.size() > 100); + + // Targeting individual functions, there are fewer opportunities. Again, we + // avoid checking against an exact number so that the test is not too + // sensitive. + const auto ops_for_function_4 = + OperandToConstReductionOpportunityFinder().GetAvailableOpportunities( + context.get(), 4); + const auto ops_for_function_10 = + OperandToConstReductionOpportunityFinder().GetAvailableOpportunities( + context.get(), 10); + const auto ops_for_function_13 = + OperandToConstReductionOpportunityFinder().GetAvailableOpportunities( + context.get(), 13); + ASSERT_TRUE(ops_for_function_4.size() < 60); + ASSERT_TRUE(ops_for_function_10.size() < 50); + ASSERT_TRUE(ops_for_function_13.size() < 80); + + // The total number of opportunities should be the sum of the per-function + // opportunities. + ASSERT_EQ(all_ops.size(), ops_for_function_4.size() + + ops_for_function_10.size() + + ops_for_function_13.size()); +} + } // namespace } // namespace reduce } // namespace spvtools diff --git a/test/reduce/operand_to_dominating_id_test.cpp b/test/reduce/operand_to_dominating_id_test.cpp index cd5b2c68..697c5cb8 100644 --- a/test/reduce/operand_to_dominating_id_test.cpp +++ b/test/reduce/operand_to_dominating_id_test.cpp @@ -56,7 +56,7 @@ TEST(OperandToDominatingIdReductionPassTest, BasicCheck) { const auto context = BuildModule(env, consumer, original, kReduceAssembleOption); const auto ops = OperandToDominatingIdReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(10, ops.size()); ASSERT_TRUE(ops[0]->PreconditionHolds()); ops[0]->TryToApply(); diff --git a/test/reduce/operand_to_undef_test.cpp b/test/reduce/operand_to_undef_test.cpp index fa64bd53..41974275 100644 --- a/test/reduce/operand_to_undef_test.cpp +++ b/test/reduce/operand_to_undef_test.cpp @@ -167,7 +167,7 @@ TEST(OperandToUndefReductionPassTest, BasicCheck) { BuildModule(env, consumer, original, kReduceAssembleOption); const auto ops = OperandToUndefReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); ASSERT_EQ(10, ops.size()); @@ -221,7 +221,7 @@ TEST(OperandToUndefReductionPassTest, WithCalledFunction) { BuildModule(env, consumer, shader, kReduceAssembleOption); const auto ops = OperandToUndefReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); ASSERT_EQ(0, ops.size()); } diff --git a/test/reduce/reducer_test.cpp b/test/reduce/reducer_test.cpp index 0de5af1d..276aedc8 100644 --- a/test/reduce/reducer_test.cpp +++ b/test/reduce/reducer_test.cpp @@ -14,6 +14,8 @@ #include "source/reduce/reducer.h" +#include + #include "source/opt/build_module.h" #include "source/reduce/operand_to_const_reduction_opportunity_finder.h" #include "source/reduce/remove_unused_instruction_reduction_opportunity_finder.h" @@ -23,11 +25,8 @@ namespace spvtools { namespace reduce { namespace { -using opt::BasicBlock; -using opt::IRContext; - const spv_target_env kEnv = SPV_ENV_UNIVERSAL_1_3; -const MessageConsumer kMessageConsumer = CLIMessageConsumer; +const MessageConsumer kMessageConsumer = NopDiagnostic; // This changes its mind each time IsInteresting is invoked as to whether the // binary is interesting, until some limit is reached after which the binary is @@ -42,7 +41,7 @@ class PingPongInteresting { always_interesting_after_(always_interesting_after), count_(0) {} - bool IsInteresting(const std::vector&) { + bool IsInteresting() { bool result; if (count_ > always_interesting_after_) { result = true; @@ -194,10 +193,10 @@ TEST(ReducerTest, ExprToConstantAndRemoveUnreferenced) { Reducer reducer(kEnv); PingPongInteresting ping_pong_interesting(10); - reducer.SetMessageConsumer(NopDiagnostic); + reducer.SetMessageConsumer(kMessageConsumer); reducer.SetInterestingnessFunction( - [&](const std::vector& binary, uint32_t) -> bool { - return ping_pong_interesting.IsInteresting(binary); + [&ping_pong_interesting](const std::vector&, uint32_t) -> bool { + return ping_pong_interesting.IsInteresting(); }); reducer.AddReductionPass( MakeUnique(false)); @@ -230,13 +229,14 @@ bool InterestingWhileOpcodeExists(const std::vector& binary, DumpShader(binary, ss.str().c_str()); } - std::unique_ptr context = + std::unique_ptr context = BuildModule(kEnv, kMessageConsumer, binary.data(), binary.size()); assert(context); bool interesting = false; for (auto& function : *context->module()) { context->cfg()->ForEachBlockInPostOrder( - &*function.begin(), [opcode, &interesting](BasicBlock* block) -> void { + &*function.begin(), + [opcode, &interesting](opt::BasicBlock* block) -> void { for (auto& inst : *block) { if (inst.opcode() == opcode) { interesting = true; @@ -369,6 +369,147 @@ const std::string kShaderWithLoopsDivAndMul = R"( OpFunctionEnd )"; +// The shader below comes from the following GLSL. +// #version 320 es +// +// int baz(int x) { +// int y = x + 1; +// y = y + 2; +// if (y > 0) { +// return x; +// } +// return x + 1; +// } +// +// int bar(int a) { +// if (a == 3) { +// return baz(2*a); +// } +// a = a + 1; +// for (int i = 0; i < 10; i++) { +// a += baz(a); +// } +// return a; +// } +// +// void main() { +// int x; +// x = 3; +// x += 1; +// x += bar(x); +// x += baz(x); +// } +const std::string kShaderWithMultipleFunctions = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 320 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %8 = OpTypeFunction %6 %7 + %17 = OpConstant %6 1 + %20 = OpConstant %6 2 + %23 = OpConstant %6 0 + %24 = OpTypeBool + %35 = OpConstant %6 3 + %53 = OpConstant %6 10 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %65 = OpVariable %7 Function + %68 = OpVariable %7 Function + %73 = OpVariable %7 Function + OpStore %65 %35 + %66 = OpLoad %6 %65 + %67 = OpIAdd %6 %66 %17 + OpStore %65 %67 + %69 = OpLoad %6 %65 + OpStore %68 %69 + %70 = OpFunctionCall %6 %13 %68 + %71 = OpLoad %6 %65 + %72 = OpIAdd %6 %71 %70 + OpStore %65 %72 + %74 = OpLoad %6 %65 + OpStore %73 %74 + %75 = OpFunctionCall %6 %10 %73 + %76 = OpLoad %6 %65 + %77 = OpIAdd %6 %76 %75 + OpStore %65 %77 + OpReturn + OpFunctionEnd + %10 = OpFunction %6 None %8 + %9 = OpFunctionParameter %7 + %11 = OpLabel + %15 = OpVariable %7 Function + %16 = OpLoad %6 %9 + %18 = OpIAdd %6 %16 %17 + OpStore %15 %18 + %19 = OpLoad %6 %15 + %21 = OpIAdd %6 %19 %20 + OpStore %15 %21 + %22 = OpLoad %6 %15 + %25 = OpSGreaterThan %24 %22 %23 + OpSelectionMerge %27 None + OpBranchConditional %25 %26 %27 + %26 = OpLabel + %28 = OpLoad %6 %9 + OpReturnValue %28 + %27 = OpLabel + %30 = OpLoad %6 %9 + %31 = OpIAdd %6 %30 %17 + OpReturnValue %31 + OpFunctionEnd + %13 = OpFunction %6 None %8 + %12 = OpFunctionParameter %7 + %14 = OpLabel + %41 = OpVariable %7 Function + %46 = OpVariable %7 Function + %55 = OpVariable %7 Function + %34 = OpLoad %6 %12 + %36 = OpIEqual %24 %34 %35 + OpSelectionMerge %38 None + OpBranchConditional %36 %37 %38 + %37 = OpLabel + %39 = OpLoad %6 %12 + %40 = OpIMul %6 %20 %39 + OpStore %41 %40 + %42 = OpFunctionCall %6 %10 %41 + OpReturnValue %42 + %38 = OpLabel + %44 = OpLoad %6 %12 + %45 = OpIAdd %6 %44 %17 + OpStore %12 %45 + OpStore %46 %23 + OpBranch %47 + %47 = OpLabel + OpLoopMerge %49 %50 None + OpBranch %51 + %51 = OpLabel + %52 = OpLoad %6 %46 + %54 = OpSLessThan %24 %52 %53 + OpBranchConditional %54 %48 %49 + %48 = OpLabel + %56 = OpLoad %6 %12 + OpStore %55 %56 + %57 = OpFunctionCall %6 %10 %55 + %58 = OpLoad %6 %12 + %59 = OpIAdd %6 %58 %57 + OpStore %12 %59 + OpBranch %50 + %50 = OpLabel + %60 = OpLoad %6 %46 + %61 = OpIAdd %6 %60 %17 + OpStore %46 %61 + OpBranch %47 + %49 = OpLabel + %62 = OpLoad %6 %12 + OpReturnValue %62 + OpFunctionEnd + )"; + TEST(ReducerTest, ShaderReduceWhileMulReachable) { Reducer reducer(kEnv); @@ -417,6 +558,70 @@ TEST(ReducerTest, ShaderReduceWhileDivReachable) { ASSERT_EQ(status, Reducer::ReductionResultStatus::kComplete); } +// Computes an instruction count for each function in the module represented by +// |binary|. +std::unordered_map GetFunctionInstructionCount( + const std::vector& binary) { + std::unique_ptr context = + BuildModule(kEnv, kMessageConsumer, binary.data(), binary.size()); + assert(context != nullptr && "Failed to build module."); + std::unordered_map result; + for (auto& function : *context->module()) { + uint32_t& count = result[function.result_id()] = 0; + function.ForEachInst([&count](opt::Instruction*) { count++; }); + } + return result; +} + +TEST(ReducerTest, SingleFunctionReduction) { + Reducer reducer(kEnv); + + PingPongInteresting ping_pong_interesting(4); + reducer.SetInterestingnessFunction( + [&ping_pong_interesting](const std::vector&, uint32_t) -> bool { + return ping_pong_interesting.IsInteresting(); + }); + reducer.AddDefaultReductionPasses(); + reducer.SetMessageConsumer(kMessageConsumer); + + std::vector binary_in; + SpirvTools t(kEnv); + + ASSERT_TRUE(t.Assemble(kShaderWithMultipleFunctions, &binary_in, + kReduceAssembleOption)); + + auto original_instruction_count = GetFunctionInstructionCount(binary_in); + + std::vector binary_out; + spvtools::ReducerOptions reducer_options; + reducer_options.set_step_limit(500); + reducer_options.set_fail_on_validation_error(true); + + // Instruct the reducer to only target function 13. + reducer_options.set_target_function(13); + + spvtools::ValidatorOptions validator_options; + + Reducer::ReductionResultStatus status = reducer.Run( + std::move(binary_in), &binary_out, reducer_options, validator_options); + + ASSERT_EQ(status, Reducer::ReductionResultStatus::kComplete); + + auto final_instruction_count = GetFunctionInstructionCount(binary_out); + + // Nothing should have been removed from these functions. + ASSERT_EQ(original_instruction_count.at(4), final_instruction_count.at(4)); + ASSERT_EQ(original_instruction_count.at(10), final_instruction_count.at(10)); + + // Function 13 should have been reduced to these five instructions: + // OpFunction + // OpFunctionParameter + // OpLabel + // OpReturnValue + // OpFunctionEnd + ASSERT_EQ(5, final_instruction_count.at(13)); +} + } // namespace } // namespace reduce } // namespace spvtools diff --git a/test/reduce/remove_block_test.cpp b/test/reduce/remove_block_test.cpp index f31cc9da..2500d0c9 100644 --- a/test/reduce/remove_block_test.cpp +++ b/test/reduce/remove_block_test.cpp @@ -66,7 +66,7 @@ TEST(RemoveBlockReductionPassTest, BasicCheck) { BuildModule(env, consumer, shader, kReduceAssembleOption); const auto ops = RemoveBlockReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); ASSERT_EQ(2, ops.size()); ASSERT_TRUE(ops[0]->PreconditionHolds()); @@ -181,7 +181,7 @@ TEST(RemoveBlockReductionPassTest, UnreachableContinueAndMerge) { BuildModule(env, consumer, shader, kReduceAssembleOption); const auto ops = RemoveBlockReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); ASSERT_EQ(0, ops.size()); } @@ -209,7 +209,7 @@ TEST(RemoveBlockReductionPassTest, OneBlock) { BuildModule(env, consumer, shader, kReduceAssembleOption); const auto ops = RemoveBlockReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); ASSERT_EQ(0, ops.size()); } @@ -247,7 +247,7 @@ TEST(RemoveBlockReductionPassTest, UnreachableBlocksWithOutsideIdUses) { BuildModule(env, consumer, shader, kReduceAssembleOption); const auto ops = RemoveBlockReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); ASSERT_EQ(0, ops.size()); } @@ -287,7 +287,7 @@ TEST(RemoveBlockReductionPassTest, UnreachableBlocksWithInsideIdUses) { const auto context = BuildModule(env, consumer, shader, kReduceAssembleOption); auto ops = RemoveBlockReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); ASSERT_EQ(1, ops.size()); ASSERT_TRUE(ops[0]->PreconditionHolds()); @@ -323,7 +323,7 @@ TEST(RemoveBlockReductionPassTest, UnreachableBlocksWithInsideIdUses) { // removed. ops = RemoveBlockReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); ASSERT_EQ(1, ops.size()); diff --git a/test/reduce/remove_function_test.cpp b/test/reduce/remove_function_test.cpp index 576b6031..e293f4ec 100644 --- a/test/reduce/remove_function_test.cpp +++ b/test/reduce/remove_function_test.cpp @@ -67,7 +67,7 @@ TEST(RemoveFunctionTest, BasicCheck) { auto ops = RemoveFunctionReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); ASSERT_EQ(1, ops.size()); ASSERT_TRUE(ops[0]->PreconditionHolds()); @@ -97,7 +97,7 @@ TEST(RemoveFunctionTest, BasicCheck) { CheckEqual(env, after_first, context.get()); ops = RemoveFunctionReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); ASSERT_EQ(1, ops.size()); @@ -156,7 +156,7 @@ TEST(RemoveFunctionTest, NothingToRemove) { BuildModule(env, consumer, shader, kReduceAssembleOption); auto ops = RemoveFunctionReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); ASSERT_EQ(0, ops.size()); } @@ -193,7 +193,7 @@ TEST(RemoveFunctionTest, TwoRemovableFunctions) { auto ops = RemoveFunctionReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); ASSERT_EQ(2, ops.size()); ASSERT_TRUE(ops[0]->PreconditionHolds()); @@ -254,7 +254,7 @@ TEST(RemoveFunctionTest, NoRemovalsDueToOpName) { BuildModule(env, consumer, shader, kReduceAssembleOption); auto ops = RemoveFunctionReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); ASSERT_EQ(0, ops.size()); } @@ -286,7 +286,7 @@ TEST(RemoveFunctionTest, NoRemovalDueToLinkageDecoration) { BuildModule(env, consumer, shader, kReduceAssembleOption); auto ops = RemoveFunctionReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); ASSERT_EQ(0, ops.size()); } diff --git a/test/reduce/remove_selection_test.cpp b/test/reduce/remove_selection_test.cpp index f8acd5d4..2921bbeb 100644 --- a/test/reduce/remove_selection_test.cpp +++ b/test/reduce/remove_selection_test.cpp @@ -62,7 +62,7 @@ TEST(RemoveSelectionTest, OpportunityBecauseSameTargetBlock) { auto ops = RemoveSelectionReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); ASSERT_EQ(1, ops.size()); @@ -96,7 +96,7 @@ TEST(RemoveSelectionTest, OpportunityBecauseSameTargetBlock) { CheckEqual(env, after, context.get()); ops = RemoveSelectionReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); ASSERT_EQ(0, ops.size()); } @@ -136,7 +136,7 @@ TEST(RemoveSelectionTest, OpportunityBecauseSameTargetBlockMerge) { auto ops = RemoveSelectionReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); ASSERT_EQ(1, ops.size()); @@ -168,7 +168,7 @@ TEST(RemoveSelectionTest, OpportunityBecauseSameTargetBlockMerge) { CheckEqual(env, after, context.get()); ops = RemoveSelectionReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); ASSERT_EQ(0, ops.size()); } @@ -212,7 +212,7 @@ TEST(RemoveSelectionTest, NoOpportunityBecauseDifferentTargetBlocksOneMerge) { auto ops = RemoveSelectionReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); ASSERT_EQ(0, ops.size()); } @@ -258,7 +258,7 @@ TEST(RemoveSelectionTest, NoOpportunityBecauseDifferentTargetBlocks) { auto ops = RemoveSelectionReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); ASSERT_EQ(0, ops.size()); } @@ -306,7 +306,7 @@ TEST(RemoveSelectionTest, NoOpportunityBecauseMergeUsed) { auto ops = RemoveSelectionReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); ASSERT_EQ(0, ops.size()); } @@ -384,7 +384,7 @@ TEST(RemoveSelectionTest, OpportunityBecauseLoopMergeUsed) { auto ops = RemoveSelectionReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); ASSERT_EQ(1, ops.size()); @@ -427,7 +427,7 @@ TEST(RemoveSelectionTest, OpportunityBecauseLoopMergeUsed) { CheckEqual(env, after, context.get()); ops = RemoveSelectionReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); ASSERT_EQ(0, ops.size()); } @@ -505,7 +505,7 @@ TEST(RemoveSelectionTest, OpportunityBecauseLoopContinueUsed) { auto ops = RemoveSelectionReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); ASSERT_EQ(1, ops.size()); @@ -548,7 +548,7 @@ TEST(RemoveSelectionTest, OpportunityBecauseLoopContinueUsed) { CheckEqual(env, after, context.get()); ops = RemoveSelectionReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); ASSERT_EQ(0, ops.size()); } diff --git a/test/reduce/remove_unused_instruction_test.cpp b/test/reduce/remove_unused_instruction_test.cpp index 68bc6014..eb548e11 100644 --- a/test/reduce/remove_unused_instruction_test.cpp +++ b/test/reduce/remove_unused_instruction_test.cpp @@ -72,7 +72,7 @@ TEST(RemoveUnusedInstructionReductionPassTest, RemoveStores) { CheckValid(kEnv, context.get()); - auto ops = finder.GetAvailableOpportunities(context.get()); + auto ops = finder.GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(10, ops.size()); @@ -108,7 +108,7 @@ TEST(RemoveUnusedInstructionReductionPassTest, RemoveStores) { CheckEqual(kEnv, step_2, context.get()); - ops = finder.GetAvailableOpportunities(context.get()); + ops = finder.GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(7, ops.size()); @@ -137,7 +137,7 @@ TEST(RemoveUnusedInstructionReductionPassTest, RemoveStores) { CheckEqual(kEnv, step_3, context.get()); - ops = finder.GetAvailableOpportunities(context.get()); + ops = finder.GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(1, ops.size()); @@ -165,7 +165,7 @@ TEST(RemoveUnusedInstructionReductionPassTest, RemoveStores) { CheckEqual(kEnv, step_4, context.get()); - ops = finder.GetAvailableOpportunities(context.get()); + ops = finder.GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(1, ops.size()); @@ -192,7 +192,7 @@ TEST(RemoveUnusedInstructionReductionPassTest, RemoveStores) { CheckEqual(kEnv, step_5, context.get()); - ops = finder.GetAvailableOpportunities(context.get()); + ops = finder.GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(1, ops.size()); @@ -218,7 +218,7 @@ TEST(RemoveUnusedInstructionReductionPassTest, RemoveStores) { CheckEqual(kEnv, step_6, context.get()); - ops = finder.GetAvailableOpportunities(context.get()); + ops = finder.GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(0, ops.size()); } @@ -258,7 +258,7 @@ TEST(RemoveUnusedInstructionReductionPassTest, Referenced) { CheckValid(kEnv, context.get()); - auto ops = finder.GetAvailableOpportunities(context.get()); + auto ops = finder.GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(6, ops.size()); @@ -289,7 +289,7 @@ TEST(RemoveUnusedInstructionReductionPassTest, Referenced) { CheckEqual(kEnv, after, context.get()); - ops = finder.GetAvailableOpportunities(context.get()); + ops = finder.GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(3, ops.size()); @@ -317,7 +317,7 @@ TEST(RemoveUnusedInstructionReductionPassTest, Referenced) { CheckEqual(kEnv, after_2, context.get()); - ops = finder.GetAvailableOpportunities(context.get()); + ops = finder.GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(1, ops.size()); @@ -344,7 +344,7 @@ TEST(RemoveUnusedInstructionReductionPassTest, Referenced) { CheckEqual(kEnv, after_3, context.get()); - ops = finder.GetAvailableOpportunities(context.get()); + ops = finder.GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(1, ops.size()); @@ -370,7 +370,7 @@ TEST(RemoveUnusedInstructionReductionPassTest, Referenced) { CheckEqual(kEnv, after_4, context.get()); - ops = finder.GetAvailableOpportunities(context.get()); + ops = finder.GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(0, ops.size()); } @@ -438,7 +438,7 @@ TEST(RemoveUnusedResourceVariableTest, RemoveUnusedResourceVariables) { BuildModule(env, consumer, shader, kReduceAssembleOption); auto ops = RemoveUnusedInstructionReductionOpportunityFinder(true) - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(7, ops.size()); for (auto& op : ops) { @@ -487,7 +487,7 @@ TEST(RemoveUnusedResourceVariableTest, RemoveUnusedResourceVariables) { CheckEqual(env, expected_1, context.get()); ops = RemoveUnusedInstructionReductionOpportunityFinder(true) - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(6, ops.size()); for (auto& op : ops) { @@ -530,7 +530,7 @@ TEST(RemoveUnusedResourceVariableTest, RemoveUnusedResourceVariables) { CheckEqual(env, expected_2, context.get()); ops = RemoveUnusedInstructionReductionOpportunityFinder(true) - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(6, ops.size()); for (auto& op : ops) { diff --git a/test/reduce/remove_unused_struct_member_test.cpp b/test/reduce/remove_unused_struct_member_test.cpp index 402ef2d8..d3c1487d 100644 --- a/test/reduce/remove_unused_struct_member_test.cpp +++ b/test/reduce/remove_unused_struct_member_test.cpp @@ -61,7 +61,7 @@ TEST(RemoveUnusedStructMemberTest, RemoveOneMember) { BuildModule(env, consumer, shader, kReduceAssembleOption); auto ops = RemoveUnusedStructMemberReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(1, ops.size()); ASSERT_TRUE(ops[0]->PreconditionHolds()); ops[0]->TryToApply(); @@ -143,7 +143,7 @@ TEST(RemoveUnusedStructMemberTest, RemoveUniformBufferMember) { BuildModule(env, consumer, shader, kReduceAssembleOption); auto ops = RemoveUnusedStructMemberReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(1, ops.size()); ASSERT_TRUE(ops[0]->PreconditionHolds()); ops[0]->TryToApply(); @@ -229,7 +229,7 @@ TEST(RemoveUnusedStructMemberTest, DoNotRemoveNamedMemberRemoveOneMember) { BuildModule(env, consumer, shader, kReduceAssembleOption); auto ops = RemoveUnusedStructMemberReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(0, ops.size()); } diff --git a/test/reduce/simple_conditional_branch_to_branch_test.cpp b/test/reduce/simple_conditional_branch_to_branch_test.cpp index d55e6910..fcc9d721 100644 --- a/test/reduce/simple_conditional_branch_to_branch_test.cpp +++ b/test/reduce/simple_conditional_branch_to_branch_test.cpp @@ -73,7 +73,7 @@ TEST(SimpleConditionalBranchToBranchTest, Diamond) { CheckValid(kEnv, context.get()); auto ops = SimpleConditionalBranchToBranchOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(0, ops.size()); } @@ -122,7 +122,7 @@ TEST(SimpleConditionalBranchToBranchTest, DiamondNoSelection) { CheckValid(kEnv, context.get()); auto ops = SimpleConditionalBranchToBranchOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(1, ops.size()); @@ -161,7 +161,7 @@ TEST(SimpleConditionalBranchToBranchTest, DiamondNoSelection) { CheckEqual(kEnv, after, context.get()); ops = SimpleConditionalBranchToBranchOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(0, ops.size()); } @@ -217,7 +217,7 @@ TEST(SimpleConditionalBranchToBranchTest, ConditionalBranchesButNotSimple) { CheckValid(kEnv, context.get()); auto ops = SimpleConditionalBranchToBranchOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(0, ops.size()); } @@ -266,7 +266,7 @@ TEST(SimpleConditionalBranchToBranchTest, SimplifyBackEdge) { CheckValid(kEnv, context.get()); auto ops = SimpleConditionalBranchToBranchOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(1, ops.size()); @@ -303,7 +303,7 @@ TEST(SimpleConditionalBranchToBranchTest, SimplifyBackEdge) { CheckEqual(kEnv, after, context.get()); ops = SimpleConditionalBranchToBranchOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(0, ops.size()); } @@ -349,7 +349,7 @@ TEST(SimpleConditionalBranchToBranchTest, CheckValid(kEnv, context.get()); auto ops = SimpleConditionalBranchToBranchOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(1, ops.size()); @@ -384,7 +384,7 @@ TEST(SimpleConditionalBranchToBranchTest, CheckEqual(kEnv, after, context.get()); ops = SimpleConditionalBranchToBranchOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(0, ops.size()); } @@ -438,7 +438,7 @@ TEST(SimpleConditionalBranchToBranchTest, BackEdgeUnreachable) { CheckValid(kEnv, context.get()); auto ops = SimpleConditionalBranchToBranchOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(1, ops.size()); @@ -477,7 +477,7 @@ TEST(SimpleConditionalBranchToBranchTest, BackEdgeUnreachable) { CheckEqual(kEnv, after, context.get()); ops = SimpleConditionalBranchToBranchOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(0, ops.size()); } diff --git a/test/reduce/structured_loop_to_selection_test.cpp b/test/reduce/structured_loop_to_selection_test.cpp index 95b5f4f1..0cfcfdff 100644 --- a/test/reduce/structured_loop_to_selection_test.cpp +++ b/test/reduce/structured_loop_to_selection_test.cpp @@ -65,7 +65,7 @@ TEST(StructuredLoopToSelectionReductionPassTest, LoopyShader1) { const auto env = SPV_ENV_UNIVERSAL_1_3; const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); const auto ops = StructuredLoopToSelectionReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(1, ops.size()); ASSERT_TRUE(ops[0]->PreconditionHolds()); @@ -211,7 +211,7 @@ TEST(StructuredLoopToSelectionReductionPassTest, LoopyShader2) { const auto env = SPV_ENV_UNIVERSAL_1_3; const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); const auto ops = StructuredLoopToSelectionReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(4, ops.size()); ASSERT_TRUE(ops[0]->PreconditionHolds()); @@ -680,7 +680,7 @@ TEST(StructuredLoopToSelectionReductionPassTest, LoopyShader3) { const auto env = SPV_ENV_UNIVERSAL_1_3; const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); const auto ops = StructuredLoopToSelectionReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(0, ops.size()); } @@ -758,7 +758,7 @@ TEST(StructuredLoopToSelectionReductionPassTest, LoopyShader4) { const auto env = SPV_ENV_UNIVERSAL_1_3; const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); const auto ops = StructuredLoopToSelectionReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); // Initially there are two opportunities. ASSERT_EQ(2, ops.size()); @@ -881,7 +881,7 @@ TEST(StructuredLoopToSelectionReductionPassTest, ConditionalBreak1) { const auto env = SPV_ENV_UNIVERSAL_1_3; const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); const auto ops = StructuredLoopToSelectionReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(1, ops.size()); ASSERT_TRUE(ops[0]->PreconditionHolds()); @@ -956,7 +956,7 @@ TEST(StructuredLoopToSelectionReductionPassTest, ConditionalBreak2) { const auto env = SPV_ENV_UNIVERSAL_1_3; const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); const auto ops = StructuredLoopToSelectionReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(1, ops.size()); ASSERT_TRUE(ops[0]->PreconditionHolds()); @@ -1024,7 +1024,7 @@ TEST(StructuredLoopToSelectionReductionPassTest, UnconditionalBreak) { const auto env = SPV_ENV_UNIVERSAL_1_3; const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); const auto ops = StructuredLoopToSelectionReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(1, ops.size()); ASSERT_TRUE(ops[0]->PreconditionHolds()); @@ -1224,7 +1224,7 @@ TEST(StructuredLoopToSelectionReductionPassTest, Complex) { const auto env = SPV_ENV_UNIVERSAL_1_3; const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); const auto ops = StructuredLoopToSelectionReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(2, ops.size()); ASSERT_TRUE(ops[0]->PreconditionHolds()); @@ -1691,7 +1691,7 @@ TEST(StructuredLoopToSelectionReductionPassTest, ComplexOptimized) { const auto env = SPV_ENV_UNIVERSAL_1_3; const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); const auto ops = StructuredLoopToSelectionReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(2, ops.size()); ASSERT_TRUE(ops[0]->PreconditionHolds()); @@ -2008,7 +2008,7 @@ TEST(StructuredLoopToSelectionReductionPassTest, DominanceIssue) { const auto env = SPV_ENV_UNIVERSAL_1_3; const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); const auto ops = StructuredLoopToSelectionReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(1, ops.size()); ASSERT_TRUE(ops[0]->PreconditionHolds()); @@ -2156,7 +2156,7 @@ TEST(StructuredLoopToSelectionReductionPassTest, AccessChainIssue) { const auto env = SPV_ENV_UNIVERSAL_1_3; const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); const auto ops = StructuredLoopToSelectionReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(1, ops.size()); ASSERT_TRUE(ops[0]->PreconditionHolds()); @@ -2313,7 +2313,7 @@ TEST(StructuredLoopToSelectionReductionPassTest, DominanceAndPhiIssue) { const auto env = SPV_ENV_UNIVERSAL_1_3; const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); const auto ops = StructuredLoopToSelectionReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(1, ops.size()); ASSERT_TRUE(ops[0]->PreconditionHolds()); @@ -2421,7 +2421,7 @@ TEST(StructuredLoopToSelectionReductionPassTest, OpLineBeforeOpPhi) { const auto env = SPV_ENV_UNIVERSAL_1_3; const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); const auto ops = StructuredLoopToSelectionReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(1, ops.size()); ASSERT_TRUE(ops[0]->PreconditionHolds()); @@ -2511,7 +2511,7 @@ TEST(StructuredLoopToSelectionReductionPassTest, const auto env = SPV_ENV_UNIVERSAL_1_3; const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); const auto ops = StructuredLoopToSelectionReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); // There should be no opportunities. ASSERT_EQ(0, ops.size()); @@ -2555,7 +2555,7 @@ TEST(StructuredLoopToSelectionReductionPassTest, const auto env = SPV_ENV_UNIVERSAL_1_3; const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); const auto ops = StructuredLoopToSelectionReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); // There should be no opportunities. ASSERT_EQ(0, ops.size()); @@ -2595,7 +2595,7 @@ TEST(StructuredLoopToSelectionReductionPassTest, ContinueTargetIsSwitchTarget) { const auto env = SPV_ENV_UNIVERSAL_1_3; const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); const auto ops = StructuredLoopToSelectionReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(1, ops.size()); ASSERT_TRUE(ops[0]->PreconditionHolds()); @@ -2670,7 +2670,7 @@ TEST(StructuredLoopToSelectionReductionPassTest, const auto env = SPV_ENV_UNIVERSAL_1_3; const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); const auto ops = StructuredLoopToSelectionReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(1, ops.size()); ASSERT_TRUE(ops[0]->PreconditionHolds()); @@ -2733,7 +2733,7 @@ TEST(StructuredLoopToSelectionReductionPassTest, LoopBranchesStraightToMerge) { const auto env = SPV_ENV_UNIVERSAL_1_3; const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); const auto ops = StructuredLoopToSelectionReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(1, ops.size()); ASSERT_TRUE(ops[0]->PreconditionHolds()); @@ -2790,7 +2790,7 @@ TEST(StructuredLoopToSelectionReductionPassTest, const auto env = SPV_ENV_UNIVERSAL_1_3; const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); const auto ops = StructuredLoopToSelectionReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(1, ops.size()); ASSERT_TRUE(ops[0]->PreconditionHolds()); @@ -2874,7 +2874,7 @@ TEST(StructuredLoopToSelectionReductionPassTest, MultipleAccessChains) { const auto env = SPV_ENV_UNIVERSAL_1_3; const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); const auto ops = StructuredLoopToSelectionReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(1, ops.size()); ASSERT_TRUE(ops[0]->PreconditionHolds()); @@ -2970,7 +2970,7 @@ TEST(StructuredLoopToSelectionReductionPassTest, const auto env = SPV_ENV_UNIVERSAL_1_3; const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); const auto ops = StructuredLoopToSelectionReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(2, ops.size()); ASSERT_TRUE(ops[0]->PreconditionHolds()); @@ -3089,7 +3089,7 @@ TEST(StructuredLoopToSelectionReductionPassTest, const auto env = SPV_ENV_UNIVERSAL_1_3; const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); const auto ops = StructuredLoopToSelectionReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(2, ops.size()); ASSERT_TRUE(ops[0]->PreconditionHolds()); @@ -3209,7 +3209,7 @@ TEST(StructuredLoopToSelectionReductionPassTest, const auto env = SPV_ENV_UNIVERSAL_1_3; const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); auto ops = StructuredLoopToSelectionReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); // We cannot transform the inner loop due to its header jumping straight to // the outer loop merge (the inner loop's merge does not post-dominate its @@ -3254,7 +3254,7 @@ TEST(StructuredLoopToSelectionReductionPassTest, // Now look again for more opportunities. ops = StructuredLoopToSelectionReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); // What was the inner loop should now be transformable, as the jump to the // outer loop's merge has been redirected. @@ -3422,7 +3422,7 @@ TEST(StructuredLoopToSelectionReductionPassTest, LongAccessChains) { const auto env = SPV_ENV_UNIVERSAL_1_3; const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); auto ops = StructuredLoopToSelectionReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(1, ops.size()); ASSERT_TRUE(ops[0]->PreconditionHolds()); @@ -3513,7 +3513,7 @@ TEST(StructuredLoopToSelectionReductionPassTest, LoopyShaderWithOpDecorate) { const auto env = SPV_ENV_UNIVERSAL_1_3; const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); const auto ops = StructuredLoopToSelectionReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(1, ops.size()); ASSERT_TRUE(ops[0]->PreconditionHolds()); @@ -3619,7 +3619,7 @@ TEST(StructuredLoopToSelectionReductionPassTest, const auto env = SPV_ENV_UNIVERSAL_1_3; const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); const auto ops = StructuredLoopToSelectionReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(0, ops.size()); } diff --git a/test/reduce/validation_during_reduction_test.cpp b/test/reduce/validation_during_reduction_test.cpp index 9790eac0..d8643449 100644 --- a/test/reduce/validation_during_reduction_test.cpp +++ b/test/reduce/validation_during_reduction_test.cpp @@ -22,8 +22,8 @@ namespace reduce { namespace { using opt::Function; -using opt::Instruction; using opt::IRContext; +using opt::Instruction; // A reduction opportunity finder that finds opportunities to remove global // values regardless of whether they are referenced. This is very likely to make @@ -43,7 +43,7 @@ class BlindlyRemoveGlobalValuesReductionOpportunityFinder // referenced (directly or indirectly) from elsewhere in the module, each such // opportunity will make the module invalid. std::vector> GetAvailableOpportunities( - IRContext* context) const final { + IRContext* context, uint32_t /*unused*/) const final { std::vector> result; for (auto& inst : context->module()->types_values()) { if (inst.HasResultId()) { @@ -101,7 +101,7 @@ class OpVariableDuplicatorReductionOpportunityFinder } std::vector> GetAvailableOpportunities( - IRContext* context) const final { + IRContext* context, uint32_t /*unused*/) const final { std::vector> result; for (auto& function : *context->module()) { Instruction* first_instruction = &*function.begin()[0].begin(); diff --git a/tools/reduce/reduce.cpp b/tools/reduce/reduce.cpp index 0bdeb82a..49a5efef 100644 --- a/tools/reduce/reduce.cpp +++ b/tools/reduce/reduce.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include "source/opt/build_module.h" #include "source/opt/ir_context.h" @@ -102,6 +103,14 @@ Options (in lexicographical order): --step-limit= 32-bit unsigned integer specifying maximum number of steps the reducer will take before giving up. + --target-function= + 32-bit unsigned integer specifying the id of a function in the + input module. The reducer will restrict attention to this + function, and will not make changes to other functions or to + instructions outside of functions, except that some global + instructions may be added in support of reducing the target + function. If 0 is specified (the default) then all functions are + reduced. --temp-file-prefix= Specifies a temporary file prefix that will be used to output temporary shader files during reduction. A number and .spv @@ -169,6 +178,15 @@ ReduceStatus ParseFlags(int argc, const char** argv, static_cast(strtol(split_flag.second.c_str(), &end, 10)); assert(end != split_flag.second.c_str() && errno == 0); reducer_options->set_step_limit(step_limit); + } else if (0 == strncmp(cur_arg, "--target-function=", + sizeof("--target-function=") - 1)) { + const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg); + char* end = nullptr; + errno = 0; + const auto target_function = + static_cast(strtol(split_flag.second.c_str(), &end, 10)); + assert(end != split_flag.second.c_str() && errno == 0); + reducer_options->set_target_function(target_function); } else if (0 == strcmp(cur_arg, "--fail-on-validation-error")) { reducer_options->set_fail_on_validation_error(true); } else if (0 == strcmp(cur_arg, "--before-hlsl-legalization")) { @@ -304,6 +322,29 @@ int main(int argc, const char** argv) { return 1; } + const uint32_t target_function = (*reducer_options).target_function; + if (target_function) { + // A target function was specified; check that it exists. + std::unique_ptr context = spvtools::BuildModule( + kDefaultEnvironment, spvtools::utils::CLIMessageConsumer, + binary_in.data(), binary_in.size()); + bool found_target_function = false; + for (auto& function : *context->module()) { + if (function.result_id() == target_function) { + found_target_function = true; + break; + } + } + if (!found_target_function) { + std::stringstream strstr; + strstr << "Target function with id " << target_function + << " was requested, but not found in the module; stopping."; + spvtools::utils::CLIMessageConsumer(SPV_MSG_ERROR, nullptr, {}, + strstr.str().c_str()); + return 1; + } + } + std::vector binary_out; const auto reduction_status = reducer.Run(std::move(binary_in), &binary_out, reducer_options, validator_options);