| // Copyright (c) 2018 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/reducer.h" |
| |
| #include <cassert> |
| #include <sstream> |
| |
| #include "source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.h" |
| #include "source/reduce/merge_blocks_reduction_opportunity_finder.h" |
| #include "source/reduce/operand_to_const_reduction_opportunity_finder.h" |
| #include "source/reduce/operand_to_dominating_id_reduction_opportunity_finder.h" |
| #include "source/reduce/operand_to_undef_reduction_opportunity_finder.h" |
| #include "source/reduce/remove_block_reduction_opportunity_finder.h" |
| #include "source/reduce/remove_function_reduction_opportunity_finder.h" |
| #include "source/reduce/remove_selection_reduction_opportunity_finder.h" |
| #include "source/reduce/remove_unused_instruction_reduction_opportunity_finder.h" |
| #include "source/reduce/remove_unused_struct_member_reduction_opportunity_finder.h" |
| #include "source/reduce/simple_conditional_branch_to_branch_opportunity_finder.h" |
| #include "source/reduce/structured_loop_to_selection_reduction_opportunity_finder.h" |
| #include "source/spirv_reducer_options.h" |
| |
| namespace spvtools { |
| namespace reduce { |
| |
| Reducer::Reducer(spv_target_env target_env) : target_env_(target_env) {} |
| |
| Reducer::~Reducer() = default; |
| |
| void Reducer::SetMessageConsumer(MessageConsumer c) { |
| for (auto& pass : passes_) { |
| pass->SetMessageConsumer(c); |
| } |
| for (auto& pass : cleanup_passes_) { |
| pass->SetMessageConsumer(c); |
| } |
| consumer_ = std::move(c); |
| } |
| |
| void Reducer::SetInterestingnessFunction( |
| Reducer::InterestingnessFunction interestingness_function) { |
| interestingness_function_ = std::move(interestingness_function); |
| } |
| |
| Reducer::ReductionResultStatus Reducer::Run( |
| std::vector<uint32_t>&& binary_in, std::vector<uint32_t>* binary_out, |
| spv_const_reducer_options options, |
| spv_validator_options validator_options) { |
| std::vector<uint32_t> current_binary(std::move(binary_in)); |
| |
| spvtools::SpirvTools tools(target_env_); |
| assert(tools.IsValid() && "Failed to create SPIRV-Tools interface"); |
| |
| // Keeps track of how many reduction attempts have been tried. Reduction |
| // bails out if this reaches a given limit. |
| uint32_t reductions_applied = 0; |
| |
| // Initial state should be valid. |
| if (!tools.Validate(¤t_binary[0], current_binary.size(), |
| validator_options)) { |
| consumer_(SPV_MSG_INFO, nullptr, {}, |
| "Initial binary is invalid; stopping."); |
| return Reducer::ReductionResultStatus::kInitialStateInvalid; |
| } |
| |
| // Initial state should be interesting. |
| if (!interestingness_function_(current_binary, reductions_applied)) { |
| consumer_(SPV_MSG_INFO, nullptr, {}, |
| "Initial state was not interesting; stopping."); |
| return Reducer::ReductionResultStatus::kInitialStateNotInteresting; |
| } |
| |
| Reducer::ReductionResultStatus result = |
| RunPasses(&passes_, options, validator_options, tools, ¤t_binary, |
| &reductions_applied); |
| |
| if (result == Reducer::ReductionResultStatus::kComplete) { |
| // Cleanup passes. |
| result = RunPasses(&cleanup_passes_, options, validator_options, tools, |
| ¤t_binary, &reductions_applied); |
| } |
| |
| if (result == Reducer::ReductionResultStatus::kComplete) { |
| consumer_(SPV_MSG_INFO, nullptr, {}, "No more to reduce; stopping."); |
| } |
| |
| // Even if the reduction has failed by this point (e.g. due to producing an |
| // invalid binary), we still update the output binary for better debugging. |
| *binary_out = std::move(current_binary); |
| |
| return result; |
| } |
| |
| void Reducer::AddDefaultReductionPasses() { |
| AddReductionPass( |
| spvtools::MakeUnique<RemoveUnusedInstructionReductionOpportunityFinder>( |
| false)); |
| AddReductionPass( |
| spvtools::MakeUnique<OperandToUndefReductionOpportunityFinder>()); |
| AddReductionPass( |
| spvtools::MakeUnique<OperandToConstReductionOpportunityFinder>()); |
| AddReductionPass( |
| spvtools::MakeUnique<OperandToDominatingIdReductionOpportunityFinder>()); |
| AddReductionPass(spvtools::MakeUnique< |
| StructuredLoopToSelectionReductionOpportunityFinder>()); |
| AddReductionPass( |
| spvtools::MakeUnique<MergeBlocksReductionOpportunityFinder>()); |
| AddReductionPass( |
| spvtools::MakeUnique<RemoveFunctionReductionOpportunityFinder>()); |
| AddReductionPass( |
| spvtools::MakeUnique<RemoveBlockReductionOpportunityFinder>()); |
| AddReductionPass( |
| spvtools::MakeUnique<RemoveSelectionReductionOpportunityFinder>()); |
| AddReductionPass( |
| spvtools::MakeUnique< |
| ConditionalBranchToSimpleConditionalBranchOpportunityFinder>()); |
| AddReductionPass( |
| spvtools::MakeUnique<SimpleConditionalBranchToBranchOpportunityFinder>()); |
| AddReductionPass(spvtools::MakeUnique< |
| RemoveUnusedStructMemberReductionOpportunityFinder>()); |
| |
| // Cleanup passes. |
| |
| AddCleanupReductionPass( |
| spvtools::MakeUnique<RemoveUnusedInstructionReductionOpportunityFinder>( |
| true)); |
| } |
| |
| void Reducer::AddReductionPass( |
| std::unique_ptr<ReductionOpportunityFinder>&& finder) { |
| passes_.push_back( |
| spvtools::MakeUnique<ReductionPass>(target_env_, std::move(finder))); |
| } |
| |
| void Reducer::AddCleanupReductionPass( |
| std::unique_ptr<ReductionOpportunityFinder>&& finder) { |
| cleanup_passes_.push_back( |
| spvtools::MakeUnique<ReductionPass>(target_env_, std::move(finder))); |
| } |
| |
| bool Reducer::ReachedStepLimit(uint32_t current_step, |
| spv_const_reducer_options options) { |
| return current_step >= options->step_limit; |
| } |
| |
| Reducer::ReductionResultStatus Reducer::RunPasses( |
| std::vector<std::unique_ptr<ReductionPass>>* passes, |
| spv_const_reducer_options options, spv_validator_options validator_options, |
| const SpirvTools& tools, std::vector<uint32_t>* current_binary, |
| uint32_t* const reductions_applied) { |
| // Determines whether, on completing one round of reduction passes, it is |
| // worthwhile trying a further round. |
| bool another_round_worthwhile = true; |
| |
| // Apply round after round of reduction passes until we hit the reduction |
| // step limit, or deem that another round is not going to be worthwhile. |
| while (!ReachedStepLimit(*reductions_applied, options) && |
| another_round_worthwhile) { |
| // At the start of a round of reduction passes, assume another round will |
| // not be worthwhile unless we find evidence to the contrary. |
| another_round_worthwhile = false; |
| |
| // Iterate through the available passes. |
| for (auto& pass : *passes) { |
| // If this pass hasn't reached its minimum granularity then it's |
| // worth eventually doing another round of reductions, in order to |
| // try this pass at a finer granularity. |
| another_round_worthwhile |= !pass->ReachedMinimumGranularity(); |
| |
| // Keep applying this pass at its current granularity until it stops |
| // working or we hit the reduction step limit. |
| consumer_(SPV_MSG_INFO, nullptr, {}, |
| ("Trying pass " + pass->GetName() + ".").c_str()); |
| do { |
| auto maybe_result = pass->TryApplyReduction(*current_binary); |
| if (maybe_result.empty()) { |
| // For this round, the pass has no more opportunities (chunks) to |
| // apply, so move on to the next pass. |
| consumer_( |
| SPV_MSG_INFO, nullptr, {}, |
| ("Pass " + pass->GetName() + " did not make a reduction step.") |
| .c_str()); |
| break; |
| } |
| bool interesting = false; |
| std::stringstream stringstream; |
| (*reductions_applied)++; |
| stringstream << "Pass " << pass->GetName() << " made reduction step " |
| << *reductions_applied << "."; |
| consumer_(SPV_MSG_INFO, nullptr, {}, (stringstream.str().c_str())); |
| if (!tools.Validate(&maybe_result[0], maybe_result.size(), |
| validator_options)) { |
| // The reduction step went wrong and an invalid binary was produced. |
| // By design, this shouldn't happen; this is a safeguard to stop an |
| // invalid binary from being regarded as interesting. |
| consumer_(SPV_MSG_INFO, nullptr, {}, |
| "Reduction step produced an invalid binary."); |
| if (options->fail_on_validation_error) { |
| // In this mode, we fail, so we update the current binary so it is |
| // output for debugging. |
| *current_binary = std::move(maybe_result); |
| return Reducer::ReductionResultStatus::kStateInvalid; |
| } |
| } else if (interestingness_function_(maybe_result, |
| *reductions_applied)) { |
| // Success! The binary produced by this reduction step is |
| // interesting, so make it the binary of interest henceforth, and |
| // note that it's worth doing another round of reduction passes. |
| consumer_(SPV_MSG_INFO, nullptr, {}, "Reduction step succeeded."); |
| *current_binary = std::move(maybe_result); |
| interesting = true; |
| another_round_worthwhile = true; |
| } |
| // We must call this before the next call to TryApplyReduction. |
| pass->NotifyInteresting(interesting); |
| // Bail out if the reduction step limit has been reached. |
| } while (!ReachedStepLimit(*reductions_applied, options)); |
| } |
| } |
| |
| // Report whether reduction completed, or bailed out early due to reaching |
| // the step limit. |
| if (ReachedStepLimit(*reductions_applied, options)) { |
| consumer_(SPV_MSG_INFO, nullptr, {}, |
| "Reached reduction step limit; stopping."); |
| return Reducer::ReductionResultStatus::kReachedStepLimit; |
| } |
| |
| // The passes completed successfully, although we may still run more passes. |
| return Reducer::ReductionResultStatus::kComplete; |
| } |
| |
| } // namespace reduce |
| } // namespace spvtools |