| // Copyright 2021 The Tint Authors. |
| // |
| // 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 "fuzzers/tint_spirv_tools_fuzzer/spirv_reduce_mutator.h" |
| |
| #include <fstream> |
| |
| #include "fuzzers/tint_spirv_tools_fuzzer/util.h" |
| #include "source/fuzz/fuzzer_util.h" |
| #include "source/opt/build_module.h" |
| #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" |
| |
| namespace tint { |
| namespace fuzzers { |
| namespace spvtools_fuzzer { |
| |
| SpirvReduceMutator::SpirvReduceMutator(spv_target_env target_env, |
| std::vector<uint32_t> binary, |
| uint32_t seed, |
| uint32_t reductions_batch_size, |
| bool enable_all_reductions, |
| bool validate_after_each_reduction) |
| : ir_context_(nullptr), |
| finders_(), |
| generator_(seed), |
| errors_(), |
| is_valid_(true), |
| reductions_batch_size_(reductions_batch_size), |
| total_applied_reductions_(0), |
| enable_all_reductions_(enable_all_reductions), |
| validate_after_each_reduction_(validate_after_each_reduction), |
| original_binary_(std::move(binary)), |
| seed_(seed) { |
| ir_context_ = spvtools::BuildModule( |
| target_env, spvtools::fuzz::fuzzerutil::kSilentMessageConsumer, |
| original_binary_.data(), original_binary_.size()); |
| assert(ir_context_ && "|binary| is invalid"); |
| |
| do { |
| MaybeAddFinder< |
| spvtools::reduce:: |
| ConditionalBranchToSimpleConditionalBranchOpportunityFinder>(); |
| MaybeAddFinder<spvtools::reduce::MergeBlocksReductionOpportunityFinder>(); |
| MaybeAddFinder< |
| spvtools::reduce::OperandToConstReductionOpportunityFinder>(); |
| MaybeAddFinder< |
| spvtools::reduce::OperandToDominatingIdReductionOpportunityFinder>(); |
| MaybeAddFinder< |
| spvtools::reduce::OperandToUndefReductionOpportunityFinder>(); |
| MaybeAddFinder<spvtools::reduce::RemoveBlockReductionOpportunityFinder>(); |
| MaybeAddFinder< |
| spvtools::reduce::RemoveFunctionReductionOpportunityFinder>(); |
| MaybeAddFinder< |
| spvtools::reduce::RemoveSelectionReductionOpportunityFinder>(); |
| MaybeAddFinder< |
| spvtools::reduce::RemoveUnusedInstructionReductionOpportunityFinder>( |
| true); |
| MaybeAddFinder< |
| spvtools::reduce::RemoveUnusedStructMemberReductionOpportunityFinder>(); |
| MaybeAddFinder< |
| spvtools::reduce::SimpleConditionalBranchToBranchOpportunityFinder>(); |
| MaybeAddFinder<spvtools::reduce:: |
| StructuredLoopToSelectionReductionOpportunityFinder>(); |
| } while (finders_.empty()); |
| } |
| |
| Mutator::Result SpirvReduceMutator::Mutate() { |
| assert(is_valid_ && "Can't mutate invalid module"); |
| |
| // The upper limit on the number of applied reduction passes. |
| const uint32_t kMaxAppliedReductions = 500; |
| const auto old_applied_reductions = total_applied_reductions_; |
| |
| // The upper limit on the number of failed attempts to apply reductions (i.e. |
| // when no reduction was returned by the reduction finder). |
| const uint32_t kMaxConsecutiveFailures = 10; |
| uint32_t num_consecutive_failures = 0; |
| |
| // Iterate while we haven't exceeded the limit on the total number of applied |
| // reductions, the limit on the number of reductions applied at once and limit |
| // on the number of consecutive failed attempts. |
| while (total_applied_reductions_ < kMaxAppliedReductions && |
| (reductions_batch_size_ == 0 || |
| total_applied_reductions_ - old_applied_reductions < |
| reductions_batch_size_) && |
| num_consecutive_failures < kMaxConsecutiveFailures) { |
| // Select an opportunity finder and get some reduction opportunities from |
| // it. |
| auto finder = GetRandomElement(&finders_); |
| auto reduction_opportunities = |
| finder->GetAvailableOpportunities(ir_context_.get(), 0); |
| |
| if (reduction_opportunities.empty()) { |
| // There is nothing to reduce. We increase the counter to make sure we |
| // don't stuck in this situation. |
| num_consecutive_failures++; |
| } else { |
| // Apply a random reduction opportunity. The latter should be applicable. |
| auto opportunity = GetRandomElement(&reduction_opportunities); |
| assert(opportunity->PreconditionHolds() && "Preconditions should hold"); |
| total_applied_reductions_++; |
| num_consecutive_failures = 0; |
| if (!ApplyReduction(opportunity)) { |
| // The module became invalid as a result of the applied reduction. |
| is_valid_ = false; |
| return {Mutator::Status::kInvalid, |
| total_applied_reductions_ != old_applied_reductions}; |
| } |
| } |
| } |
| |
| auto is_changed = total_applied_reductions_ != old_applied_reductions; |
| if (total_applied_reductions_ == kMaxAppliedReductions) { |
| return {Mutator::Status::kLimitReached, is_changed}; |
| } |
| |
| if (num_consecutive_failures == kMaxConsecutiveFailures) { |
| return {Mutator::Status::kStuck, is_changed}; |
| } |
| |
| assert(is_changed && "This is the only way left to break the loop"); |
| return {Mutator::Status::kComplete, is_changed}; |
| } |
| |
| bool SpirvReduceMutator::ApplyReduction( |
| spvtools::reduce::ReductionOpportunity* reduction_opportunity) { |
| reduction_opportunity->TryToApply(); |
| return !validate_after_each_reduction_ || |
| spvtools::fuzz::fuzzerutil::IsValidAndWellFormed( |
| ir_context_.get(), spvtools::ValidatorOptions(), |
| util::GetBufferMessageConsumer(&errors_)); |
| } |
| |
| std::vector<uint32_t> SpirvReduceMutator::GetBinary() const { |
| std::vector<uint32_t> result; |
| ir_context_->module()->ToBinary(&result, true); |
| return result; |
| } |
| |
| std::string SpirvReduceMutator::GetErrors() const { |
| return errors_.str(); |
| } |
| |
| void SpirvReduceMutator::LogErrors(const std::string* path, |
| uint32_t count) const { |
| auto message = GetErrors(); |
| std::cout << count << " | SpirvReduceMutator (seed: " << seed_ << ")" |
| << std::endl; |
| std::cout << message << std::endl; |
| |
| if (path) { |
| auto prefix = *path + std::to_string(count); |
| |
| // Write errors to file. |
| std::ofstream(prefix + ".reducer.log") << "seed: " << seed_ << std::endl |
| << message << std::endl; |
| |
| // Write the invalid SPIR-V binary. |
| util::WriteBinary(prefix + ".reducer.invalid.spv", GetBinary()); |
| |
| // Write the original SPIR-V binary. |
| util::WriteBinary(prefix + ".reducer.original.spv", original_binary_); |
| } |
| } |
| |
| } // namespace spvtools_fuzzer |
| } // namespace fuzzers |
| } // namespace tint |