// 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 #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 binary, uint32_t seed, uint32_t reductions_batch_size, bool enable_all_reductions, bool validate_after_each_reduction) : ir_context_(nullptr), finders_(), rng_(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(); MaybeAddFinder< spvtools::reduce::OperandToConstReductionOpportunityFinder>(); MaybeAddFinder< spvtools::reduce::OperandToDominatingIdReductionOpportunityFinder>(); MaybeAddFinder< spvtools::reduce::OperandToUndefReductionOpportunityFinder>(); MaybeAddFinder(); MaybeAddFinder< spvtools::reduce::RemoveFunctionReductionOpportunityFinder>(); MaybeAddFinder< spvtools::reduce::RemoveSelectionReductionOpportunityFinder>(); MaybeAddFinder< spvtools::reduce::RemoveUnusedInstructionReductionOpportunityFinder>( true); MaybeAddFinder< spvtools::reduce::RemoveUnusedStructMemberReductionOpportunityFinder>(); MaybeAddFinder< spvtools::reduce::SimpleConditionalBranchToBranchOpportunityFinder>(); MaybeAddFinder(); } 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 SpirvReduceMutator::GetBinary() const { std::vector 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