SPIRV-Tools/source/fuzz/fuzzer_pass_construct_composites.cpp
Alastair Donaldson 61e256c9c4
spirv-fuzz: Efficiency improvements to fuzzer pass (#4188)
FuzzerPassConstructComposites is adapted to use AvailableInstructions
to manage available instructions, and to use zero constants when
trying to construct a composite for which not all fields can otherwise
be constructed. The change uncovered some cases where we create
structs and arrays with struct fields or components that are
block-decorated; these possibilities have been eliminated.
2021-03-27 12:15:59 +00:00

351 lines
15 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_construct_composites.h"
#include <memory>
#include "source/fuzz/available_instructions.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/transformation_composite_construct.h"
namespace spvtools {
namespace fuzz {
FuzzerPassConstructComposites::FuzzerPassConstructComposites(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations)
: FuzzerPass(ir_context, transformation_context, fuzzer_context,
transformations) {}
void FuzzerPassConstructComposites::Apply() {
// Gather up the ids of all composite types, but skip block-/buffer
// block-decorated struct types.
std::vector<uint32_t> composite_type_ids;
for (auto& inst : GetIRContext()->types_values()) {
if (fuzzerutil::IsCompositeType(
GetIRContext()->get_type_mgr()->GetType(inst.result_id())) &&
!fuzzerutil::HasBlockOrBufferBlockDecoration(GetIRContext(),
inst.result_id())) {
composite_type_ids.push_back(inst.result_id());
}
}
if (composite_type_ids.empty()) {
// There are no composite types, so this fuzzer pass cannot do anything.
return;
}
AvailableInstructions available_composite_constituents(
GetIRContext(),
[this](opt::IRContext* ir_context, opt::Instruction* inst) -> bool {
if (!inst->result_id() || !inst->type_id()) {
return false;
}
// If the id is irrelevant, we can use it since it will not
// participate in DataSynonym fact. Otherwise, we should be able
// to produce a synonym out of the id.
return GetTransformationContext()->GetFactManager()->IdIsIrrelevant(
inst->result_id()) ||
fuzzerutil::CanMakeSynonymOf(ir_context,
*GetTransformationContext(), inst);
});
ForEachInstructionWithInstructionDescriptor(
[this, &available_composite_constituents, &composite_type_ids](
opt::Function* /*unused*/, opt::BasicBlock* /*unused*/,
opt::BasicBlock::iterator inst_it,
const protobufs::InstructionDescriptor& instruction_descriptor)
-> void {
// Randomly decide whether to try inserting a composite construction
// here.
if (!GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfConstructingComposite())) {
return;
}
// Check whether it is legitimate to insert a composite construction
// before the instruction.
if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(
SpvOpCompositeConstruct, inst_it)) {
return;
}
// For each instruction that is available at this program point (i.e. an
// instruction that is global or whose definition strictly dominates the
// program point) and suitable for making a synonym of, associate it
// with the id of its result type.
TypeIdToInstructions type_id_to_available_instructions;
auto available_instructions =
available_composite_constituents.GetAvailableBeforeInstruction(
&*inst_it);
for (uint32_t available_instruction_index = 0;
available_instruction_index < available_instructions.size();
available_instruction_index++) {
opt::Instruction* inst =
available_instructions[available_instruction_index];
type_id_to_available_instructions[inst->type_id()].push_back(
inst->result_id());
}
// At this point, |composite_type_ids| captures all the composite types
// we could try to create, while |type_id_to_available_instructions|
// captures all the available result ids we might use, organized by
// type.
// Now we choose a composite type to construct, building it from
// available constituent components and using zero constants if suitable
// components are not available.
std::vector<uint32_t> constructor_arguments;
uint32_t chosen_composite_type =
composite_type_ids[GetFuzzerContext()->RandomIndex(
composite_type_ids)];
// Construct a composite of this type, using an appropriate helper
// method depending on the kind of composite type.
auto composite_type_inst =
GetIRContext()->get_def_use_mgr()->GetDef(chosen_composite_type);
switch (composite_type_inst->opcode()) {
case SpvOpTypeArray:
constructor_arguments = FindComponentsToConstructArray(
*composite_type_inst, type_id_to_available_instructions);
break;
case SpvOpTypeMatrix:
constructor_arguments = FindComponentsToConstructMatrix(
*composite_type_inst, type_id_to_available_instructions);
break;
case SpvOpTypeStruct:
constructor_arguments = FindComponentsToConstructStruct(
*composite_type_inst, type_id_to_available_instructions);
break;
case SpvOpTypeVector:
constructor_arguments = FindComponentsToConstructVector(
*composite_type_inst, type_id_to_available_instructions);
break;
default:
assert(false &&
"The space of possible composite types should be covered "
"by the above cases.");
break;
}
assert(!constructor_arguments.empty());
// Make and apply a transformation.
ApplyTransformation(TransformationCompositeConstruct(
chosen_composite_type, constructor_arguments,
instruction_descriptor, GetFuzzerContext()->GetFreshId()));
});
}
std::vector<uint32_t>
FuzzerPassConstructComposites::FindComponentsToConstructArray(
const opt::Instruction& array_type_instruction,
const TypeIdToInstructions& type_id_to_available_instructions) {
assert(array_type_instruction.opcode() == SpvOpTypeArray &&
"Precondition: instruction must be an array type.");
// Get the element type for the array.
auto element_type_id = array_type_instruction.GetSingleWordInOperand(0);
// Get all instructions at our disposal that compute something of this element
// type.
auto available_instructions =
type_id_to_available_instructions.find(element_type_id);
uint32_t array_length =
GetIRContext()
->get_def_use_mgr()
->GetDef(array_type_instruction.GetSingleWordInOperand(1))
->GetSingleWordInOperand(0);
std::vector<uint32_t> result;
for (uint32_t index = 0; index < array_length; index++) {
if (available_instructions == type_id_to_available_instructions.cend()) {
// No suitable instructions are available, so use a zero constant
result.push_back(FindOrCreateZeroConstant(element_type_id, true));
} else {
result.push_back(
available_instructions->second[GetFuzzerContext()->RandomIndex(
available_instructions->second)]);
}
}
return result;
}
std::vector<uint32_t>
FuzzerPassConstructComposites::FindComponentsToConstructMatrix(
const opt::Instruction& matrix_type_instruction,
const TypeIdToInstructions& type_id_to_available_instructions) {
assert(matrix_type_instruction.opcode() == SpvOpTypeMatrix &&
"Precondition: instruction must be a matrix type.");
// Get the element type for the matrix.
auto element_type_id = matrix_type_instruction.GetSingleWordInOperand(0);
// Get all instructions at our disposal that compute something of this element
// type.
auto available_instructions =
type_id_to_available_instructions.find(element_type_id);
std::vector<uint32_t> result;
for (uint32_t index = 0;
index < matrix_type_instruction.GetSingleWordInOperand(1); index++) {
if (available_instructions == type_id_to_available_instructions.cend()) {
// No suitable components are available, so use a zero constant.
result.push_back(FindOrCreateZeroConstant(element_type_id, true));
} else {
result.push_back(
available_instructions->second[GetFuzzerContext()->RandomIndex(
available_instructions->second)]);
}
}
return result;
}
std::vector<uint32_t>
FuzzerPassConstructComposites::FindComponentsToConstructStruct(
const opt::Instruction& struct_type_instruction,
const TypeIdToInstructions& type_id_to_available_instructions) {
assert(struct_type_instruction.opcode() == SpvOpTypeStruct &&
"Precondition: instruction must be a struct type.");
std::vector<uint32_t> result;
// Consider the type of each field of the struct.
for (uint32_t in_operand_index = 0;
in_operand_index < struct_type_instruction.NumInOperands();
in_operand_index++) {
auto element_type_id =
struct_type_instruction.GetSingleWordInOperand(in_operand_index);
// Find the instructions at our disposal that compute something of the field
// type.
auto available_instructions =
type_id_to_available_instructions.find(element_type_id);
if (available_instructions == type_id_to_available_instructions.cend()) {
// No suitable component is available for this element type, so use a zero
// constant.
result.push_back(FindOrCreateZeroConstant(element_type_id, true));
} else {
result.push_back(
available_instructions->second[GetFuzzerContext()->RandomIndex(
available_instructions->second)]);
}
}
return result;
}
std::vector<uint32_t>
FuzzerPassConstructComposites::FindComponentsToConstructVector(
const opt::Instruction& vector_type_instruction,
const TypeIdToInstructions& type_id_to_available_instructions) {
assert(vector_type_instruction.opcode() == SpvOpTypeVector &&
"Precondition: instruction must be a vector type.");
// Get details of the type underlying the vector, and the width of the vector,
// for convenience.
auto element_type_id = vector_type_instruction.GetSingleWordInOperand(0);
auto element_type = GetIRContext()->get_type_mgr()->GetType(element_type_id);
auto element_count = vector_type_instruction.GetSingleWordInOperand(1);
// Collect a mapping, from type id to width, for scalar/vector types that are
// smaller in width than |vector_type|, but that have the same underlying
// type. For example, if |vector_type| is vec4, the mapping will be:
// { float -> 1, vec2 -> 2, vec3 -> 3 }
// The mapping will have missing entries if some of these types do not exist.
std::map<uint32_t, uint32_t> smaller_vector_type_id_to_width;
// Add the underlying type. This id must exist, in order for |vector_type| to
// exist.
smaller_vector_type_id_to_width[element_type_id] = 1;
// Now add every vector type with width at least 2, and less than the width of
// |vector_type|.
for (uint32_t width = 2; width < element_count; width++) {
opt::analysis::Vector smaller_vector_type(element_type, width);
auto smaller_vector_type_id =
GetIRContext()->get_type_mgr()->GetId(&smaller_vector_type);
// We might find that there is no declared type of this smaller width.
// For example, a module can declare vec4 without having declared vec2 or
// vec3.
if (smaller_vector_type_id) {
smaller_vector_type_id_to_width[smaller_vector_type_id] = width;
}
}
// Now we know the types that are available to us, we set about populating a
// vector of the right length. We do this by deciding, with no order in mind,
// which instructions we will use to populate the vector, and subsequently
// randomly choosing an order. This is to avoid biasing construction of
// vectors with smaller vectors to the left and scalars to the right. That is
// a concern because, e.g. in the case of populating a vec4, if we populate
// the constructor instructions left-to-right, we can always choose a vec3 to
// construct the first three elements, but can only choose a vec3 to construct
// the last three elements if we chose a float to construct the first element
// (otherwise there will not be space left for a vec3).
uint32_t vector_slots_used = 0;
// The instructions result ids we will use to construct the vector, in no
// particular order at this stage.
std::vector<uint32_t> result;
while (vector_slots_used < element_count) {
std::vector<uint32_t> instructions_to_choose_from;
for (auto& entry : smaller_vector_type_id_to_width) {
if (entry.second >
std::min(element_count - 1, element_count - vector_slots_used)) {
continue;
}
auto available_instructions =
type_id_to_available_instructions.find(entry.first);
if (available_instructions == type_id_to_available_instructions.cend()) {
continue;
}
instructions_to_choose_from.insert(instructions_to_choose_from.end(),
available_instructions->second.begin(),
available_instructions->second.end());
}
// If there are no instructions to choose from then use a zero constant,
// otherwise select one of the instructions at random.
uint32_t id_of_instruction_to_use =
instructions_to_choose_from.empty()
? FindOrCreateZeroConstant(element_type_id, true)
: instructions_to_choose_from[GetFuzzerContext()->RandomIndex(
instructions_to_choose_from)];
opt::Instruction* instruction_to_use =
GetIRContext()->get_def_use_mgr()->GetDef(id_of_instruction_to_use);
result.push_back(instruction_to_use->result_id());
auto chosen_type =
GetIRContext()->get_type_mgr()->GetType(instruction_to_use->type_id());
if (chosen_type->AsVector()) {
assert(chosen_type->AsVector()->element_type() == element_type);
assert(chosen_type->AsVector()->element_count() < element_count);
assert(chosen_type->AsVector()->element_count() <=
element_count - vector_slots_used);
vector_slots_used += chosen_type->AsVector()->element_count();
} else {
assert(chosen_type == element_type);
vector_slots_used += 1;
}
}
assert(vector_slots_used == element_count);
GetFuzzerContext()->Shuffle(&result);
return result;
}
} // namespace fuzz
} // namespace spvtools