blob: e6bebead1cf4003b7e293676b9cbc067a9fbc0d6 [file] [log] [blame]
// 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/fuzzer_pass_replace_branches_from_dead_blocks_with_exits.h"
#include <algorithm>
#include <vector>
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/transformation_replace_branch_from_dead_block_with_exit.h"
namespace spvtools {
namespace fuzz {
FuzzerPassReplaceBranchesFromDeadBlocksWithExits::
FuzzerPassReplaceBranchesFromDeadBlocksWithExits(
opt::IRContext* ir_context,
TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations)
: FuzzerPass(ir_context, transformation_context, fuzzer_context,
transformations) {}
FuzzerPassReplaceBranchesFromDeadBlocksWithExits::
~FuzzerPassReplaceBranchesFromDeadBlocksWithExits() = default;
void FuzzerPassReplaceBranchesFromDeadBlocksWithExits::Apply() {
// OpKill can only be used as a terminator in a function that is guaranteed
// to be executed with the Fragment execution model. We conservatively only
// allow OpKill if every entry point in the module has the Fragment execution
// model.
auto fragment_execution_model_guaranteed =
std::all_of(GetIRContext()->module()->entry_points().begin(),
GetIRContext()->module()->entry_points().end(),
[](const opt::Instruction& entry_point) -> bool {
return entry_point.GetSingleWordInOperand(0) ==
SpvExecutionModelFragment;
});
// Transformations of this type can disable one another. To avoid ordering
// bias, we therefore build a set of candidate transformations to apply, and
// subsequently apply them in a random order, skipping any that cease to be
// applicable.
std::vector<TransformationReplaceBranchFromDeadBlockWithExit>
candidate_transformations;
// Consider every block in every function.
for (auto& function : *GetIRContext()->module()) {
for (auto& block : function) {
// Probabilistically decide whether to skip this block.
if (GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()
->GetChanceOfReplacingBranchFromDeadBlockWithExit())) {
continue;
}
// Check whether the block is suitable for having its terminator replaced.
if (!TransformationReplaceBranchFromDeadBlockWithExit::BlockIsSuitable(
GetIRContext(), *GetTransformationContext(), block)) {
continue;
}
// We can always use OpUnreachable to replace a block's terminator.
// Whether we can use OpKill depends on the execution model, and which of
// OpReturn and OpReturnValue we can use depends on the return type of the
// enclosing function.
std::vector<SpvOp> opcodes = {SpvOpUnreachable};
if (fragment_execution_model_guaranteed) {
opcodes.emplace_back(SpvOpKill);
}
auto function_return_type =
GetIRContext()->get_type_mgr()->GetType(function.type_id());
if (function_return_type->AsVoid()) {
opcodes.emplace_back(SpvOpReturn);
} else if (fuzzerutil::CanCreateConstant(GetIRContext(),
function.type_id())) {
// For simplicity we only allow OpReturnValue if the function return
// type is a type for which we can create a constant. This allows us a
// zero of the given type as a default return value.
opcodes.emplace_back(SpvOpReturnValue);
}
// Choose one of the available terminator opcodes at random and create a
// candidate transformation.
auto opcode = opcodes[GetFuzzerContext()->RandomIndex(opcodes)];
candidate_transformations.emplace_back(
TransformationReplaceBranchFromDeadBlockWithExit(
block.id(), opcode,
opcode == SpvOpReturnValue
? FindOrCreateZeroConstant(function.type_id(), true)
: 0));
}
}
// Process the candidate transformations in a random order.
while (!candidate_transformations.empty()) {
// Transformations of this type can disable one another. For example,
// suppose we have dead blocks A, B, C, D arranged as follows:
//
// A |
// / \ |
// B C |
// \ / |
// D |
//
// Here we can replace the terminator of either B or C with an early exit,
// because D has two predecessors. But if we replace the terminator of B,
// say, we get:
//
// A |
// / \ |
// B C |
// / |
// D |
//
// and now it is no longer OK to replace the terminator of C as D only has
// one predecessor and we do not want to make D unreachable in the control
// flow graph.
MaybeApplyTransformation(
GetFuzzerContext()->RemoveAtRandomIndex(&candidate_transformations));
}
}
} // namespace fuzz
} // namespace spvtools