SPIRV-Tools/source/fuzz/fuzzer_pass.cpp
Alastair Donaldson 4fcdc58946
Add IsReachable function to IRContext (#4323)
There was a lot of code in the codebase that would get the dominator
analysis for a function and then use it to check whether a block is
reachable. In the fuzzer, a utility method had been introduced to make
this more concise, but it was not being used consistently.

This change moves the utility method to IRContext, so that it can be
used throughout the codebase, and refactors all existing checks for
block reachability to use the utility method.
2021-06-28 20:00:14 +01:00

782 lines
30 KiB
C++

// Copyright (c) 2019 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.h"
#include <set>
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/id_use_descriptor.h"
#include "source/fuzz/instruction_descriptor.h"
#include "source/fuzz/transformation_add_constant_boolean.h"
#include "source/fuzz/transformation_add_constant_composite.h"
#include "source/fuzz/transformation_add_constant_null.h"
#include "source/fuzz/transformation_add_constant_scalar.h"
#include "source/fuzz/transformation_add_global_undef.h"
#include "source/fuzz/transformation_add_global_variable.h"
#include "source/fuzz/transformation_add_local_variable.h"
#include "source/fuzz/transformation_add_loop_preheader.h"
#include "source/fuzz/transformation_add_type_boolean.h"
#include "source/fuzz/transformation_add_type_float.h"
#include "source/fuzz/transformation_add_type_function.h"
#include "source/fuzz/transformation_add_type_int.h"
#include "source/fuzz/transformation_add_type_matrix.h"
#include "source/fuzz/transformation_add_type_pointer.h"
#include "source/fuzz/transformation_add_type_struct.h"
#include "source/fuzz/transformation_add_type_vector.h"
#include "source/fuzz/transformation_split_block.h"
namespace spvtools {
namespace fuzz {
FuzzerPass::FuzzerPass(opt::IRContext* ir_context,
TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations)
: ir_context_(ir_context),
transformation_context_(transformation_context),
fuzzer_context_(fuzzer_context),
transformations_(transformations) {}
FuzzerPass::~FuzzerPass() = default;
std::vector<opt::Instruction*> FuzzerPass::FindAvailableInstructions(
opt::Function* function, opt::BasicBlock* block,
const opt::BasicBlock::iterator& inst_it,
std::function<bool(opt::IRContext*, opt::Instruction*)>
instruction_is_relevant) const {
// TODO(afd) The following is (relatively) simple, but may end up being
// prohibitively inefficient, as it walks the whole dominator tree for
// every instruction that is considered.
std::vector<opt::Instruction*> result;
// Consider all global declarations
for (auto& global : GetIRContext()->module()->types_values()) {
if (instruction_is_relevant(GetIRContext(), &global)) {
result.push_back(&global);
}
}
// Consider all function parameters
function->ForEachParam(
[this, &instruction_is_relevant, &result](opt::Instruction* param) {
if (instruction_is_relevant(GetIRContext(), param)) {
result.push_back(param);
}
});
// Consider all previous instructions in this block
for (auto prev_inst_it = block->begin(); prev_inst_it != inst_it;
++prev_inst_it) {
if (instruction_is_relevant(GetIRContext(), &*prev_inst_it)) {
result.push_back(&*prev_inst_it);
}
}
// Walk the dominator tree to consider all instructions from dominating
// blocks
auto dominator_analysis = GetIRContext()->GetDominatorAnalysis(function);
for (auto next_dominator = dominator_analysis->ImmediateDominator(block);
next_dominator != nullptr;
next_dominator =
dominator_analysis->ImmediateDominator(next_dominator)) {
for (auto& dominating_inst : *next_dominator) {
if (instruction_is_relevant(GetIRContext(), &dominating_inst)) {
result.push_back(&dominating_inst);
}
}
}
return result;
}
void FuzzerPass::ForEachInstructionWithInstructionDescriptor(
opt::Function* function,
std::function<
void(opt::BasicBlock* block, opt::BasicBlock::iterator inst_it,
const protobufs::InstructionDescriptor& instruction_descriptor)>
action) {
// Consider only reachable blocks. We do this in a separate loop to avoid
// recomputing the dominator analysis every time |action| changes the
// module.
std::vector<opt::BasicBlock*> reachable_blocks;
for (auto& block : *function) {
if (GetIRContext()->IsReachable(block)) {
reachable_blocks.push_back(&block);
}
}
for (auto* block : reachable_blocks) {
// We now consider every instruction in the block, randomly deciding
// whether to apply a transformation before it.
// In order for transformations to insert new instructions, they need to
// be able to identify the instruction to insert before. We describe an
// instruction via its opcode, 'opc', a base instruction 'base' that has a
// result id, and the number of instructions with opcode 'opc' that we
// should skip when searching from 'base' for the desired instruction.
// (An instruction that has a result id is represented by its own opcode,
// itself as 'base', and a skip-count of 0.)
std::vector<std::tuple<uint32_t, SpvOp, uint32_t>> base_opcode_skip_triples;
// The initial base instruction is the block label.
uint32_t base = block->id();
// Counts the number of times we have seen each opcode since we reset the
// base instruction.
std::map<SpvOp, uint32_t> skip_count;
// Consider every instruction in the block. The label is excluded: it is
// only necessary to consider it as a base in case the first instruction
// in the block does not have a result id.
for (auto inst_it = block->begin(); inst_it != block->end(); ++inst_it) {
if (inst_it->HasResultId()) {
// In the case that the instruction has a result id, we use the
// instruction as its own base, and clear the skip counts we have
// collected.
base = inst_it->result_id();
skip_count.clear();
}
const SpvOp opcode = inst_it->opcode();
// Invoke the provided function, which might apply a transformation.
action(block, inst_it,
MakeInstructionDescriptor(
base, opcode,
skip_count.count(opcode) ? skip_count.at(opcode) : 0));
if (!inst_it->HasResultId()) {
skip_count[opcode] =
skip_count.count(opcode) ? skip_count.at(opcode) + 1 : 1;
}
}
}
}
void FuzzerPass::ForEachInstructionWithInstructionDescriptor(
std::function<
void(opt::Function* function, opt::BasicBlock* block,
opt::BasicBlock::iterator inst_it,
const protobufs::InstructionDescriptor& instruction_descriptor)>
action) {
// Consider every block in every function.
for (auto& function : *GetIRContext()->module()) {
ForEachInstructionWithInstructionDescriptor(
&function,
[&action, &function](
opt::BasicBlock* block, opt::BasicBlock::iterator inst_it,
const protobufs::InstructionDescriptor& instruction_descriptor) {
action(&function, block, inst_it, instruction_descriptor);
});
}
}
void FuzzerPass::ApplyTransformation(const Transformation& transformation) {
assert(transformation.IsApplicable(GetIRContext(),
*GetTransformationContext()) &&
"Transformation should be applicable by construction.");
transformation.Apply(GetIRContext(), GetTransformationContext());
auto transformation_message = transformation.ToMessage();
assert(transformation_message.transformation_case() !=
protobufs::Transformation::TRANSFORMATION_NOT_SET &&
"Bad transformation.");
*GetTransformations()->add_transformation() =
std::move(transformation_message);
}
bool FuzzerPass::MaybeApplyTransformation(
const Transformation& transformation) {
if (transformation.IsApplicable(GetIRContext(),
*GetTransformationContext())) {
transformation.Apply(GetIRContext(), GetTransformationContext());
auto transformation_message = transformation.ToMessage();
assert(transformation_message.transformation_case() !=
protobufs::Transformation::TRANSFORMATION_NOT_SET &&
"Bad transformation.");
*GetTransformations()->add_transformation() =
std::move(transformation_message);
return true;
}
return false;
}
uint32_t FuzzerPass::FindOrCreateBoolType() {
if (auto existing_id = fuzzerutil::MaybeGetBoolType(GetIRContext())) {
return existing_id;
}
auto result = GetFuzzerContext()->GetFreshId();
ApplyTransformation(TransformationAddTypeBoolean(result));
return result;
}
uint32_t FuzzerPass::FindOrCreateIntegerType(uint32_t width, bool is_signed) {
opt::analysis::Integer int_type(width, is_signed);
auto existing_id = GetIRContext()->get_type_mgr()->GetId(&int_type);
if (existing_id) {
return existing_id;
}
auto result = GetFuzzerContext()->GetFreshId();
ApplyTransformation(TransformationAddTypeInt(result, width, is_signed));
return result;
}
uint32_t FuzzerPass::FindOrCreateFloatType(uint32_t width) {
opt::analysis::Float float_type(width);
auto existing_id = GetIRContext()->get_type_mgr()->GetId(&float_type);
if (existing_id) {
return existing_id;
}
auto result = GetFuzzerContext()->GetFreshId();
ApplyTransformation(TransformationAddTypeFloat(result, width));
return result;
}
uint32_t FuzzerPass::FindOrCreateFunctionType(
uint32_t return_type_id, const std::vector<uint32_t>& argument_id) {
// FindFunctionType has a sigle argument for OpTypeFunction operands
// so we will have to copy them all in this vector
std::vector<uint32_t> type_ids(argument_id.size() + 1);
type_ids[0] = return_type_id;
std::copy(argument_id.begin(), argument_id.end(), type_ids.begin() + 1);
// Check if type exists
auto existing_id = fuzzerutil::FindFunctionType(GetIRContext(), type_ids);
if (existing_id) {
return existing_id;
}
auto result = GetFuzzerContext()->GetFreshId();
ApplyTransformation(
TransformationAddTypeFunction(result, return_type_id, argument_id));
return result;
}
uint32_t FuzzerPass::FindOrCreateVectorType(uint32_t component_type_id,
uint32_t component_count) {
assert(component_count >= 2 && component_count <= 4 &&
"Precondition: component count must be in range [2, 4].");
opt::analysis::Type* component_type =
GetIRContext()->get_type_mgr()->GetType(component_type_id);
assert(component_type && "Precondition: the component type must exist.");
opt::analysis::Vector vector_type(component_type, component_count);
auto existing_id = GetIRContext()->get_type_mgr()->GetId(&vector_type);
if (existing_id) {
return existing_id;
}
auto result = GetFuzzerContext()->GetFreshId();
ApplyTransformation(
TransformationAddTypeVector(result, component_type_id, component_count));
return result;
}
uint32_t FuzzerPass::FindOrCreateMatrixType(uint32_t column_count,
uint32_t row_count) {
assert(column_count >= 2 && column_count <= 4 &&
"Precondition: column count must be in range [2, 4].");
assert(row_count >= 2 && row_count <= 4 &&
"Precondition: row count must be in range [2, 4].");
uint32_t column_type_id =
FindOrCreateVectorType(FindOrCreateFloatType(32), row_count);
opt::analysis::Type* column_type =
GetIRContext()->get_type_mgr()->GetType(column_type_id);
opt::analysis::Matrix matrix_type(column_type, column_count);
auto existing_id = GetIRContext()->get_type_mgr()->GetId(&matrix_type);
if (existing_id) {
return existing_id;
}
auto result = GetFuzzerContext()->GetFreshId();
ApplyTransformation(
TransformationAddTypeMatrix(result, column_type_id, column_count));
return result;
}
uint32_t FuzzerPass::FindOrCreateStructType(
const std::vector<uint32_t>& component_type_ids) {
if (auto existing_id =
fuzzerutil::MaybeGetStructType(GetIRContext(), component_type_ids)) {
return existing_id;
}
auto new_id = GetFuzzerContext()->GetFreshId();
ApplyTransformation(TransformationAddTypeStruct(new_id, component_type_ids));
return new_id;
}
uint32_t FuzzerPass::FindOrCreatePointerType(uint32_t base_type_id,
SpvStorageClass storage_class) {
// We do not use the type manager here, due to problems related to isomorphic
// but distinct structs not being regarded as different.
auto existing_id = fuzzerutil::MaybeGetPointerType(
GetIRContext(), base_type_id, storage_class);
if (existing_id) {
return existing_id;
}
auto result = GetFuzzerContext()->GetFreshId();
ApplyTransformation(
TransformationAddTypePointer(result, storage_class, base_type_id));
return result;
}
uint32_t FuzzerPass::FindOrCreatePointerToIntegerType(
uint32_t width, bool is_signed, SpvStorageClass storage_class) {
return FindOrCreatePointerType(FindOrCreateIntegerType(width, is_signed),
storage_class);
}
uint32_t FuzzerPass::FindOrCreateIntegerConstant(
const std::vector<uint32_t>& words, uint32_t width, bool is_signed,
bool is_irrelevant) {
auto int_type_id = FindOrCreateIntegerType(width, is_signed);
if (auto constant_id = fuzzerutil::MaybeGetScalarConstant(
GetIRContext(), *GetTransformationContext(), words, int_type_id,
is_irrelevant)) {
return constant_id;
}
auto result = GetFuzzerContext()->GetFreshId();
ApplyTransformation(TransformationAddConstantScalar(result, int_type_id,
words, is_irrelevant));
return result;
}
uint32_t FuzzerPass::FindOrCreateFloatConstant(
const std::vector<uint32_t>& words, uint32_t width, bool is_irrelevant) {
auto float_type_id = FindOrCreateFloatType(width);
if (auto constant_id = fuzzerutil::MaybeGetScalarConstant(
GetIRContext(), *GetTransformationContext(), words, float_type_id,
is_irrelevant)) {
return constant_id;
}
auto result = GetFuzzerContext()->GetFreshId();
ApplyTransformation(TransformationAddConstantScalar(result, float_type_id,
words, is_irrelevant));
return result;
}
uint32_t FuzzerPass::FindOrCreateBoolConstant(bool value, bool is_irrelevant) {
auto bool_type_id = FindOrCreateBoolType();
if (auto constant_id = fuzzerutil::MaybeGetScalarConstant(
GetIRContext(), *GetTransformationContext(), {value ? 1u : 0u},
bool_type_id, is_irrelevant)) {
return constant_id;
}
auto result = GetFuzzerContext()->GetFreshId();
ApplyTransformation(
TransformationAddConstantBoolean(result, value, is_irrelevant));
return result;
}
uint32_t FuzzerPass::FindOrCreateConstant(const std::vector<uint32_t>& words,
uint32_t type_id,
bool is_irrelevant) {
assert(type_id && "Constant's type id can't be 0.");
const auto* type = GetIRContext()->get_type_mgr()->GetType(type_id);
assert(type && "Type does not exist.");
if (type->AsBool()) {
assert(words.size() == 1);
return FindOrCreateBoolConstant(words[0], is_irrelevant);
} else if (const auto* integer = type->AsInteger()) {
return FindOrCreateIntegerConstant(words, integer->width(),
integer->IsSigned(), is_irrelevant);
} else if (const auto* floating = type->AsFloat()) {
return FindOrCreateFloatConstant(words, floating->width(), is_irrelevant);
}
// This assertion will fail in debug build but not in release build
// so we return 0 to make compiler happy.
assert(false && "Constant type is not supported");
return 0;
}
uint32_t FuzzerPass::FindOrCreateCompositeConstant(
const std::vector<uint32_t>& component_ids, uint32_t type_id,
bool is_irrelevant) {
if (auto existing_constant = fuzzerutil::MaybeGetCompositeConstant(
GetIRContext(), *GetTransformationContext(), component_ids, type_id,
is_irrelevant)) {
return existing_constant;
}
uint32_t result = GetFuzzerContext()->GetFreshId();
ApplyTransformation(TransformationAddConstantComposite(
result, type_id, component_ids, is_irrelevant));
return result;
}
uint32_t FuzzerPass::FindOrCreateGlobalUndef(uint32_t type_id) {
for (auto& inst : GetIRContext()->types_values()) {
if (inst.opcode() == SpvOpUndef && inst.type_id() == type_id) {
return inst.result_id();
}
}
auto result = GetFuzzerContext()->GetFreshId();
ApplyTransformation(TransformationAddGlobalUndef(result, type_id));
return result;
}
uint32_t FuzzerPass::FindOrCreateNullConstant(uint32_t type_id) {
// Find existing declaration
opt::analysis::NullConstant null_constant(
GetIRContext()->get_type_mgr()->GetType(type_id));
auto existing_constant =
GetIRContext()->get_constant_mgr()->FindConstant(&null_constant);
// Return if found
if (existing_constant) {
return GetIRContext()
->get_constant_mgr()
->GetDefiningInstruction(existing_constant)
->result_id();
}
// Create new if not found
auto result = GetFuzzerContext()->GetFreshId();
ApplyTransformation(TransformationAddConstantNull(result, type_id));
return result;
}
std::pair<std::vector<uint32_t>, std::map<uint32_t, std::vector<uint32_t>>>
FuzzerPass::GetAvailableBasicTypesAndPointers(
SpvStorageClass storage_class) const {
// Records all of the basic types available in the module.
std::set<uint32_t> basic_types;
// For each basic type, records all the associated pointer types that target
// the basic type and that have |storage_class| as their storage class.
std::map<uint32_t, std::vector<uint32_t>> basic_type_to_pointers;
for (auto& inst : GetIRContext()->types_values()) {
// For each basic type that we come across, record type, and the fact that
// we cannot yet have seen any pointers that use the basic type as its
// pointee type.
//
// For pointer types with basic pointee types, associate the pointer type
// with the basic type.
switch (inst.opcode()) {
case SpvOpTypeBool:
case SpvOpTypeFloat:
case SpvOpTypeInt:
case SpvOpTypeMatrix:
case SpvOpTypeVector:
// These are all basic types.
basic_types.insert(inst.result_id());
basic_type_to_pointers.insert({inst.result_id(), {}});
break;
case SpvOpTypeArray:
// An array type is basic if its base type is basic.
if (basic_types.count(inst.GetSingleWordInOperand(0))) {
basic_types.insert(inst.result_id());
basic_type_to_pointers.insert({inst.result_id(), {}});
}
break;
case SpvOpTypeStruct: {
// A struct type is basic if it does not have the Block/BufferBlock
// decoration, and if all of its members are basic.
if (!fuzzerutil::HasBlockOrBufferBlockDecoration(GetIRContext(),
inst.result_id())) {
bool all_members_are_basic_types = true;
for (uint32_t i = 0; i < inst.NumInOperands(); i++) {
if (!basic_types.count(inst.GetSingleWordInOperand(i))) {
all_members_are_basic_types = false;
break;
}
}
if (all_members_are_basic_types) {
basic_types.insert(inst.result_id());
basic_type_to_pointers.insert({inst.result_id(), {}});
}
}
break;
}
case SpvOpTypePointer: {
// We are interested in the pointer if its pointee type is basic and it
// has the right storage class.
auto pointee_type = inst.GetSingleWordInOperand(1);
if (inst.GetSingleWordInOperand(0) == storage_class &&
basic_types.count(pointee_type)) {
// The pointer has the desired storage class, and its pointee type is
// a basic type, so we are interested in it. Associate it with its
// basic type.
basic_type_to_pointers.at(pointee_type).push_back(inst.result_id());
}
break;
}
default:
break;
}
}
return {{basic_types.begin(), basic_types.end()}, basic_type_to_pointers};
}
uint32_t FuzzerPass::FindOrCreateZeroConstant(
uint32_t scalar_or_composite_type_id, bool is_irrelevant) {
auto type_instruction =
GetIRContext()->get_def_use_mgr()->GetDef(scalar_or_composite_type_id);
assert(type_instruction && "The type instruction must exist.");
switch (type_instruction->opcode()) {
case SpvOpTypeBool:
return FindOrCreateBoolConstant(false, is_irrelevant);
case SpvOpTypeFloat: {
auto width = type_instruction->GetSingleWordInOperand(0);
auto num_words = (width + 32 - 1) / 32;
return FindOrCreateFloatConstant(std::vector<uint32_t>(num_words, 0),
width, is_irrelevant);
}
case SpvOpTypeInt: {
auto width = type_instruction->GetSingleWordInOperand(0);
auto num_words = (width + 32 - 1) / 32;
return FindOrCreateIntegerConstant(
std::vector<uint32_t>(num_words, 0), width,
type_instruction->GetSingleWordInOperand(1), is_irrelevant);
}
case SpvOpTypeArray: {
auto component_type_id = type_instruction->GetSingleWordInOperand(0);
auto num_components =
fuzzerutil::GetArraySize(*type_instruction, GetIRContext());
return FindOrCreateCompositeConstant(
std::vector<uint32_t>(
num_components,
FindOrCreateZeroConstant(component_type_id, is_irrelevant)),
scalar_or_composite_type_id, is_irrelevant);
}
case SpvOpTypeMatrix:
case SpvOpTypeVector: {
auto component_type_id = type_instruction->GetSingleWordInOperand(0);
auto num_components = type_instruction->GetSingleWordInOperand(1);
return FindOrCreateCompositeConstant(
std::vector<uint32_t>(
num_components,
FindOrCreateZeroConstant(component_type_id, is_irrelevant)),
scalar_or_composite_type_id, is_irrelevant);
}
case SpvOpTypeStruct: {
assert(!fuzzerutil::HasBlockOrBufferBlockDecoration(
GetIRContext(), scalar_or_composite_type_id) &&
"We do not construct constants of struct types decorated with "
"Block or BufferBlock.");
std::vector<uint32_t> field_zero_ids;
for (uint32_t index = 0; index < type_instruction->NumInOperands();
index++) {
field_zero_ids.push_back(FindOrCreateZeroConstant(
type_instruction->GetSingleWordInOperand(index), is_irrelevant));
}
return FindOrCreateCompositeConstant(
field_zero_ids, scalar_or_composite_type_id, is_irrelevant);
}
default:
assert(false && "Unknown type.");
return 0;
}
}
void FuzzerPass::MaybeAddUseToReplace(
opt::Instruction* use_inst, uint32_t use_index, uint32_t replacement_id,
std::vector<std::pair<protobufs::IdUseDescriptor, uint32_t>>*
uses_to_replace) {
// Only consider this use if it is in a block
if (!GetIRContext()->get_instr_block(use_inst)) {
return;
}
// Get the index of the operand restricted to input operands.
uint32_t in_operand_index =
fuzzerutil::InOperandIndexFromOperandIndex(*use_inst, use_index);
auto id_use_descriptor =
MakeIdUseDescriptorFromUse(GetIRContext(), use_inst, in_operand_index);
uses_to_replace->emplace_back(
std::make_pair(id_use_descriptor, replacement_id));
}
opt::BasicBlock* FuzzerPass::GetOrCreateSimpleLoopPreheader(
uint32_t header_id) {
auto header_block = fuzzerutil::MaybeFindBlock(GetIRContext(), header_id);
assert(header_block && header_block->IsLoopHeader() &&
"|header_id| should be the label id of a loop header");
auto predecessors = GetIRContext()->cfg()->preds(header_id);
assert(predecessors.size() >= 2 &&
"The block |header_id| should be reachable.");
auto function = header_block->GetParent();
if (predecessors.size() == 2) {
// The header has a single out-of-loop predecessor, which could be a
// preheader.
opt::BasicBlock* maybe_preheader;
if (GetIRContext()->GetDominatorAnalysis(function)->Dominates(
header_id, predecessors[0])) {
// The first predecessor is the back-edge block, because the header
// dominates it, so the second one is out of the loop.
maybe_preheader = &*function->FindBlock(predecessors[1]);
} else {
// The first predecessor is out of the loop.
maybe_preheader = &*function->FindBlock(predecessors[0]);
}
// |maybe_preheader| is a preheader if it branches unconditionally to
// the header. We also require it not to be a loop header.
if (maybe_preheader->terminator()->opcode() == SpvOpBranch &&
!maybe_preheader->IsLoopHeader()) {
return maybe_preheader;
}
}
// We need to add a preheader.
// Get a fresh id for the preheader.
uint32_t preheader_id = GetFuzzerContext()->GetFreshId();
// Get a fresh id for each OpPhi instruction, if there is more than one
// out-of-loop predecessor.
std::vector<uint32_t> phi_ids;
if (predecessors.size() > 2) {
header_block->ForEachPhiInst(
[this, &phi_ids](opt::Instruction* /* unused */) {
phi_ids.push_back(GetFuzzerContext()->GetFreshId());
});
}
// Add the preheader.
ApplyTransformation(
TransformationAddLoopPreheader(header_id, preheader_id, phi_ids));
// Make the newly-created preheader the new entry block.
return &*function->FindBlock(preheader_id);
}
opt::BasicBlock* FuzzerPass::SplitBlockAfterOpPhiOrOpVariable(
uint32_t block_id) {
auto block = fuzzerutil::MaybeFindBlock(GetIRContext(), block_id);
assert(block && "|block_id| must be a block label");
assert(!block->IsLoopHeader() && "|block_id| cannot be a loop header");
// Find the first non-OpPhi and non-OpVariable instruction.
auto non_phi_or_var_inst = &*block->begin();
while (non_phi_or_var_inst->opcode() == SpvOpPhi ||
non_phi_or_var_inst->opcode() == SpvOpVariable) {
non_phi_or_var_inst = non_phi_or_var_inst->NextNode();
}
// Split the block.
uint32_t new_block_id = GetFuzzerContext()->GetFreshId();
ApplyTransformation(TransformationSplitBlock(
MakeInstructionDescriptor(GetIRContext(), non_phi_or_var_inst),
new_block_id));
// We need to return the newly-created block.
return &*block->GetParent()->FindBlock(new_block_id);
}
uint32_t FuzzerPass::FindOrCreateLocalVariable(
uint32_t pointer_type_id, uint32_t function_id,
bool pointee_value_is_irrelevant) {
auto pointer_type = GetIRContext()->get_type_mgr()->GetType(pointer_type_id);
// No unused variables in release mode.
(void)pointer_type;
assert(pointer_type && pointer_type->AsPointer() &&
pointer_type->AsPointer()->storage_class() ==
SpvStorageClassFunction &&
"The pointer_type_id must refer to a defined pointer type with "
"storage class Function");
auto function = fuzzerutil::FindFunction(GetIRContext(), function_id);
assert(function && "The function must be defined.");
// First we try to find a suitable existing variable.
// All of the local variable declarations are located in the first block.
for (auto& instruction : *function->begin()) {
if (instruction.opcode() != SpvOpVariable) {
continue;
}
// The existing OpVariable must have type |pointer_type_id|.
if (instruction.type_id() != pointer_type_id) {
continue;
}
// Check if the found variable is marked with PointeeValueIsIrrelevant
// according to |pointee_value_is_irrelevant|.
if (GetTransformationContext()->GetFactManager()->PointeeValueIsIrrelevant(
instruction.result_id()) != pointee_value_is_irrelevant) {
continue;
}
return instruction.result_id();
}
// No such variable was found. Apply a transformation to get one.
uint32_t pointee_type_id = fuzzerutil::GetPointeeTypeIdFromPointerType(
GetIRContext(), pointer_type_id);
uint32_t result_id = GetFuzzerContext()->GetFreshId();
ApplyTransformation(TransformationAddLocalVariable(
result_id, pointer_type_id, function_id,
FindOrCreateZeroConstant(pointee_type_id, pointee_value_is_irrelevant),
pointee_value_is_irrelevant));
return result_id;
}
uint32_t FuzzerPass::FindOrCreateGlobalVariable(
uint32_t pointer_type_id, bool pointee_value_is_irrelevant) {
auto pointer_type = GetIRContext()->get_type_mgr()->GetType(pointer_type_id);
// No unused variables in release mode.
(void)pointer_type;
assert(
pointer_type && pointer_type->AsPointer() &&
(pointer_type->AsPointer()->storage_class() == SpvStorageClassPrivate ||
pointer_type->AsPointer()->storage_class() ==
SpvStorageClassWorkgroup) &&
"The pointer_type_id must refer to a defined pointer type with storage "
"class Private or Workgroup");
// First we try to find a suitable existing variable.
for (auto& instruction : GetIRContext()->module()->types_values()) {
if (instruction.opcode() != SpvOpVariable) {
continue;
}
// The existing OpVariable must have type |pointer_type_id|.
if (instruction.type_id() != pointer_type_id) {
continue;
}
// Check if the found variable is marked with PointeeValueIsIrrelevant
// according to |pointee_value_is_irrelevant|.
if (GetTransformationContext()->GetFactManager()->PointeeValueIsIrrelevant(
instruction.result_id()) != pointee_value_is_irrelevant) {
continue;
}
return instruction.result_id();
}
// No such variable was found. Apply a transformation to get one.
uint32_t pointee_type_id = fuzzerutil::GetPointeeTypeIdFromPointerType(
GetIRContext(), pointer_type_id);
auto storage_class = fuzzerutil::GetStorageClassFromPointerType(
GetIRContext(), pointer_type_id);
uint32_t result_id = GetFuzzerContext()->GetFreshId();
// A variable with storage class Workgroup shouldn't have an initializer.
if (storage_class == SpvStorageClassWorkgroup) {
ApplyTransformation(TransformationAddGlobalVariable(
result_id, pointer_type_id, SpvStorageClassWorkgroup, 0,
pointee_value_is_irrelevant));
} else {
ApplyTransformation(TransformationAddGlobalVariable(
result_id, pointer_type_id, SpvStorageClassPrivate,
FindOrCreateZeroConstant(pointee_type_id, pointee_value_is_irrelevant),
pointee_value_is_irrelevant));
}
return result_id;
}
} // namespace fuzz
} // namespace spvtools