|  | // 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( | 
|  | SpvCapabilityFloat64) && | 
|  | ir_context->get_feature_mgr()->HasCapability(SpvCapabilityInt64); | 
|  | case 16: | 
|  | return ir_context->get_feature_mgr()->HasCapability( | 
|  | SpvCapabilityFloat16) && | 
|  | ir_context->get_feature_mgr()->HasCapability(SpvCapabilityInt16); | 
|  | 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(SpvOpIAdd, 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() != SpvOpUndef && | 
|  | !GetTransformationContext() | 
|  | ->GetFactManager() | 
|  | ->IdIsIrrelevant(instruction->result_id()); | 
|  | }); | 
|  |  | 
|  | // Try the opcodes for which we know how to make ids at random until | 
|  | // something works. | 
|  | std::vector<SpvOp> candidate_opcodes = { | 
|  | SpvOpIAdd,        SpvOpISub,        SpvOpLogicalNot, SpvOpSNegate, | 
|  | SpvOpConvertUToF, SpvOpConvertSToF, SpvOpBitcast}; | 
|  | do { | 
|  | auto opcode = | 
|  | GetFuzzerContext()->RemoveAtRandomIndex(&candidate_opcodes); | 
|  | switch (opcode) { | 
|  | case SpvOpConvertSToF: | 
|  | case SpvOpConvertUToF: { | 
|  | 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 SpvOpBitcast: { | 
|  | 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 SpvOpIAdd: | 
|  | case SpvOpISub: { | 
|  | // 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 SpvOpLogicalNot: { | 
|  | // 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 SpvOpSNegate: { | 
|  | // 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 |