SPIRV-Tools/source/fuzz/added_function_reducer.cpp
alan-baker d35a78db57
Switch SPIRV-Tools to use spirv.hpp11 internally (#4981)
Fixes #4960

* Switches to using enum classes with an underlying type to avoid
  undefined behaviour
2022-11-04 17:27:10 -04:00

303 lines
14 KiB
C++

// Copyright (c) 2020 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 "source/fuzz/added_function_reducer.h"
#include "source/fuzz/instruction_message.h"
#include "source/fuzz/replayer.h"
#include "source/fuzz/transformation_add_function.h"
#include "source/opt/build_module.h"
#include "source/opt/ir_context.h"
#include "source/reduce/reducer.h"
namespace spvtools {
namespace fuzz {
AddedFunctionReducer::AddedFunctionReducer(
spv_target_env target_env, MessageConsumer consumer,
const std::vector<uint32_t>& binary_in,
const protobufs::FactSequence& initial_facts,
const protobufs::TransformationSequence& transformation_sequence_in,
uint32_t index_of_add_function_transformation,
const Shrinker::InterestingnessFunction& shrinker_interestingness_function,
bool validate_during_replay, spv_validator_options validator_options,
uint32_t shrinker_step_limit, uint32_t num_existing_shrink_attempts)
: target_env_(target_env),
consumer_(std::move(consumer)),
binary_in_(binary_in),
initial_facts_(initial_facts),
transformation_sequence_in_(transformation_sequence_in),
index_of_add_function_transformation_(
index_of_add_function_transformation),
shrinker_interestingness_function_(shrinker_interestingness_function),
validate_during_replay_(validate_during_replay),
validator_options_(validator_options),
shrinker_step_limit_(shrinker_step_limit),
num_existing_shrink_attempts_(num_existing_shrink_attempts),
num_reducer_interestingness_function_invocations_(0) {}
AddedFunctionReducer::~AddedFunctionReducer() = default;
AddedFunctionReducer::AddedFunctionReducerResult AddedFunctionReducer::Run() {
// Replay all transformations before the AddFunction transformation, then
// add the raw function associated with the AddFunction transformation.
std::vector<uint32_t> binary_to_reduce;
std::unordered_set<uint32_t> irrelevant_pointee_global_variables;
ReplayPrefixAndAddFunction(&binary_to_reduce,
&irrelevant_pointee_global_variables);
// Set up spirv-reduce to use our very specific interestingness function.
reduce::Reducer reducer(target_env_);
reducer.SetMessageConsumer(consumer_);
reducer.AddDefaultReductionPasses();
reducer.SetInterestingnessFunction(
[this, &irrelevant_pointee_global_variables](
const std::vector<uint32_t>& binary_under_reduction,
uint32_t /*unused*/) {
return InterestingnessFunctionForReducingAddedFunction(
binary_under_reduction, irrelevant_pointee_global_variables);
});
// Instruct spirv-reduce to only target the function with the id associated
// with the AddFunction transformation that we care about.
spvtools::ReducerOptions reducer_options;
reducer_options.set_target_function(GetAddedFunctionId());
// Bound the number of reduction steps that spirv-reduce can make according
// to the overall shrinker step limit and the number of shrink attempts that
// have already been tried.
assert(shrinker_step_limit_ > num_existing_shrink_attempts_ &&
"The added function reducer should not have been invoked.");
reducer_options.set_step_limit(shrinker_step_limit_ -
num_existing_shrink_attempts_);
// Run spirv-reduce.
std::vector<uint32_t> reduced_binary;
auto reducer_result =
reducer.Run(std::move(binary_to_reduce), &reduced_binary, reducer_options,
validator_options_);
if (reducer_result != reduce::Reducer::kComplete &&
reducer_result != reduce::Reducer::kReachedStepLimit) {
return {AddedFunctionReducerResultStatus::kReductionFailed,
std::vector<uint32_t>(), protobufs::TransformationSequence(), 0};
}
// Provide the outer shrinker with an adapted sequence of transformations in
// which the AddFunction transformation of interest has been simplified to use
// the version of the added function that appears in |reduced_binary|.
std::vector<uint32_t> binary_out;
protobufs::TransformationSequence transformation_sequence_out;
ReplayAdaptedTransformations(reduced_binary, &binary_out,
&transformation_sequence_out);
// We subtract 1 from |num_reducer_interestingness_function_invocations_| to
// account for the fact that spirv-reduce invokes its interestingness test
// once before reduction commences in order to check that the initial module
// is interesting.
assert(num_reducer_interestingness_function_invocations_ > 0 &&
"At a minimum spirv-reduce should have invoked its interestingness "
"test once.");
return {AddedFunctionReducerResultStatus::kComplete, std::move(binary_out),
std::move(transformation_sequence_out),
num_reducer_interestingness_function_invocations_ - 1};
}
bool AddedFunctionReducer::InterestingnessFunctionForReducingAddedFunction(
const std::vector<uint32_t>& binary_under_reduction,
const std::unordered_set<uint32_t>& irrelevant_pointee_global_variables) {
uint32_t counter_for_shrinker_interestingness_function =
num_existing_shrink_attempts_ +
num_reducer_interestingness_function_invocations_;
num_reducer_interestingness_function_invocations_++;
// The reduced version of the added function must be limited to accessing
// global variables appearing in |irrelevant_pointee_global_variables|. This
// is to guard against the possibility of spirv-reduce changing a reference
// to an irrelevant global to a reference to a regular global variable, which
// could cause the added function to change the semantics of the original
// module.
auto ir_context =
BuildModule(target_env_, consumer_, binary_under_reduction.data(),
binary_under_reduction.size());
assert(ir_context != nullptr && "The binary should be parsable.");
for (auto& type_or_value : ir_context->module()->types_values()) {
if (type_or_value.opcode() != spv::Op::OpVariable) {
continue;
}
if (irrelevant_pointee_global_variables.count(type_or_value.result_id())) {
continue;
}
if (!ir_context->get_def_use_mgr()->WhileEachUse(
&type_or_value,
[this, &ir_context](opt::Instruction* user,
uint32_t /*unused*/) -> bool {
auto block = ir_context->get_instr_block(user);
if (block != nullptr &&
block->GetParent()->result_id() == GetAddedFunctionId()) {
return false;
}
return true;
})) {
return false;
}
}
// For the binary to be deemed interesting, it must be possible to
// successfully apply all the transformations, with the transformation at
// index |index_of_add_function_transformation_| simplified to use the version
// of the added function from |binary_under_reduction|.
//
// This might not be the case: spirv-reduce might have removed a chunk of the
// added function on which future transformations depend.
//
// This is an optimization: the assumption is that having already shrunk the
// transformation sequence down to minimal form, all transformations have a
// role to play, and it's almost certainly a waste of time to invoke the
// shrinker's interestingness function if we have eliminated transformations
// that the shrinker previously tried to -- but could not -- eliminate.
std::vector<uint32_t> binary_out;
protobufs::TransformationSequence modified_transformations;
ReplayAdaptedTransformations(binary_under_reduction, &binary_out,
&modified_transformations);
if (transformation_sequence_in_.transformation_size() !=
modified_transformations.transformation_size()) {
return false;
}
// The resulting binary must be deemed interesting according to the shrinker's
// interestingness function.
return shrinker_interestingness_function_(
binary_out, counter_for_shrinker_interestingness_function);
}
void AddedFunctionReducer::ReplayPrefixAndAddFunction(
std::vector<uint32_t>* binary_out,
std::unordered_set<uint32_t>* irrelevant_pointee_global_variables) const {
assert(transformation_sequence_in_
.transformation(index_of_add_function_transformation_)
.has_add_function() &&
"A TransformationAddFunction is required at the given index.");
auto replay_result = Replayer(target_env_, consumer_, binary_in_,
initial_facts_, transformation_sequence_in_,
index_of_add_function_transformation_,
validate_during_replay_, validator_options_)
.Run();
assert(replay_result.status == Replayer::ReplayerResultStatus::kComplete &&
"Replay should succeed");
assert(static_cast<uint32_t>(
replay_result.applied_transformations.transformation_size()) ==
index_of_add_function_transformation_ &&
"All requested transformations should have applied.");
auto* ir_context = replay_result.transformed_module.get();
for (auto& type_or_value : ir_context->module()->types_values()) {
if (type_or_value.opcode() != spv::Op::OpVariable) {
continue;
}
if (replay_result.transformation_context->GetFactManager()
->PointeeValueIsIrrelevant(type_or_value.result_id())) {
irrelevant_pointee_global_variables->insert(type_or_value.result_id());
}
}
// Add the function associated with the transformation at
// |index_of_add_function_transformation| to the module. By construction this
// should succeed.
const protobufs::TransformationAddFunction&
transformation_add_function_message =
transformation_sequence_in_
.transformation(index_of_add_function_transformation_)
.add_function();
bool success = TransformationAddFunction(transformation_add_function_message)
.TryToAddFunction(ir_context);
(void)success; // Keep release mode compilers happy.
assert(success && "Addition of the function should have succeeded.");
// Get the binary representation of the module with this function added.
ir_context->module()->ToBinary(binary_out, false);
}
void AddedFunctionReducer::ReplayAdaptedTransformations(
const std::vector<uint32_t>& binary_under_reduction,
std::vector<uint32_t>* binary_out,
protobufs::TransformationSequence* transformation_sequence_out) const {
assert(index_of_add_function_transformation_ <
static_cast<uint32_t>(
transformation_sequence_in_.transformation_size()) &&
"The relevant add function transformation must be present.");
std::unique_ptr<opt::IRContext> ir_context_under_reduction =
BuildModule(target_env_, consumer_, binary_under_reduction.data(),
binary_under_reduction.size());
assert(ir_context_under_reduction && "Error building module.");
protobufs::TransformationSequence modified_transformations;
for (uint32_t i = 0;
i <
static_cast<uint32_t>(transformation_sequence_in_.transformation_size());
i++) {
if (i == index_of_add_function_transformation_) {
protobufs::TransformationAddFunction modified_add_function =
transformation_sequence_in_
.transformation(index_of_add_function_transformation_)
.add_function();
assert(GetAddedFunctionId() ==
modified_add_function.instruction(0).result_id() &&
"Unexpected result id for added function.");
modified_add_function.clear_instruction();
for (auto& function : *ir_context_under_reduction->module()) {
if (function.result_id() != GetAddedFunctionId()) {
continue;
}
function.ForEachInst(
[&modified_add_function](const opt::Instruction* instruction) {
*modified_add_function.add_instruction() =
MakeInstructionMessage(instruction);
});
}
assert(modified_add_function.instruction_size() > 0 &&
"Some instructions for the added function should remain.");
*modified_transformations.add_transformation()->mutable_add_function() =
modified_add_function;
} else {
*modified_transformations.add_transformation() =
transformation_sequence_in_.transformation(i);
}
}
assert(
transformation_sequence_in_.transformation_size() ==
modified_transformations.transformation_size() &&
"The original and modified transformations should have the same size.");
auto replay_result = Replayer(target_env_, consumer_, binary_in_,
initial_facts_, modified_transformations,
modified_transformations.transformation_size(),
validate_during_replay_, validator_options_)
.Run();
assert(replay_result.status == Replayer::ReplayerResultStatus::kComplete &&
"Replay should succeed.");
replay_result.transformed_module->module()->ToBinary(binary_out, false);
*transformation_sequence_out =
std::move(replay_result.applied_transformations);
}
uint32_t AddedFunctionReducer::GetAddedFunctionId() const {
return transformation_sequence_in_
.transformation(index_of_add_function_transformation_)
.add_function()
.instruction(0)
.result_id();
}
} // namespace fuzz
} // namespace spvtools