SPIRV-Tools/source/fuzz/fuzzer_pass_flatten_conditional_branches.cpp
Stefano Milizia f62357e7b8
spirv-fuzz: Transformation to flatten conditional branch (#3667)
This transformation, given the header of a selection construct with
branching instruction OpBranchConditional, flattens it.
Side-effecting operations such as OpLoad, OpStore and OpFunctionCall
are enclosed within smaller conditionals.
It is applicable if the construct does not contain inner selection
constructs or loops, or atomic or barrier instructions.

The corresponding fuzzer pass looks for selection headers and
tries to flatten them.

Needed for the issue #3544, but it does not fix it completely.
2020-09-15 10:31:01 +01:00

125 lines
5.4 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/fuzzer_pass_flatten_conditional_branches.h"
#include "source/fuzz/comparator_deep_blocks_first.h"
#include "source/fuzz/instruction_descriptor.h"
#include "source/fuzz/transformation_flatten_conditional_branch.h"
namespace spvtools {
namespace fuzz {
// A fuzzer pass that randomly selects conditional branches to flatten and
// flattens them, if possible.
FuzzerPassFlattenConditionalBranches::FuzzerPassFlattenConditionalBranches(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations)
: FuzzerPass(ir_context, transformation_context, fuzzer_context,
transformations) {}
FuzzerPassFlattenConditionalBranches::~FuzzerPassFlattenConditionalBranches() =
default;
void FuzzerPassFlattenConditionalBranches::Apply() {
// Get all the selection headers that we want to flatten. We need to collect
// all of them first, because, since we are changing the structure of the
// module, it's not safe to modify them while iterating.
std::vector<opt::BasicBlock*> selection_headers;
for (auto& function : *GetIRContext()->module()) {
for (auto& block : function) {
// Randomly decide whether to consider this block.
if (!GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfFlatteningConditionalBranch())) {
continue;
}
// Only consider this block if it is the header of a conditional.
if (block.GetMergeInst() &&
block.GetMergeInst()->opcode() == SpvOpSelectionMerge &&
block.terminator()->opcode() == SpvOpBranchConditional) {
selection_headers.emplace_back(&block);
}
}
}
// Sort the headers so that those that are more deeply nested are considered
// first, possibly enabling outer conditionals to be flattened.
std::sort(selection_headers.begin(), selection_headers.end(),
ComparatorDeepBlocksFirst(GetIRContext()));
// Apply the transformation to the headers which can be flattened.
for (auto header : selection_headers) {
// Make a set to keep track of the instructions that need fresh ids.
std::set<opt::Instruction*> instructions_that_need_ids;
// Do not consider this header if the conditional cannot be flattened.
if (!TransformationFlattenConditionalBranch::
GetProblematicInstructionsIfConditionalCanBeFlattened(
GetIRContext(), header, &instructions_that_need_ids)) {
continue;
}
// Some instructions will require to be enclosed inside conditionals because
// they have side effects (for example, loads and stores). Some of this have
// no result id, so we require instruction descriptors to identify them.
// Each of them is associated with the necessary ids for it via a
// SideEffectWrapperInfo message.
std::vector<protobufs::SideEffectWrapperInfo> wrappers_info;
for (auto instruction : instructions_that_need_ids) {
protobufs::SideEffectWrapperInfo wrapper_info;
*wrapper_info.mutable_instruction() =
MakeInstructionDescriptor(GetIRContext(), instruction);
wrapper_info.set_merge_block_id(GetFuzzerContext()->GetFreshId());
wrapper_info.set_execute_block_id(GetFuzzerContext()->GetFreshId());
// If the instruction has a non-void result id, we need to define more
// fresh ids and provide an id of the suitable type whose value can be
// copied in order to create a placeholder id.
if (TransformationFlattenConditionalBranch::InstructionNeedsPlaceholder(
GetIRContext(), *instruction)) {
wrapper_info.set_actual_result_id(GetFuzzerContext()->GetFreshId());
wrapper_info.set_alternative_block_id(GetFuzzerContext()->GetFreshId());
wrapper_info.set_placeholder_result_id(
GetFuzzerContext()->GetFreshId());
// The id will be a zero constant if the type allows it, and an OpUndef
// otherwise. We want to avoid using OpUndef, if possible, to avoid
// undefined behaviour in the module as much as possible.
if (CanFindOrCreateZeroConstant(
*GetIRContext()->get_type_mgr()->GetType(
instruction->type_id()))) {
wrapper_info.set_value_to_copy_id(
FindOrCreateZeroConstant(instruction->type_id(), true));
} else {
wrapper_info.set_value_to_copy_id(
FindOrCreateGlobalUndef(instruction->type_id()));
}
}
wrappers_info.emplace_back(wrapper_info);
}
// Apply the transformation, evenly choosing whether to lay out the true
// branch or the false branch first.
ApplyTransformation(TransformationFlattenConditionalBranch(
header->id(), GetFuzzerContext()->ChooseEven(), wrappers_info));
}
}
} // namespace fuzz
} // namespace spvtools