| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416 |
- // 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
|