SPIRV-Tools/source/fuzz/fuzzer_pass_flatten_conditional_branches.cpp
Alastair Donaldson 9c4481419e
spirv-fuzz: Allow inapplicable transformations to be ignored (#4407)
spirv-fuzz features transformations that should be applicable by
construction. Assertions are used to detect when such transformations
turn out to be inapplicable. Failures of such assertions indicate bugs
in the fuzzer. However, when using the fuzzer at scale (e.g. in
ClusterFuzz) reports of these assertion failures create noise, and
cause the fuzzer to exit early. This change adds an option whereby
inapplicable transformations can be ignored. This reduces noise and
allows fuzzing to continue even when a transformation that should be
applicable but is not has been erroneously created.
2021-07-28 22:59:37 +01:00

250 lines
11 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,
bool ignore_inapplicable_transformations)
: FuzzerPass(ir_context, transformation_context, fuzzer_context,
transformations, ignore_inapplicable_transformations) {}
void FuzzerPassFlattenConditionalBranches::Apply() {
for (auto& function : *GetIRContext()->module()) {
// 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& 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, with a
// non-irrelevant condition.
if (block.GetMergeInst() &&
block.GetMergeInst()->opcode() == SpvOpSelectionMerge &&
block.terminator()->opcode() == SpvOpBranchConditional &&
!GetTransformationContext()->GetFactManager()->IdIsIrrelevant(
block.terminator()->GetSingleWordInOperand(0))) {
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, *GetTransformationContext(),
&instructions_that_need_ids)) {
continue;
}
uint32_t convergence_block_id =
TransformationFlattenConditionalBranch::FindConvergenceBlock(
GetIRContext(), *header);
// If the SPIR-V version is restricted so that OpSelect can only work on
// scalar, pointer and vector types then we cannot apply this
// transformation to a header whose convergence block features OpPhi
// instructions on different types, as we cannot convert such instructions
// to OpSelect instructions.
if (TransformationFlattenConditionalBranch::
OpSelectArgumentsAreRestricted(GetIRContext())) {
if (!GetIRContext()
->cfg()
->block(convergence_block_id)
->WhileEachPhiInst(
[this](opt::Instruction* phi_instruction) -> bool {
switch (GetIRContext()
->get_def_use_mgr()
->GetDef(phi_instruction->type_id())
->opcode()) {
case SpvOpTypeBool:
case SpvOpTypeInt:
case SpvOpTypeFloat:
case SpvOpTypePointer:
case SpvOpTypeVector:
return true;
default:
return false;
}
})) {
// An OpPhi is performed on a type not supported by OpSelect; we
// cannot flatten this selection.
continue;
}
}
// If the construct's convergence block features OpPhi instructions with
// vector result types then we may be *forced*, by the SPIR-V version, to
// turn these into component-wise OpSelect instructions, or we might wish
// to do so anyway. The following booleans capture whether we will opt
// to use a component-wise select even if we don't have to.
bool use_component_wise_2d_select_even_if_optional =
GetFuzzerContext()->ChooseEven();
bool use_component_wise_3d_select_even_if_optional =
GetFuzzerContext()->ChooseEven();
bool use_component_wise_4d_select_even_if_optional =
GetFuzzerContext()->ChooseEven();
// If we do need to perform any component-wise selections, we will need a
// fresh id for a boolean vector representing the selection's condition
// repeated N times, where N is the vector dimension.
uint32_t fresh_id_for_bvec2_selector = 0;
uint32_t fresh_id_for_bvec3_selector = 0;
uint32_t fresh_id_for_bvec4_selector = 0;
GetIRContext()
->cfg()
->block(convergence_block_id)
->ForEachPhiInst([this, &fresh_id_for_bvec2_selector,
&fresh_id_for_bvec3_selector,
&fresh_id_for_bvec4_selector,
use_component_wise_2d_select_even_if_optional,
use_component_wise_3d_select_even_if_optional,
use_component_wise_4d_select_even_if_optional](
opt::Instruction* phi_instruction) {
opt::Instruction* type_instruction =
GetIRContext()->get_def_use_mgr()->GetDef(
phi_instruction->type_id());
switch (type_instruction->opcode()) {
case SpvOpTypeVector: {
uint32_t dimension =
type_instruction->GetSingleWordInOperand(1);
switch (dimension) {
case 2:
PrepareForOpPhiOnVectors(
dimension,
use_component_wise_2d_select_even_if_optional,
&fresh_id_for_bvec2_selector);
break;
case 3:
PrepareForOpPhiOnVectors(
dimension,
use_component_wise_3d_select_even_if_optional,
&fresh_id_for_bvec3_selector);
break;
case 4:
PrepareForOpPhiOnVectors(
dimension,
use_component_wise_4d_select_even_if_optional,
&fresh_id_for_bvec4_selector);
break;
default:
assert(false && "Invalid vector dimension.");
}
break;
}
default:
break;
}
});
// 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 (fuzzerutil::CanCreateConstant(GetIRContext(),
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.push_back(std::move(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(),
fresh_id_for_bvec2_selector, fresh_id_for_bvec3_selector,
fresh_id_for_bvec4_selector, wrappers_info));
}
}
}
void FuzzerPassFlattenConditionalBranches::PrepareForOpPhiOnVectors(
uint32_t vector_dimension, bool use_vector_select_if_optional,
uint32_t* fresh_id_for_bvec_selector) {
if (*fresh_id_for_bvec_selector != 0) {
// We already have a fresh id for a component-wise OpSelect of this
// dimension
return;
}
if (TransformationFlattenConditionalBranch::OpSelectArgumentsAreRestricted(
GetIRContext()) ||
use_vector_select_if_optional) {
// We either have to, or have chosen to, perform a component-wise select, so
// we ensure that the right boolean vector type is available, and grab a
// fresh id.
FindOrCreateVectorType(FindOrCreateBoolType(), vector_dimension);
*fresh_id_for_bvec_selector = GetFuzzerContext()->GetFreshId();
}
}
} // namespace fuzz
} // namespace spvtools