| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302 |
- // 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
|