mirror of
https://github.com/KhronosGroup/SPIRV-Tools
synced 2024-11-29 14:31:04 +00:00
d35a78db57
Fixes #4960 * Switches to using enum classes with an underlying type to avoid undefined behaviour
303 lines
14 KiB
C++
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
|