SPIRV-Tools/source/fuzz/fuzzer_pass_add_equation_instructions.cpp
alan-baker d35a78db57
Switch SPIRV-Tools to use spirv.hpp11 internally (#4981)
Fixes #4960

* Switches to using enum classes with an underlying type to avoid
  undefined behaviour
2022-11-04 17:27:10 -04:00

417 lines
17 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_add_equation_instructions.h"
#include <vector>
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/transformation_equation_instruction.h"
namespace spvtools {
namespace fuzz {
namespace {
bool IsBitWidthSupported(opt::IRContext* ir_context, uint32_t bit_width) {
switch (bit_width) {
case 32:
return true;
case 64:
return ir_context->get_feature_mgr()->HasCapability(
spv::Capability::Float64) &&
ir_context->get_feature_mgr()->HasCapability(
spv::Capability::Int64);
case 16:
return ir_context->get_feature_mgr()->HasCapability(
spv::Capability::Float16) &&
ir_context->get_feature_mgr()->HasCapability(
spv::Capability::Int16);
default:
return false;
}
}
} // namespace
FuzzerPassAddEquationInstructions::FuzzerPassAddEquationInstructions(
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 FuzzerPassAddEquationInstructions::Apply() {
ForEachInstructionWithInstructionDescriptor(
[this](opt::Function* function, opt::BasicBlock* block,
opt::BasicBlock::iterator inst_it,
const protobufs::InstructionDescriptor& instruction_descriptor) {
if (!GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfAddingEquationInstruction())) {
return;
}
// Check that it is OK to add an equation instruction before the given
// instruction in principle - e.g. check that this does not lead to
// inserting before an OpVariable or OpPhi instruction. We use OpIAdd
// as an example opcode for this check, to be representative of *some*
// opcode that defines an equation, even though we may choose a
// different opcode below.
if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(spv::Op::OpIAdd,
inst_it)) {
return;
}
// Get all available instructions with result ids and types that are not
// OpUndef.
std::vector<opt::Instruction*> available_instructions =
FindAvailableInstructions(
function, block, inst_it,
[this](opt::IRContext* /*unused*/,
opt::Instruction* instruction) -> bool {
return instruction->result_id() && instruction->type_id() &&
instruction->opcode() != spv::Op::OpUndef &&
!GetTransformationContext()
->GetFactManager()
->IdIsIrrelevant(instruction->result_id());
});
// Try the opcodes for which we know how to make ids at random until
// something works.
std::vector<spv::Op> candidate_opcodes = {
spv::Op::OpIAdd, spv::Op::OpISub, spv::Op::OpLogicalNot,
spv::Op::OpSNegate, spv::Op::OpConvertUToF, spv::Op::OpConvertSToF,
spv::Op::OpBitcast};
do {
auto opcode =
GetFuzzerContext()->RemoveAtRandomIndex(&candidate_opcodes);
switch (opcode) {
case spv::Op::OpConvertSToF:
case spv::Op::OpConvertUToF: {
std::vector<const opt::Instruction*> candidate_instructions;
for (const auto* inst :
GetIntegerInstructions(available_instructions)) {
const auto* type =
GetIRContext()->get_type_mgr()->GetType(inst->type_id());
assert(type && "|inst| has invalid type");
if (const auto* vector_type = type->AsVector()) {
type = vector_type->element_type();
}
if (IsBitWidthSupported(GetIRContext(),
type->AsInteger()->width())) {
candidate_instructions.push_back(inst);
}
}
if (candidate_instructions.empty()) {
break;
}
const auto* operand =
candidate_instructions[GetFuzzerContext()->RandomIndex(
candidate_instructions)];
const auto* type =
GetIRContext()->get_type_mgr()->GetType(operand->type_id());
assert(type && "Operand has invalid type");
// Make sure a result type exists in the module.
if (const auto* vector = type->AsVector()) {
// We store element count in a separate variable since the
// call FindOrCreate* functions below might invalidate
// |vector| pointer.
const auto element_count = vector->element_count();
FindOrCreateVectorType(
FindOrCreateFloatType(
vector->element_type()->AsInteger()->width()),
element_count);
} else {
FindOrCreateFloatType(type->AsInteger()->width());
}
ApplyTransformation(TransformationEquationInstruction(
GetFuzzerContext()->GetFreshId(), opcode,
{operand->result_id()}, instruction_descriptor));
return;
}
case spv::Op::OpBitcast: {
const auto candidate_instructions =
GetNumericalInstructions(available_instructions);
if (!candidate_instructions.empty()) {
const auto* operand_inst =
candidate_instructions[GetFuzzerContext()->RandomIndex(
candidate_instructions)];
const auto* operand_type =
GetIRContext()->get_type_mgr()->GetType(
operand_inst->type_id());
assert(operand_type && "Operand instruction has invalid type");
// Make sure a result type exists in the module.
//
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3539):
// The only constraint on the types of OpBitcast's parameters
// is that they must have the same number of bits. Consider
// improving the code below to support this in full.
if (const auto* vector = operand_type->AsVector()) {
// We store element count in a separate variable since the
// call FindOrCreate* functions below might invalidate
// |vector| pointer.
const auto element_count = vector->element_count();
uint32_t element_type_id;
if (const auto* int_type =
vector->element_type()->AsInteger()) {
element_type_id = FindOrCreateFloatType(int_type->width());
} else {
assert(vector->element_type()->AsFloat() &&
"Vector must have numerical elements");
element_type_id = FindOrCreateIntegerType(
vector->element_type()->AsFloat()->width(),
GetFuzzerContext()->ChooseEven());
}
FindOrCreateVectorType(element_type_id, element_count);
} else if (const auto* int_type = operand_type->AsInteger()) {
FindOrCreateFloatType(int_type->width());
} else {
assert(operand_type->AsFloat() &&
"Operand is not a scalar of numerical type");
FindOrCreateIntegerType(operand_type->AsFloat()->width(),
GetFuzzerContext()->ChooseEven());
}
ApplyTransformation(TransformationEquationInstruction(
GetFuzzerContext()->GetFreshId(), opcode,
{operand_inst->result_id()}, instruction_descriptor));
return;
}
} break;
case spv::Op::OpIAdd:
case spv::Op::OpISub: {
// Instructions of integer (scalar or vector) result type are
// suitable for these opcodes.
auto integer_instructions =
GetIntegerInstructions(available_instructions);
if (!integer_instructions.empty()) {
// There is at least one such instruction, so pick one at random
// for the LHS of an equation.
auto lhs = integer_instructions.at(
GetFuzzerContext()->RandomIndex(integer_instructions));
// For the RHS, we can use any instruction with an integer
// scalar/vector result type of the same number of components
// and the same bit-width for the underlying integer type.
// Work out the element count and bit-width.
auto lhs_type =
GetIRContext()->get_type_mgr()->GetType(lhs->type_id());
uint32_t lhs_element_count;
uint32_t lhs_bit_width;
if (lhs_type->AsVector()) {
lhs_element_count = lhs_type->AsVector()->element_count();
lhs_bit_width = lhs_type->AsVector()
->element_type()
->AsInteger()
->width();
} else {
lhs_element_count = 1;
lhs_bit_width = lhs_type->AsInteger()->width();
}
// Get all the instructions that match on element count and
// bit-width.
auto candidate_rhs_instructions = RestrictToElementBitWidth(
RestrictToVectorWidth(integer_instructions,
lhs_element_count),
lhs_bit_width);
// Choose a RHS instruction at random; there is guaranteed to
// be at least one choice as the LHS will be available.
auto rhs = candidate_rhs_instructions.at(
GetFuzzerContext()->RandomIndex(
candidate_rhs_instructions));
// Add the equation instruction.
ApplyTransformation(TransformationEquationInstruction(
GetFuzzerContext()->GetFreshId(), opcode,
{lhs->result_id(), rhs->result_id()},
instruction_descriptor));
return;
}
break;
}
case spv::Op::OpLogicalNot: {
// Choose any available instruction of boolean scalar/vector
// result type and equate its negation with a fresh id.
auto boolean_instructions =
GetBooleanInstructions(available_instructions);
if (!boolean_instructions.empty()) {
ApplyTransformation(TransformationEquationInstruction(
GetFuzzerContext()->GetFreshId(), opcode,
{boolean_instructions
.at(GetFuzzerContext()->RandomIndex(
boolean_instructions))
->result_id()},
instruction_descriptor));
return;
}
break;
}
case spv::Op::OpSNegate: {
// Similar to OpLogicalNot, but for signed integer negation.
auto integer_instructions =
GetIntegerInstructions(available_instructions);
if (!integer_instructions.empty()) {
ApplyTransformation(TransformationEquationInstruction(
GetFuzzerContext()->GetFreshId(), opcode,
{integer_instructions
.at(GetFuzzerContext()->RandomIndex(
integer_instructions))
->result_id()},
instruction_descriptor));
return;
}
break;
}
default:
assert(false && "Unexpected opcode.");
break;
}
} while (!candidate_opcodes.empty());
// Reaching here means that we did not manage to apply any
// transformation at this point of the module.
});
}
std::vector<opt::Instruction*>
FuzzerPassAddEquationInstructions::GetIntegerInstructions(
const std::vector<opt::Instruction*>& instructions) const {
std::vector<opt::Instruction*> result;
for (auto& inst : instructions) {
auto type = GetIRContext()->get_type_mgr()->GetType(inst->type_id());
if (type->AsInteger() ||
(type->AsVector() && type->AsVector()->element_type()->AsInteger())) {
result.push_back(inst);
}
}
return result;
}
std::vector<opt::Instruction*>
FuzzerPassAddEquationInstructions::GetFloatInstructions(
const std::vector<opt::Instruction*>& instructions) const {
std::vector<opt::Instruction*> result;
for (auto& inst : instructions) {
auto type = GetIRContext()->get_type_mgr()->GetType(inst->type_id());
if (type->AsFloat() ||
(type->AsVector() && type->AsVector()->element_type()->AsFloat())) {
result.push_back(inst);
}
}
return result;
}
std::vector<opt::Instruction*>
FuzzerPassAddEquationInstructions::GetBooleanInstructions(
const std::vector<opt::Instruction*>& instructions) const {
std::vector<opt::Instruction*> result;
for (auto& inst : instructions) {
auto type = GetIRContext()->get_type_mgr()->GetType(inst->type_id());
if (type->AsBool() ||
(type->AsVector() && type->AsVector()->element_type()->AsBool())) {
result.push_back(inst);
}
}
return result;
}
std::vector<opt::Instruction*>
FuzzerPassAddEquationInstructions::RestrictToVectorWidth(
const std::vector<opt::Instruction*>& instructions,
uint32_t vector_width) const {
std::vector<opt::Instruction*> result;
for (auto& inst : instructions) {
auto type = GetIRContext()->get_type_mgr()->GetType(inst->type_id());
// Get the vector width of |inst|, which is 1 if |inst| is a scalar and is
// otherwise derived from its vector type.
uint32_t other_vector_width =
type->AsVector() ? type->AsVector()->element_count() : 1;
// Keep |inst| if the vector widths match.
if (vector_width == other_vector_width) {
result.push_back(inst);
}
}
return result;
}
std::vector<opt::Instruction*>
FuzzerPassAddEquationInstructions::RestrictToElementBitWidth(
const std::vector<opt::Instruction*>& instructions,
uint32_t bit_width) const {
std::vector<opt::Instruction*> result;
for (auto& inst : instructions) {
const opt::analysis::Type* type =
GetIRContext()->get_type_mgr()->GetType(inst->type_id());
if (type->AsVector()) {
type = type->AsVector()->element_type();
}
assert((type->AsInteger() || type->AsFloat()) &&
"Precondition: all input instructions must "
"have integer or float scalar or vector type.");
if ((type->AsInteger() && type->AsInteger()->width() == bit_width) ||
(type->AsFloat() && type->AsFloat()->width() == bit_width)) {
result.push_back(inst);
}
}
return result;
}
std::vector<opt::Instruction*>
FuzzerPassAddEquationInstructions::GetNumericalInstructions(
const std::vector<opt::Instruction*>& instructions) const {
std::vector<opt::Instruction*> result;
for (auto* inst : instructions) {
const auto* type = GetIRContext()->get_type_mgr()->GetType(inst->type_id());
assert(type && "Instruction has invalid type");
if (const auto* vector_type = type->AsVector()) {
type = vector_type->element_type();
}
if (!type->AsInteger() && !type->AsFloat()) {
// Only numerical scalars or vectors of numerical components are
// supported.
continue;
}
if (!IsBitWidthSupported(GetIRContext(), type->AsInteger()
? type->AsInteger()->width()
: type->AsFloat()->width())) {
continue;
}
result.push_back(inst);
}
return result;
}
} // namespace fuzz
} // namespace spvtools