160 lines
5.8 KiB
C++
160 lines
5.8 KiB
C++
// 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
|