SPIRV-Tools/source/reduce/reducer.cpp
Alastair Donaldson 1cba9942bd Validate during reduction (#2194)
* Run validator during reduction.

* Added functionality to validate modules after each reduction step, and some tests to check this is working.  Also fixed an issue where reduction passes were not guaranteed to be executed at their minimum granularities.
2018-12-12 09:06:13 -05:00

155 lines
6.1 KiB
C++

// Copyright (c) 2018 Google LLC
//
// 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 <cassert>
#include <sstream>
#include "source/spirv_reducer_options.h"
#include "reducer.h"
#include "reduction_pass.h"
namespace spvtools {
namespace reduce {
struct Reducer::Impl {
explicit Impl(spv_target_env env) : target_env(env) {}
bool ReachedStepLimit(uint32_t current_step,
spv_const_reducer_options options);
const spv_target_env target_env; // Target environment.
MessageConsumer consumer; // Message consumer.
InterestingnessFunction interestingness_function;
std::vector<std::unique_ptr<ReductionPass>> passes;
};
Reducer::Reducer(spv_target_env env) : impl_(MakeUnique<Impl>(env)) {}
Reducer::~Reducer() = default;
void Reducer::SetMessageConsumer(MessageConsumer c) {
for (auto& pass : impl_->passes) {
pass->SetMessageConsumer(c);
}
impl_->consumer = std::move(c);
}
void Reducer::SetInterestingnessFunction(
Reducer::InterestingnessFunction interestingness_function) {
impl_->interestingness_function = std::move(interestingness_function);
}
Reducer::ReductionResultStatus Reducer::Run(
std::vector<uint32_t>&& binary_in, std::vector<uint32_t>* binary_out,
spv_const_reducer_options options) const {
std::vector<uint32_t> current_binary = binary_in;
// Keeps track of how many reduction attempts have been tried. Reduction
// bails out if this reaches a given limit.
uint32_t reductions_applied = 0;
// Initial state should be interesting.
if (!impl_->interestingness_function(current_binary, reductions_applied)) {
impl_->consumer(SPV_MSG_INFO, nullptr, {},
"Initial state was not interesting; stopping.");
return Reducer::ReductionResultStatus::kInitialStateNotInteresting;
}
// Determines whether, on completing one round of reduction passes, it is
// worthwhile trying a further round.
bool another_round_worthwhile = true;
// Apply round after round of reduction passes until we hit the reduction
// step limit, or deem that another round is not going to be worthwhile.
while (!impl_->ReachedStepLimit(reductions_applied, options) &&
another_round_worthwhile) {
// At the start of a round of reduction passes, assume another round will
// not be worthwhile unless we find evidence to the contrary.
another_round_worthwhile = false;
// Iterate through the available passes
for (auto& pass : impl_->passes) {
// If this pass hasn't reached its minimum granularity then it's
// worth eventually doing another round of reductions, in order to
// try this pass at a finer granularity.
another_round_worthwhile |= !pass->ReachedMinimumGranularity();
// Keep applying this pass at its current granularity until it stops
// working or we hit the reduction step limit.
impl_->consumer(SPV_MSG_INFO, nullptr, {},
("Trying pass " + pass->GetName() + ".").c_str());
do {
auto maybe_result = pass->TryApplyReduction(current_binary);
if (maybe_result.empty()) {
// This pass did not have any impact, so move on to the next pass.
impl_->consumer(
SPV_MSG_INFO, nullptr, {},
("Pass " + pass->GetName() + " did not make a reduction step.")
.c_str());
break;
}
std::stringstream stringstream;
reductions_applied++;
stringstream << "Pass " << pass->GetName() << " made reduction step "
<< reductions_applied << ".";
impl_->consumer(SPV_MSG_INFO, nullptr, {},
(stringstream.str().c_str()));
if (!spvtools::SpirvTools(impl_->target_env).Validate(maybe_result)) {
// The reduction step went wrong and an invalid binary was produced.
// By design, this shouldn't happen; this is a safeguard to stop an
// invalid binary from being regarded as interesting.
impl_->consumer(SPV_MSG_INFO, nullptr, {},
"Reduction step produced an invalid binary.");
} else if (impl_->interestingness_function(maybe_result,
reductions_applied)) {
// Success! The binary produced by this reduction step is
// interesting, so make it the binary of interest henceforth, and
// note that it's worth doing another round of reduction passes.
impl_->consumer(SPV_MSG_INFO, nullptr, {},
"Reduction step succeeded.");
current_binary = std::move(maybe_result);
another_round_worthwhile = true;
}
// Bail out if the reduction step limit has been reached.
} while (!impl_->ReachedStepLimit(reductions_applied, options));
}
}
*binary_out = std::move(current_binary);
// Report whether reduction completed, or bailed out early due to reaching
// the step limit.
if (impl_->ReachedStepLimit(reductions_applied, options)) {
impl_->consumer(SPV_MSG_INFO, nullptr, {},
"Reached reduction step limit; stopping.");
return Reducer::ReductionResultStatus::kReachedStepLimit;
}
impl_->consumer(SPV_MSG_INFO, nullptr, {}, "No more to reduce; stopping.");
return Reducer::ReductionResultStatus::kComplete;
}
void Reducer::AddReductionPass(
std::unique_ptr<ReductionPass>&& reduction_pass) {
impl_->passes.push_back(std::move(reduction_pass));
}
bool Reducer::Impl::ReachedStepLimit(uint32_t current_step,
spv_const_reducer_options options) {
return current_step >= options->step_limit;
}
} // namespace reduce
} // namespace spvtools