blob: 360f1d9d8a5708698149a43ffd55e5d8ef6c5eab [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_opt_mutator.h"
#include <fstream>
#include <iostream>
#include <unordered_set>
#include <utility>
#include "fuzzers/tint_spirv_tools_fuzzer/util.h"
#include "spirv-tools/optimizer.hpp"
namespace tint {
namespace fuzzers {
namespace spvtools_fuzzer {
SpirvOptMutator::SpirvOptMutator(spv_target_env target_env,
uint32_t seed,
std::vector<uint32_t> binary,
bool validate_after_each_opt,
uint32_t opt_batch_size)
: num_executions_(0),
is_valid_(true),
target_env_(target_env),
original_binary_(std::move(binary)),
seed_(seed),
opt_passes_({"--combine-access-chains",
"--loop-unroll",
"--merge-blocks",
"--cfg-cleanup",
"--eliminate-dead-functions",
"--merge-return",
"--wrap-opkill",
"--eliminate-dead-code-aggressive",
"--if-conversion",
"--eliminate-local-single-store",
"--eliminate-local-single-block",
"--eliminate-dead-branches",
"--scalar-replacement=0",
"--eliminate-dead-inserts",
"--eliminate-dead-members",
"--simplify-instructions",
"--private-to-local",
"--ssa-rewrite",
"--ccp",
"--reduce-load-size",
"--vector-dce",
"--scalar-replacement=100",
"--inline-entry-points-exhaustive",
"--redundancy-elimination",
"--convert-local-access-chains",
"--copy-propagate-arrays",
"--fix-storage-class"}),
optimized_binary_(),
validate_after_each_opt_(validate_after_each_opt),
opt_batch_size_(opt_batch_size),
rng_(seed) {
assert(spvtools::SpirvTools(target_env).Validate(original_binary_) &&
"Initial binary is invalid");
assert(!opt_passes_.empty() && "Must be at least one pass");
}
SpirvOptMutator::Result SpirvOptMutator::Mutate() {
assert(is_valid_ && "The optimizer is not longer valid");
const uint32_t kMaxNumExecutions = 100;
const uint32_t kMaxNumStuck = 10;
if (num_executions_ == kMaxNumExecutions) {
// We've applied this mutator many times already. Indicate to the user that
// it might be better to try a different mutator.
return {Status::kLimitReached, false};
}
num_executions_++;
// Get the input binary. If this is the first time we run this mutator, use
// the `original_binary_`. Otherwise, one of the following will be true:
// - the `optimized_binary_` is not empty.
// - the previous call to the `Mutate` method returned `kStuck`.
auto binary = num_executions_ == 1 ? original_binary_ : optimized_binary_;
optimized_binary_.clear();
assert(!binary.empty() && "Can't run the optimizer on an empty binary");
// Number of times spirv-opt wasn't able to produce any new result.
uint32_t num_stuck = 0;
do {
// Randomly select `opt_batch_size` optimization passes. If `opt_batch_size`
// is equal to 0, we will use the number of passes equal to the number of
// all available passes.
auto num_of_passes = opt_batch_size_ ? opt_batch_size_ : opt_passes_.size();
std::vector<std::string> passes;
while (passes.size() < num_of_passes) {
auto idx = std::uniform_int_distribution<size_t>(
0, opt_passes_.size() - 1)(rng_);
passes.push_back(opt_passes_[idx]);
}
// Run the `binary` into the `optimized_binary_`.
spvtools::Optimizer optimizer(target_env_);
optimizer.SetMessageConsumer(util::GetBufferMessageConsumer(&errors_));
optimizer.SetValidateAfterAll(validate_after_each_opt_);
optimizer.RegisterPassesFromFlags(passes);
if (!optimizer.Run(binary.data(), binary.size(), &optimized_binary_)) {
is_valid_ = false;
return {Status::kInvalid, true};
}
} while (optimized_binary_.empty() && ++num_stuck < kMaxNumStuck);
return {optimized_binary_.empty() ? Status::kStuck : Status::kComplete,
!optimized_binary_.empty()};
}
std::vector<uint32_t> SpirvOptMutator::GetBinary() const {
return optimized_binary_;
}
std::string SpirvOptMutator::GetErrors() const {
return errors_.str();
}
void SpirvOptMutator::LogErrors(const std::string* path, uint32_t count) const {
auto message = GetErrors();
std::cout << count << " | SpirvOptMutator (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 + ".opt.log") << "seed: " << seed_ << std::endl
<< message << std::endl;
// Write the invalid SPIR-V binary.
util::WriteBinary(prefix + ".opt.invalid.spv", optimized_binary_);
// Write the original SPIR-V binary.
util::WriteBinary(prefix + ".opt.original.spv", original_binary_);
}
}
} // namespace spvtools_fuzzer
} // namespace fuzzers
} // namespace tint