| // 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 "source/fuzz/fuzzer_util.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_scalar.h" | |
| #include "source/fuzz/transformation_add_global_undef.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_vector.h" | |
| namespace spvtools { | |
| namespace fuzz { | |
| FuzzerPass::FuzzerPass(opt::IRContext* ir_context, FactManager* fact_manager, | |
| FuzzerContext* fuzzer_context, | |
| protobufs::TransformationSequence* transformations) | |
| : ir_context_(ir_context), | |
| fact_manager_(fact_manager), | |
| 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( | |
| 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()) { | |
| for (auto& block : function) { | |
| // 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(&function, &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; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| uint32_t FuzzerPass::FindOrCreateBoolType() { | |
| opt::analysis::Bool bool_type; | |
| auto existing_id = GetIRContext()->get_type_mgr()->GetId(&bool_type); | |
| if (existing_id) { | |
| return existing_id; | |
| } | |
| auto result = GetFuzzerContext()->GetFreshId(); | |
| ApplyTransformation(TransformationAddTypeBoolean(result)); | |
| return result; | |
| } | |
| uint32_t FuzzerPass::FindOrCreate32BitIntegerType(bool is_signed) { | |
| opt::analysis::Integer int_type(32, 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, 32, is_signed)); | |
| return result; | |
| } | |
| uint32_t FuzzerPass::FindOrCreate32BitFloatType() { | |
| opt::analysis::Float float_type(32); | |
| auto existing_id = GetIRContext()->get_type_mgr()->GetId(&float_type); | |
| if (existing_id) { | |
| return existing_id; | |
| } | |
| auto result = GetFuzzerContext()->GetFreshId(); | |
| ApplyTransformation(TransformationAddTypeFloat(result, 32)); | |
| 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(FindOrCreate32BitFloatType(), 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::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::FindOrCreatePointerTo32BitIntegerType( | |
| bool is_signed, SpvStorageClass storage_class) { | |
| return FindOrCreatePointerType(FindOrCreate32BitIntegerType(is_signed), | |
| storage_class); | |
| } | |
| uint32_t FuzzerPass::FindOrCreate32BitIntegerConstant(uint32_t word, | |
| bool is_signed) { | |
| auto uint32_type_id = FindOrCreate32BitIntegerType(is_signed); | |
| opt::analysis::IntConstant int_constant( | |
| GetIRContext()->get_type_mgr()->GetType(uint32_type_id)->AsInteger(), | |
| {word}); | |
| auto existing_constant = | |
| GetIRContext()->get_constant_mgr()->FindConstant(&int_constant); | |
| if (existing_constant) { | |
| return GetIRContext() | |
| ->get_constant_mgr() | |
| ->GetDefiningInstruction(existing_constant) | |
| ->result_id(); | |
| } | |
| auto result = GetFuzzerContext()->GetFreshId(); | |
| ApplyTransformation( | |
| TransformationAddConstantScalar(result, uint32_type_id, {word})); | |
| return result; | |
| } | |
| uint32_t FuzzerPass::FindOrCreate32BitFloatConstant(uint32_t word) { | |
| auto float_type_id = FindOrCreate32BitFloatType(); | |
| opt::analysis::FloatConstant float_constant( | |
| GetIRContext()->get_type_mgr()->GetType(float_type_id)->AsFloat(), | |
| {word}); | |
| auto existing_constant = | |
| GetIRContext()->get_constant_mgr()->FindConstant(&float_constant); | |
| if (existing_constant) { | |
| return GetIRContext() | |
| ->get_constant_mgr() | |
| ->GetDefiningInstruction(existing_constant) | |
| ->result_id(); | |
| } | |
| auto result = GetFuzzerContext()->GetFreshId(); | |
| ApplyTransformation( | |
| TransformationAddConstantScalar(result, float_type_id, {word})); | |
| return result; | |
| } | |
| uint32_t FuzzerPass::FindOrCreateBoolConstant(bool value) { | |
| auto bool_type_id = FindOrCreateBoolType(); | |
| opt::analysis::BoolConstant bool_constant( | |
| GetIRContext()->get_type_mgr()->GetType(bool_type_id)->AsBool(), value); | |
| auto existing_constant = | |
| GetIRContext()->get_constant_mgr()->FindConstant(&bool_constant); | |
| if (existing_constant) { | |
| return GetIRContext() | |
| ->get_constant_mgr() | |
| ->GetDefiningInstruction(existing_constant) | |
| ->result_id(); | |
| } | |
| auto result = GetFuzzerContext()->GetFreshId(); | |
| ApplyTransformation(TransformationAddConstantBoolean(result, value)); | |
| 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; | |
| } | |
| std::pair<std::vector<uint32_t>, std::map<uint32_t, std::vector<uint32_t>>> | |
| FuzzerPass::GetAvailableBaseTypesAndPointers( | |
| SpvStorageClass storage_class) const { | |
| // Records all of the base types available in the module. | |
| std::vector<uint32_t> base_types; | |
| // For each base type, records all the associated pointer types that target | |
| // that base type and that have |storage_class| as their storage class. | |
| std::map<uint32_t, std::vector<uint32_t>> base_type_to_pointers; | |
| for (auto& inst : GetIRContext()->types_values()) { | |
| switch (inst.opcode()) { | |
| case SpvOpTypeArray: | |
| case SpvOpTypeBool: | |
| case SpvOpTypeFloat: | |
| case SpvOpTypeInt: | |
| case SpvOpTypeMatrix: | |
| case SpvOpTypeStruct: | |
| case SpvOpTypeVector: | |
| // These types are suitable as pointer base types. Record the type, | |
| // and the fact that we cannot yet have seen any pointers that use this | |
| // as its base type. | |
| base_types.push_back(inst.result_id()); | |
| base_type_to_pointers.insert({inst.result_id(), {}}); | |
| break; | |
| case SpvOpTypePointer: | |
| if (inst.GetSingleWordInOperand(0) == storage_class) { | |
| // The pointer has the desired storage class, so we are interested in | |
| // it. Associate it with its base type. | |
| base_type_to_pointers.at(inst.GetSingleWordInOperand(1)) | |
| .push_back(inst.result_id()); | |
| } | |
| break; | |
| default: | |
| break; | |
| } | |
| } | |
| return {base_types, base_type_to_pointers}; | |
| } | |
| uint32_t FuzzerPass::FindOrCreateZeroConstant( | |
| uint32_t scalar_or_composite_type_id) { | |
| 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); | |
| case SpvOpTypeFloat: | |
| return FindOrCreate32BitFloatConstant(0); | |
| case SpvOpTypeInt: | |
| return FindOrCreate32BitIntegerConstant( | |
| 0, type_instruction->GetSingleWordInOperand(1) != 0); | |
| case SpvOpTypeArray: { | |
| return GetZeroConstantForHomogeneousComposite( | |
| *type_instruction, type_instruction->GetSingleWordInOperand(0), | |
| fuzzerutil::GetArraySize(*type_instruction, GetIRContext())); | |
| } | |
| case SpvOpTypeMatrix: | |
| case SpvOpTypeVector: { | |
| return GetZeroConstantForHomogeneousComposite( | |
| *type_instruction, type_instruction->GetSingleWordInOperand(0), | |
| type_instruction->GetSingleWordInOperand(1)); | |
| } | |
| case SpvOpTypeStruct: { | |
| std::vector<const opt::analysis::Constant*> field_zero_constants; | |
| std::vector<uint32_t> field_zero_ids; | |
| for (uint32_t index = 0; index < type_instruction->NumInOperands(); | |
| index++) { | |
| uint32_t field_constant_id = FindOrCreateZeroConstant( | |
| type_instruction->GetSingleWordInOperand(index)); | |
| field_zero_ids.push_back(field_constant_id); | |
| field_zero_constants.push_back( | |
| GetIRContext()->get_constant_mgr()->FindDeclaredConstant( | |
| field_constant_id)); | |
| } | |
| return FindOrCreateCompositeConstant( | |
| *type_instruction, field_zero_constants, field_zero_ids); | |
| } | |
| default: | |
| assert(false && "Unknown type."); | |
| return 0; | |
| } | |
| } | |
| uint32_t FuzzerPass::FindOrCreateCompositeConstant( | |
| const opt::Instruction& composite_type_instruction, | |
| const std::vector<const opt::analysis::Constant*>& constants, | |
| const std::vector<uint32_t>& constant_ids) { | |
| assert(constants.size() == constant_ids.size() && | |
| "Precondition: |constants| and |constant_ids| must be in " | |
| "correspondence."); | |
| opt::analysis::Type* composite_type = GetIRContext()->get_type_mgr()->GetType( | |
| composite_type_instruction.result_id()); | |
| std::unique_ptr<opt::analysis::Constant> composite_constant; | |
| if (composite_type->AsArray()) { | |
| composite_constant = MakeUnique<opt::analysis::ArrayConstant>( | |
| composite_type->AsArray(), constants); | |
| } else if (composite_type->AsMatrix()) { | |
| composite_constant = MakeUnique<opt::analysis::MatrixConstant>( | |
| composite_type->AsMatrix(), constants); | |
| } else if (composite_type->AsStruct()) { | |
| composite_constant = MakeUnique<opt::analysis::StructConstant>( | |
| composite_type->AsStruct(), constants); | |
| } else if (composite_type->AsVector()) { | |
| composite_constant = MakeUnique<opt::analysis::VectorConstant>( | |
| composite_type->AsVector(), constants); | |
| } else { | |
| assert(false && | |
| "Precondition: |composite_type| must declare a composite type."); | |
| return 0; | |
| } | |
| uint32_t existing_constant = | |
| GetIRContext()->get_constant_mgr()->FindDeclaredConstant( | |
| composite_constant.get(), composite_type_instruction.result_id()); | |
| if (existing_constant) { | |
| return existing_constant; | |
| } | |
| uint32_t result = GetFuzzerContext()->GetFreshId(); | |
| ApplyTransformation(TransformationAddConstantComposite( | |
| result, composite_type_instruction.result_id(), constant_ids)); | |
| return result; | |
| } | |
| uint32_t FuzzerPass::GetZeroConstantForHomogeneousComposite( | |
| const opt::Instruction& composite_type_instruction, | |
| uint32_t component_type_id, uint32_t num_components) { | |
| std::vector<const opt::analysis::Constant*> zero_constants; | |
| std::vector<uint32_t> zero_ids; | |
| uint32_t zero_component = FindOrCreateZeroConstant(component_type_id); | |
| const opt::analysis::Constant* registered_zero_component = | |
| GetIRContext()->get_constant_mgr()->FindDeclaredConstant(zero_component); | |
| for (uint32_t i = 0; i < num_components; i++) { | |
| zero_constants.push_back(registered_zero_component); | |
| zero_ids.push_back(zero_component); | |
| } | |
| return FindOrCreateCompositeConstant(composite_type_instruction, | |
| zero_constants, zero_ids); | |
| } | |
| } // namespace fuzz | |
| } // namespace spvtools |