spirv-fuzz: Integrate spirv-reduce with shrinker (#3849)
This extends shrinking so that spirv-reduce is employed to simplify the functions that are added by TransformationAddFunction.
This commit is contained in:
Родитель
74a711a76d
Коммит
b920b620ad
|
@ -36,6 +36,7 @@ if(SPIRV_BUILD_FUZZER)
|
||||||
)
|
)
|
||||||
|
|
||||||
set(SPIRV_TOOLS_FUZZ_SOURCES
|
set(SPIRV_TOOLS_FUZZ_SOURCES
|
||||||
|
added_function_reducer.h
|
||||||
call_graph.h
|
call_graph.h
|
||||||
comparator_deep_blocks_first.h
|
comparator_deep_blocks_first.h
|
||||||
counter_overflow_id_source.h
|
counter_overflow_id_source.h
|
||||||
|
@ -218,6 +219,7 @@ if(SPIRV_BUILD_FUZZER)
|
||||||
uniform_buffer_element_descriptor.h
|
uniform_buffer_element_descriptor.h
|
||||||
${CMAKE_CURRENT_BINARY_DIR}/protobufs/spvtoolsfuzz.pb.h
|
${CMAKE_CURRENT_BINARY_DIR}/protobufs/spvtoolsfuzz.pb.h
|
||||||
|
|
||||||
|
added_function_reducer.cpp
|
||||||
call_graph.cpp
|
call_graph.cpp
|
||||||
counter_overflow_id_source.cpp
|
counter_overflow_id_source.cpp
|
||||||
data_descriptor.cpp
|
data_descriptor.cpp
|
||||||
|
@ -429,6 +431,7 @@ if(SPIRV_BUILD_FUZZER)
|
||||||
target_link_libraries(SPIRV-Tools-fuzz
|
target_link_libraries(SPIRV-Tools-fuzz
|
||||||
PUBLIC ${SPIRV_TOOLS}-static
|
PUBLIC ${SPIRV_TOOLS}-static
|
||||||
PUBLIC SPIRV-Tools-opt
|
PUBLIC SPIRV-Tools-opt
|
||||||
|
PUBLIC SPIRV-Tools-reduce
|
||||||
PUBLIC protobuf::libprotobuf)
|
PUBLIC protobuf::libprotobuf)
|
||||||
|
|
||||||
set_property(TARGET SPIRV-Tools-fuzz PROPERTY FOLDER "SPIRV-Tools libraries")
|
set_property(TARGET SPIRV-Tools-fuzz PROPERTY FOLDER "SPIRV-Tools libraries")
|
||||||
|
|
|
@ -0,0 +1,293 @@
|
||||||
|
// 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_reduction_attempts_(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);
|
||||||
|
return {AddedFunctionReducerResultStatus::kComplete, std::move(binary_out),
|
||||||
|
std::move(transformation_sequence_out), num_reduction_attempts_};
|
||||||
|
}
|
||||||
|
|
||||||
|
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_reduction_attempts_;
|
||||||
|
num_reduction_attempts_++;
|
||||||
|
|
||||||
|
// 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() != SpvOpVariable) {
|
||||||
|
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() != SpvOpVariable) {
|
||||||
|
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
|
|
@ -0,0 +1,192 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#ifndef SOURCE_FUZZ_ADDED_FUNCTION_REDUCER_H_
|
||||||
|
#define SOURCE_FUZZ_ADDED_FUNCTION_REDUCER_H_
|
||||||
|
|
||||||
|
#include <unordered_set>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
|
||||||
|
#include "source/fuzz/shrinker.h"
|
||||||
|
#include "spirv-tools/libspirv.hpp"
|
||||||
|
|
||||||
|
namespace spvtools {
|
||||||
|
namespace fuzz {
|
||||||
|
|
||||||
|
// An auxiliary class used by Shrinker, this class takes care of using
|
||||||
|
// spirv-reduce to reduce the body of a function encoded in an AddFunction
|
||||||
|
// transformation, in case a smaller, simpler function can be added instead.
|
||||||
|
class AddedFunctionReducer {
|
||||||
|
public:
|
||||||
|
// Possible statuses that can result from running the shrinker.
|
||||||
|
enum class AddedFunctionReducerResultStatus {
|
||||||
|
kComplete,
|
||||||
|
kReductionFailed,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AddedFunctionReducerResult {
|
||||||
|
AddedFunctionReducerResultStatus status;
|
||||||
|
std::vector<uint32_t> transformed_binary;
|
||||||
|
protobufs::TransformationSequence applied_transformations;
|
||||||
|
uint32_t num_reduction_attempts;
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Disables copy/move constructor/assignment operations.
|
||||||
|
AddedFunctionReducer(const AddedFunctionReducer&) = delete;
|
||||||
|
AddedFunctionReducer(AddedFunctionReducer&&) = delete;
|
||||||
|
AddedFunctionReducer& operator=(const AddedFunctionReducer&) = delete;
|
||||||
|
AddedFunctionReducer& operator=(AddedFunctionReducer&&) = delete;
|
||||||
|
|
||||||
|
~AddedFunctionReducer();
|
||||||
|
|
||||||
|
// Invokes spirv-reduce on the function in the AddFunction transformation
|
||||||
|
// identified by |index_of_add_function_transformation|. Returns a sequence
|
||||||
|
// of transformations identical to |transformation_sequence_in|, except that
|
||||||
|
// the AddFunction transformation at |index_of_add_function_transformation|
|
||||||
|
// might have been simplified. The binary associated with applying the
|
||||||
|
// resulting sequence of transformations to |binary_in| is also returned, as
|
||||||
|
// well as the number of reduction steps that spirv-reduce made.
|
||||||
|
//
|
||||||
|
// On failure, an empty transformation sequence and binary are returned,
|
||||||
|
// with a placeholder value of 0 for the number of reduction attempts.
|
||||||
|
AddedFunctionReducerResult Run();
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Yields, via |binary_out|, the binary obtained by applying transformations
|
||||||
|
// [0, |index_of_added_function_| - 1] from |transformations_in_| to
|
||||||
|
// |binary_in_|, and then adding the raw function encoded in
|
||||||
|
// |transformations_in_[index_of_added_function_]| (without adapting that
|
||||||
|
// function to make it livesafe). This function has |added_function_id_| as
|
||||||
|
// its result id.
|
||||||
|
//
|
||||||
|
// The ids associated with all global variables in |binary_out| that had the
|
||||||
|
// "irrelevant pointee value" fact are also returned via
|
||||||
|
// |irrelevant_pointee_global_variables|.
|
||||||
|
//
|
||||||
|
// The point of this function is that spirv-reduce can subsequently be applied
|
||||||
|
// to function |added_function_id_| in |binary_out|. By construction,
|
||||||
|
// |added_function_id_| should originally manipulate globals for which
|
||||||
|
// "irrelevant pointee value" facts hold. The set
|
||||||
|
// |irrelevant_pointee_global_variables| can be used to force spirv-reduce
|
||||||
|
// to preserve this, to avoid the reduced function ending up manipulating
|
||||||
|
// other global variables of the SPIR-V module, potentially changing their
|
||||||
|
// value and thus changing the semantics of the module.
|
||||||
|
void ReplayPrefixAndAddFunction(
|
||||||
|
std::vector<uint32_t>* binary_out,
|
||||||
|
std::unordered_set<uint32_t>* irrelevant_pointee_global_variables) const;
|
||||||
|
|
||||||
|
// This is the interestingness function that will be used by spirv-reduce
|
||||||
|
// when shrinking the added function.
|
||||||
|
//
|
||||||
|
// For |binary_under_reduction| to be deemed interesting, the following
|
||||||
|
// conditions must hold:
|
||||||
|
// - The function with id |added_function_id_| in |binary_under_reduction|
|
||||||
|
// must only reference global variables in
|
||||||
|
// |irrelevant_pointee_global_variables|. This avoids the reduced function
|
||||||
|
// changing the semantics of the original SPIR-V module.
|
||||||
|
// - It must be possible to successfully replay the transformations in
|
||||||
|
// |transformation_sequence_in_|, adapted so that the function added by the
|
||||||
|
// transformation at |index_of_add_function_transformation_| is replaced by
|
||||||
|
// the function with id |added_function_id_| in |binary_under_reduction|,
|
||||||
|
// to |binary_in| (starting with initial facts |initial_facts_|).
|
||||||
|
// - All the transformations in this sequence must be successfully applied
|
||||||
|
// during replay.
|
||||||
|
// - The resulting binary must be interesting according to
|
||||||
|
// |shrinker_interestingness_function_|.
|
||||||
|
bool InterestingnessFunctionForReducingAddedFunction(
|
||||||
|
const std::vector<uint32_t>& binary_under_reduction,
|
||||||
|
const std::unordered_set<uint32_t>& irrelevant_pointee_global_variables);
|
||||||
|
|
||||||
|
// Starting with |binary_in_| and |initial_facts_|, the transformations in
|
||||||
|
// |transformation_sequence_in_| are replayed. However, the transformation
|
||||||
|
// at index |index_of_add_function_transformation_| of
|
||||||
|
// |transformation_sequence_in_| -- which is guaranteed to be an AddFunction
|
||||||
|
// transformation -- is adapted so that the function to be added is replaced
|
||||||
|
// with the function in |binary_under_reduction| with id |added_function_id_|.
|
||||||
|
//
|
||||||
|
// The binary resulting from this replay is returned via |binary_out|, and the
|
||||||
|
// adapted transformation sequence via |transformation_sequence_out|.
|
||||||
|
void ReplayAdaptedTransformations(
|
||||||
|
const std::vector<uint32_t>& binary_under_reduction,
|
||||||
|
std::vector<uint32_t>* binary_out,
|
||||||
|
protobufs::TransformationSequence* transformation_sequence_out) const;
|
||||||
|
|
||||||
|
// Returns the id of the function to be added by the AddFunction
|
||||||
|
// transformation at
|
||||||
|
// |transformation_sequence_in_[index_of_add_function_transformation_]|.
|
||||||
|
uint32_t GetAddedFunctionId() const;
|
||||||
|
|
||||||
|
// Target environment.
|
||||||
|
const spv_target_env target_env_;
|
||||||
|
|
||||||
|
// Message consumer.
|
||||||
|
MessageConsumer consumer_;
|
||||||
|
|
||||||
|
// The initial binary to which transformations are applied -- i.e., the
|
||||||
|
// binary to which spirv-fuzz originally applied transformations.
|
||||||
|
const std::vector<uint32_t>& binary_in_;
|
||||||
|
|
||||||
|
// Initial facts about |binary_in_|.
|
||||||
|
const protobufs::FactSequence& initial_facts_;
|
||||||
|
|
||||||
|
// A set of transformations that can be successfully applied to |binary_in_|.
|
||||||
|
const protobufs::TransformationSequence& transformation_sequence_in_;
|
||||||
|
|
||||||
|
// An index into |transformation_sequence_in_| referring to an AddFunction
|
||||||
|
// transformation. This is the transformation to be simplified using
|
||||||
|
// spirv-reduce.
|
||||||
|
const uint32_t index_of_add_function_transformation_;
|
||||||
|
|
||||||
|
// The interestingness function that has been provided to guide the
|
||||||
|
// overall shrinking process. The AddFunction transformation being simplified
|
||||||
|
// by this class should still -- when applied in conjunction with the other
|
||||||
|
// transformations in |transformation_sequence_in_| -- lead to a binary that
|
||||||
|
// is deemed interesting by this function.
|
||||||
|
const Shrinker::InterestingnessFunction& shrinker_interestingness_function_;
|
||||||
|
|
||||||
|
// Determines whether to check for validity during the replaying of
|
||||||
|
// transformations.
|
||||||
|
const bool validate_during_replay_;
|
||||||
|
|
||||||
|
// Options to control validation.
|
||||||
|
spv_validator_options validator_options_;
|
||||||
|
|
||||||
|
// The step limit associated with the overall shrinking process.
|
||||||
|
const uint32_t shrinker_step_limit_;
|
||||||
|
|
||||||
|
// The number of shrink attempts that had been applied prior to invoking this
|
||||||
|
// AddedFunctionReducer instance.
|
||||||
|
const uint32_t num_existing_shrink_attempts_;
|
||||||
|
|
||||||
|
// Tracks the number of attempts that spirv-reduce has made in reducing the
|
||||||
|
// added function.
|
||||||
|
uint32_t num_reduction_attempts_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace fuzz
|
||||||
|
} // namespace spvtools
|
||||||
|
|
||||||
|
#endif // SOURCE_FUZZ_ADDED_FUNCTION_REDUCER_H_
|
|
@ -36,6 +36,18 @@ protobufs::Instruction MakeInstructionMessage(
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protobufs::Instruction MakeInstructionMessage(
|
||||||
|
const opt::Instruction* instruction) {
|
||||||
|
opt::Instruction::OperandList input_operands;
|
||||||
|
for (uint32_t input_operand_index = 0;
|
||||||
|
input_operand_index < instruction->NumInOperands();
|
||||||
|
input_operand_index++) {
|
||||||
|
input_operands.push_back(instruction->GetInOperand(input_operand_index));
|
||||||
|
}
|
||||||
|
return MakeInstructionMessage(instruction->opcode(), instruction->type_id(),
|
||||||
|
instruction->result_id(), input_operands);
|
||||||
|
}
|
||||||
|
|
||||||
std::unique_ptr<opt::Instruction> InstructionFromMessage(
|
std::unique_ptr<opt::Instruction> InstructionFromMessage(
|
||||||
opt::IRContext* ir_context,
|
opt::IRContext* ir_context,
|
||||||
const protobufs::Instruction& instruction_message) {
|
const protobufs::Instruction& instruction_message) {
|
||||||
|
|
|
@ -29,6 +29,10 @@ protobufs::Instruction MakeInstructionMessage(
|
||||||
SpvOp opcode, uint32_t result_type_id, uint32_t result_id,
|
SpvOp opcode, uint32_t result_type_id, uint32_t result_id,
|
||||||
const opt::Instruction::OperandList& input_operands);
|
const opt::Instruction::OperandList& input_operands);
|
||||||
|
|
||||||
|
// Creates an Instruction protobuf message from a parsed instruction.
|
||||||
|
protobufs::Instruction MakeInstructionMessage(
|
||||||
|
const opt::Instruction* instruction);
|
||||||
|
|
||||||
// Creates and returns an opt::Instruction from protobuf message
|
// Creates and returns an opt::Instruction from protobuf message
|
||||||
// |instruction_message|, relative to |ir_context|. In the process, the module
|
// |instruction_message|, relative to |ir_context|. In the process, the module
|
||||||
// id bound associated with |ir_context| is updated to be at least as large as
|
// id bound associated with |ir_context| is updated to be at least as large as
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
|
#include "source/fuzz/added_function_reducer.h"
|
||||||
#include "source/fuzz/pseudo_random_generator.h"
|
#include "source/fuzz/pseudo_random_generator.h"
|
||||||
#include "source/fuzz/replayer.h"
|
#include "source/fuzz/replayer.h"
|
||||||
#include "source/opt/build_module.h"
|
#include "source/opt/build_module.h"
|
||||||
|
@ -238,6 +239,51 @@ Shrinker::ShrinkerResult Shrinker::Run() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We now use spirv-reduce to minimise the functions associated with any
|
||||||
|
// AddFunction transformations that remain.
|
||||||
|
//
|
||||||
|
// Consider every remaining transformation.
|
||||||
|
for (uint32_t transformation_index = 0;
|
||||||
|
attempt < step_limit_ &&
|
||||||
|
transformation_index <
|
||||||
|
static_cast<uint32_t>(
|
||||||
|
current_best_transformations.transformation_size());
|
||||||
|
transformation_index++) {
|
||||||
|
// Skip all transformations apart from TransformationAddFunction.
|
||||||
|
if (!current_best_transformations.transformation(transformation_index)
|
||||||
|
.has_add_function()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Invoke spirv-reduce on the function encoded in this AddFunction
|
||||||
|
// transformation. The details of this are rather involved, and so are
|
||||||
|
// encapsulated in a separate class.
|
||||||
|
auto added_function_reducer_result =
|
||||||
|
AddedFunctionReducer(target_env_, consumer_, binary_in_, initial_facts_,
|
||||||
|
current_best_transformations, transformation_index,
|
||||||
|
interestingness_function_, validate_during_replay_,
|
||||||
|
validator_options_, step_limit_, attempt)
|
||||||
|
.Run();
|
||||||
|
// Reducing the added function should succeed. If it doesn't, we report
|
||||||
|
// a shrinking error.
|
||||||
|
if (added_function_reducer_result.status !=
|
||||||
|
AddedFunctionReducer::AddedFunctionReducerResultStatus::kComplete) {
|
||||||
|
return {ShrinkerResultStatus::kAddedFunctionReductionFailed,
|
||||||
|
std::vector<uint32_t>(), protobufs::TransformationSequence()};
|
||||||
|
}
|
||||||
|
assert(current_best_transformations.transformation_size() ==
|
||||||
|
added_function_reducer_result.applied_transformations
|
||||||
|
.transformation_size() &&
|
||||||
|
"The number of transformations should not have changed.");
|
||||||
|
current_best_binary =
|
||||||
|
std::move(added_function_reducer_result.transformed_binary);
|
||||||
|
current_best_transformations =
|
||||||
|
std::move(added_function_reducer_result.applied_transformations);
|
||||||
|
// The added function reducer reports how many reduction attempts
|
||||||
|
// spirv-reduce took when reducing the function. We regard each of these
|
||||||
|
// as a shrinker attempt.
|
||||||
|
attempt += added_function_reducer_result.num_reduction_attempts;
|
||||||
|
}
|
||||||
|
|
||||||
// Indicate whether shrinking completed or was truncated due to reaching the
|
// Indicate whether shrinking completed or was truncated due to reaching the
|
||||||
// step limit.
|
// step limit.
|
||||||
//
|
//
|
||||||
|
|
|
@ -37,6 +37,7 @@ class Shrinker {
|
||||||
kInitialBinaryNotInteresting,
|
kInitialBinaryNotInteresting,
|
||||||
kReplayFailed,
|
kReplayFailed,
|
||||||
kStepLimitReached,
|
kStepLimitReached,
|
||||||
|
kAddedFunctionReductionFailed,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ShrinkerResult {
|
struct ShrinkerResult {
|
||||||
|
@ -107,7 +108,7 @@ class Shrinker {
|
||||||
// The series of transformations to be shrunk.
|
// The series of transformations to be shrunk.
|
||||||
const protobufs::TransformationSequence& transformation_sequence_in_;
|
const protobufs::TransformationSequence& transformation_sequence_in_;
|
||||||
|
|
||||||
// Function that decides whether a given binary is interesting.
|
// Function that decides whether a given module is interesting.
|
||||||
const InterestingnessFunction& interestingness_function_;
|
const InterestingnessFunction& interestingness_function_;
|
||||||
|
|
||||||
// Step limit to decide when to terminate shrinking early.
|
// Step limit to decide when to terminate shrinking early.
|
||||||
|
|
|
@ -73,7 +73,6 @@ class TransformationAddFunction : public Transformation {
|
||||||
static uint32_t GetBackEdgeBlockId(opt::IRContext* ir_context,
|
static uint32_t GetBackEdgeBlockId(opt::IRContext* ir_context,
|
||||||
uint32_t loop_header_block_id);
|
uint32_t loop_header_block_id);
|
||||||
|
|
||||||
private:
|
|
||||||
// Attempts to create a function from the series of instructions in
|
// Attempts to create a function from the series of instructions in
|
||||||
// |message_.instruction| and add it to |ir_context|.
|
// |message_.instruction| and add it to |ir_context|.
|
||||||
//
|
//
|
||||||
|
@ -94,6 +93,7 @@ class TransformationAddFunction : public Transformation {
|
||||||
// to add the function.
|
// to add the function.
|
||||||
bool TryToAddFunction(opt::IRContext* ir_context) const;
|
bool TryToAddFunction(opt::IRContext* ir_context) const;
|
||||||
|
|
||||||
|
private:
|
||||||
// Should only be called if |message_.is_livesafe| holds. Attempts to make
|
// Should only be called if |message_.is_livesafe| holds. Attempts to make
|
||||||
// the function livesafe (see FactFunctionIsLivesafe for a definition).
|
// the function livesafe (see FactFunctionIsLivesafe for a definition).
|
||||||
// Returns false if this is not possible, due to |message_| or |ir_context|
|
// Returns false if this is not possible, due to |message_| or |ir_context|
|
||||||
|
|
|
@ -30,6 +30,7 @@ if (${SPIRV_BUILD_FUZZER})
|
||||||
instruction_descriptor_test.cpp
|
instruction_descriptor_test.cpp
|
||||||
fuzzer_pass_test.cpp
|
fuzzer_pass_test.cpp
|
||||||
replayer_test.cpp
|
replayer_test.cpp
|
||||||
|
shrinker_test.cpp
|
||||||
transformation_access_chain_test.cpp
|
transformation_access_chain_test.cpp
|
||||||
transformation_add_bit_instruction_synonym_test.cpp
|
transformation_add_bit_instruction_synonym_test.cpp
|
||||||
transformation_add_constant_boolean_test.cpp
|
transformation_add_constant_boolean_test.cpp
|
||||||
|
|
|
@ -0,0 +1,266 @@
|
||||||
|
// 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/shrinker.h"
|
||||||
|
|
||||||
|
#include "source/fuzz/fact_manager/fact_manager.h"
|
||||||
|
#include "source/fuzz/fuzzer_context.h"
|
||||||
|
#include "source/fuzz/fuzzer_pass_donate_modules.h"
|
||||||
|
#include "source/fuzz/pseudo_random_generator.h"
|
||||||
|
#include "source/fuzz/transformation_context.h"
|
||||||
|
#include "source/opt/ir_context.h"
|
||||||
|
#include "source/util/make_unique.h"
|
||||||
|
#include "test/fuzz/fuzz_test_util.h"
|
||||||
|
|
||||||
|
namespace spvtools {
|
||||||
|
namespace fuzz {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
TEST(ShrinkerTest, ReduceAddedFunctions) {
|
||||||
|
const std::string kReferenceModule = R"(
|
||||||
|
OpCapability Shader
|
||||||
|
%1 = OpExtInstImport "GLSL.std.450"
|
||||||
|
OpMemoryModel Logical GLSL450
|
||||||
|
OpEntryPoint Fragment %4 "main"
|
||||||
|
OpExecutionMode %4 OriginUpperLeft
|
||||||
|
OpSource ESSL 320
|
||||||
|
%2 = OpTypeVoid
|
||||||
|
%3 = OpTypeFunction %2
|
||||||
|
%6 = OpTypeInt 32 1
|
||||||
|
%7 = OpTypePointer Private %6
|
||||||
|
%8 = OpVariable %7 Private
|
||||||
|
%9 = OpConstant %6 2
|
||||||
|
%10 = OpTypePointer Function %6
|
||||||
|
%4 = OpFunction %2 None %3
|
||||||
|
%5 = OpLabel
|
||||||
|
%11 = OpVariable %10 Function
|
||||||
|
OpStore %8 %9
|
||||||
|
%12 = OpLoad %6 %8
|
||||||
|
OpStore %11 %12
|
||||||
|
OpReturn
|
||||||
|
OpFunctionEnd
|
||||||
|
)";
|
||||||
|
|
||||||
|
const std::string kDonorModule = R"(
|
||||||
|
OpCapability Shader
|
||||||
|
%1 = OpExtInstImport "GLSL.std.450"
|
||||||
|
OpMemoryModel Logical GLSL450
|
||||||
|
OpEntryPoint Fragment %4 "main"
|
||||||
|
OpExecutionMode %4 OriginUpperLeft
|
||||||
|
OpSource ESSL 320
|
||||||
|
%2 = OpTypeVoid
|
||||||
|
%3 = OpTypeFunction %2
|
||||||
|
%6 = OpTypeInt 32 1
|
||||||
|
%7 = OpTypePointer Function %6
|
||||||
|
%8 = OpTypeFunction %6 %7
|
||||||
|
%12 = OpTypeFunction %2 %7
|
||||||
|
%17 = OpConstant %6 0
|
||||||
|
%26 = OpTypeBool
|
||||||
|
%32 = OpConstant %6 1
|
||||||
|
%46 = OpTypePointer Private %6
|
||||||
|
%47 = OpVariable %46 Private
|
||||||
|
%48 = OpConstant %6 3
|
||||||
|
%4 = OpFunction %2 None %3
|
||||||
|
%5 = OpLabel
|
||||||
|
%49 = OpVariable %7 Function
|
||||||
|
%50 = OpVariable %7 Function
|
||||||
|
%51 = OpLoad %6 %49
|
||||||
|
OpStore %50 %51
|
||||||
|
%52 = OpFunctionCall %2 %14 %50
|
||||||
|
OpReturn
|
||||||
|
OpFunctionEnd
|
||||||
|
%10 = OpFunction %6 None %8
|
||||||
|
%9 = OpFunctionParameter %7
|
||||||
|
%11 = OpLabel
|
||||||
|
%16 = OpVariable %7 Function
|
||||||
|
%18 = OpVariable %7 Function
|
||||||
|
OpStore %16 %17
|
||||||
|
OpStore %18 %17
|
||||||
|
OpBranch %19
|
||||||
|
%19 = OpLabel
|
||||||
|
OpLoopMerge %21 %22 None
|
||||||
|
OpBranch %23
|
||||||
|
%23 = OpLabel
|
||||||
|
%24 = OpLoad %6 %18
|
||||||
|
%25 = OpLoad %6 %9
|
||||||
|
%27 = OpSLessThan %26 %24 %25
|
||||||
|
OpBranchConditional %27 %20 %21
|
||||||
|
%20 = OpLabel
|
||||||
|
%28 = OpLoad %6 %9
|
||||||
|
%29 = OpLoad %6 %16
|
||||||
|
%30 = OpIAdd %6 %29 %28
|
||||||
|
OpStore %16 %30
|
||||||
|
OpBranch %22
|
||||||
|
%22 = OpLabel
|
||||||
|
%31 = OpLoad %6 %18
|
||||||
|
%33 = OpIAdd %6 %31 %32
|
||||||
|
OpStore %18 %33
|
||||||
|
OpBranch %19
|
||||||
|
%21 = OpLabel
|
||||||
|
%34 = OpLoad %6 %16
|
||||||
|
%35 = OpNot %6 %34
|
||||||
|
OpReturnValue %35
|
||||||
|
OpFunctionEnd
|
||||||
|
%14 = OpFunction %2 None %12
|
||||||
|
%13 = OpFunctionParameter %7
|
||||||
|
%15 = OpLabel
|
||||||
|
%37 = OpVariable %7 Function
|
||||||
|
%38 = OpVariable %7 Function
|
||||||
|
%39 = OpLoad %6 %13
|
||||||
|
OpStore %38 %39
|
||||||
|
%40 = OpFunctionCall %6 %10 %38
|
||||||
|
OpStore %37 %40
|
||||||
|
%41 = OpLoad %6 %37
|
||||||
|
%42 = OpLoad %6 %13
|
||||||
|
%43 = OpSGreaterThan %26 %41 %42
|
||||||
|
OpSelectionMerge %45 None
|
||||||
|
OpBranchConditional %43 %44 %45
|
||||||
|
%44 = OpLabel
|
||||||
|
OpStore %47 %48
|
||||||
|
OpBranch %45
|
||||||
|
%45 = OpLabel
|
||||||
|
OpReturn
|
||||||
|
OpFunctionEnd
|
||||||
|
)";
|
||||||
|
|
||||||
|
// Note: |env| should ideally be declared const. However, due to a known
|
||||||
|
// issue with older versions of MSVC we would have to mark |env| as being
|
||||||
|
// captured due to its used in a lambda below, and other compilers would warn
|
||||||
|
// that such capturing is not necessary. Not declaring |env| as const means
|
||||||
|
// that it needs to be captured to be used in the lambda, and thus all
|
||||||
|
// compilers are kept happy. See:
|
||||||
|
// https://developercommunity.visualstudio.com/content/problem/367326/problems-with-capturing-constexpr-in-lambda.html
|
||||||
|
spv_target_env env = SPV_ENV_UNIVERSAL_1_3;
|
||||||
|
const auto consumer = kSilentConsumer;
|
||||||
|
|
||||||
|
SpirvTools tools(env);
|
||||||
|
std::vector<uint32_t> reference_binary;
|
||||||
|
ASSERT_TRUE(
|
||||||
|
tools.Assemble(kReferenceModule, &reference_binary, kFuzzAssembleOption));
|
||||||
|
|
||||||
|
const auto variant_ir_context =
|
||||||
|
BuildModule(env, consumer, kReferenceModule, kFuzzAssembleOption);
|
||||||
|
ASSERT_TRUE(IsValid(env, variant_ir_context.get()));
|
||||||
|
|
||||||
|
const auto donor_ir_context =
|
||||||
|
BuildModule(env, consumer, kDonorModule, kFuzzAssembleOption);
|
||||||
|
ASSERT_TRUE(IsValid(env, donor_ir_context.get()));
|
||||||
|
|
||||||
|
PseudoRandomGenerator random_generator(0);
|
||||||
|
FuzzerContext fuzzer_context(&random_generator, 100);
|
||||||
|
spvtools::ValidatorOptions validator_options;
|
||||||
|
TransformationContext transformation_context(
|
||||||
|
MakeUnique<FactManager>(variant_ir_context.get()), validator_options);
|
||||||
|
|
||||||
|
protobufs::TransformationSequence transformations;
|
||||||
|
FuzzerPassDonateModules pass(variant_ir_context.get(),
|
||||||
|
&transformation_context, &fuzzer_context,
|
||||||
|
&transformations, {});
|
||||||
|
pass.DonateSingleModule(donor_ir_context.get(), true);
|
||||||
|
|
||||||
|
protobufs::FactSequence no_facts;
|
||||||
|
|
||||||
|
Shrinker::InterestingnessFunction interestingness_function =
|
||||||
|
[consumer, env](const std::vector<uint32_t>& binary,
|
||||||
|
uint32_t /*unused*/) -> bool {
|
||||||
|
bool found_op_not = false;
|
||||||
|
uint32_t op_call_count = 0;
|
||||||
|
auto temp_ir_context =
|
||||||
|
BuildModule(env, consumer, binary.data(), binary.size());
|
||||||
|
for (auto& function : *temp_ir_context->module()) {
|
||||||
|
for (auto& block : function) {
|
||||||
|
for (auto& inst : block) {
|
||||||
|
if (inst.opcode() == SpvOpNot) {
|
||||||
|
found_op_not = true;
|
||||||
|
} else if (inst.opcode() == SpvOpFunctionCall) {
|
||||||
|
op_call_count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return found_op_not && op_call_count >= 2;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto shrinker_result =
|
||||||
|
Shrinker(env, consumer, reference_binary, no_facts, transformations,
|
||||||
|
interestingness_function, 1000, true, validator_options)
|
||||||
|
.Run();
|
||||||
|
ASSERT_EQ(Shrinker::ShrinkerResultStatus::kComplete, shrinker_result.status);
|
||||||
|
|
||||||
|
// We now check that the module after shrinking looks right.
|
||||||
|
// The entry point should be identical to what it looked like in the
|
||||||
|
// reference, while the other functions should be absolutely minimal,
|
||||||
|
// containing only what is needed to satisfy the interestingness function.
|
||||||
|
auto ir_context_after_shrinking =
|
||||||
|
BuildModule(env, consumer, shrinker_result.transformed_binary.data(),
|
||||||
|
shrinker_result.transformed_binary.size());
|
||||||
|
bool first_function = true;
|
||||||
|
for (auto& function : *ir_context_after_shrinking->module()) {
|
||||||
|
if (first_function) {
|
||||||
|
first_function = false;
|
||||||
|
bool first_block = true;
|
||||||
|
for (auto& block : function) {
|
||||||
|
ASSERT_TRUE(first_block);
|
||||||
|
uint32_t counter = 0;
|
||||||
|
for (auto& inst : block) {
|
||||||
|
switch (counter) {
|
||||||
|
case 0:
|
||||||
|
ASSERT_EQ(SpvOpVariable, inst.opcode());
|
||||||
|
ASSERT_EQ(11, inst.result_id());
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
ASSERT_EQ(SpvOpStore, inst.opcode());
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
ASSERT_EQ(SpvOpLoad, inst.opcode());
|
||||||
|
ASSERT_EQ(12, inst.result_id());
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
ASSERT_EQ(SpvOpStore, inst.opcode());
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
ASSERT_EQ(SpvOpReturn, inst.opcode());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
FAIL();
|
||||||
|
}
|
||||||
|
counter++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bool first_block = true;
|
||||||
|
for (auto& block : function) {
|
||||||
|
ASSERT_TRUE(first_block);
|
||||||
|
first_block = false;
|
||||||
|
for (auto& inst : block) {
|
||||||
|
switch (inst.opcode()) {
|
||||||
|
case SpvOpVariable:
|
||||||
|
case SpvOpNot:
|
||||||
|
case SpvOpReturn:
|
||||||
|
case SpvOpReturnValue:
|
||||||
|
case SpvOpFunctionCall:
|
||||||
|
// These are the only instructions we expect to see.
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
FAIL();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
} // namespace fuzz
|
||||||
|
} // namespace spvtools
|
Загрузка…
Ссылка в новой задаче