blob: e95a8de0c599a3662424ba165029d21b37f9daa4 [file] [log] [blame]
// 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