| // Copyright (c) 2020 André Perez Maselco |
| // |
| // 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/transformation_inline_function.h" |
| |
| #include "source/fuzz/fuzzer_util.h" |
| #include "source/fuzz/instruction_descriptor.h" |
| |
| namespace spvtools { |
| namespace fuzz { |
| |
| TransformationInlineFunction::TransformationInlineFunction( |
| protobufs::TransformationInlineFunction message) |
| : message_(std::move(message)) {} |
| |
| TransformationInlineFunction::TransformationInlineFunction( |
| uint32_t function_call_id, |
| const std::map<uint32_t, uint32_t>& result_id_map) { |
| message_.set_function_call_id(function_call_id); |
| *message_.mutable_result_id_map() = |
| fuzzerutil::MapToRepeatedUInt32Pair(result_id_map); |
| } |
| |
| bool TransformationInlineFunction::IsApplicable( |
| opt::IRContext* ir_context, |
| const TransformationContext& transformation_context) const { |
| // The values in the |message_.result_id_map| must be all fresh and all |
| // distinct. |
| const auto result_id_map = |
| fuzzerutil::RepeatedUInt32PairToMap(message_.result_id_map()); |
| std::set<uint32_t> ids_used_by_this_transformation; |
| for (auto& pair : result_id_map) { |
| if (!CheckIdIsFreshAndNotUsedByThisTransformation( |
| pair.second, ir_context, &ids_used_by_this_transformation)) { |
| return false; |
| } |
| } |
| |
| // |function_call_instruction| must be suitable for inlining. |
| auto* function_call_instruction = |
| ir_context->get_def_use_mgr()->GetDef(message_.function_call_id()); |
| if (!IsSuitableForInlining(ir_context, function_call_instruction)) { |
| return false; |
| } |
| |
| // |function_call_instruction| must be the penultimate instruction in its |
| // block and its block termination instruction must be an OpBranch. This |
| // avoids the case where the penultimate instruction is an OpLoopMerge, which |
| // would make the back-edge block not branch to the loop header. |
| auto* function_call_instruction_block = |
| ir_context->get_instr_block(function_call_instruction); |
| if (function_call_instruction != |
| &*--function_call_instruction_block->tail() || |
| function_call_instruction_block->terminator()->opcode() != |
| spv::Op::OpBranch) { |
| return false; |
| } |
| |
| auto* called_function = fuzzerutil::FindFunction( |
| ir_context, function_call_instruction->GetSingleWordInOperand(0)); |
| for (auto& block : *called_function) { |
| // Since the entry block label will not be inlined, only the remaining |
| // labels must have a corresponding value in the map. |
| if (&block != &*called_function->entry() && |
| !result_id_map.count(block.id()) && |
| !transformation_context.GetOverflowIdSource()->HasOverflowIds()) { |
| return false; |
| } |
| |
| // |result_id_map| must have an entry for every result id in the called |
| // function. |
| for (auto& instruction : block) { |
| // If |instruction| has result id, then it must have a mapped id in |
| // |result_id_map|. |
| if (instruction.HasResultId() && |
| !result_id_map.count(instruction.result_id()) && |
| !transformation_context.GetOverflowIdSource()->HasOverflowIds()) { |
| return false; |
| } |
| } |
| } |
| |
| // |result_id_map| must not contain an entry for any parameter of the function |
| // that is being inlined. |
| bool found_entry_for_parameter = false; |
| called_function->ForEachParam( |
| [&result_id_map, &found_entry_for_parameter](opt::Instruction* param) { |
| if (result_id_map.count(param->result_id())) { |
| found_entry_for_parameter = true; |
| } |
| }); |
| return !found_entry_for_parameter; |
| } |
| |
| void TransformationInlineFunction::Apply( |
| opt::IRContext* ir_context, |
| TransformationContext* transformation_context) const { |
| auto* function_call_instruction = |
| ir_context->get_def_use_mgr()->GetDef(message_.function_call_id()); |
| auto* caller_function = |
| ir_context->get_instr_block(function_call_instruction)->GetParent(); |
| auto* called_function = fuzzerutil::FindFunction( |
| ir_context, function_call_instruction->GetSingleWordInOperand(0)); |
| std::map<uint32_t, uint32_t> result_id_map = |
| fuzzerutil::RepeatedUInt32PairToMap(message_.result_id_map()); |
| |
| // If there are gaps in the result id map, fill them using overflow ids. |
| for (auto& block : *called_function) { |
| if (&block != &*called_function->entry() && |
| !result_id_map.count(block.id())) { |
| result_id_map.insert( |
| {block.id(), |
| transformation_context->GetOverflowIdSource()->GetNextOverflowId()}); |
| } |
| for (auto& instruction : block) { |
| // If |instruction| has result id, then it must have a mapped id in |
| // |result_id_map|. |
| if (instruction.HasResultId() && |
| !result_id_map.count(instruction.result_id())) { |
| result_id_map.insert({instruction.result_id(), |
| transformation_context->GetOverflowIdSource() |
| ->GetNextOverflowId()}); |
| } |
| } |
| } |
| |
| auto* successor_block = ir_context->cfg()->block( |
| ir_context->get_instr_block(function_call_instruction) |
| ->terminator() |
| ->GetSingleWordInOperand(0)); |
| |
| // Inline the |called_function| entry block. |
| for (auto& entry_block_instruction : *called_function->entry()) { |
| opt::Instruction* inlined_instruction; |
| |
| if (entry_block_instruction.opcode() == spv::Op::OpVariable) { |
| // All OpVariable instructions in a function must be in the first block |
| // in the function. |
| inlined_instruction = caller_function->begin()->begin()->InsertBefore( |
| std::unique_ptr<opt::Instruction>( |
| entry_block_instruction.Clone(ir_context))); |
| } else { |
| inlined_instruction = function_call_instruction->InsertBefore( |
| std::unique_ptr<opt::Instruction>( |
| entry_block_instruction.Clone(ir_context))); |
| } |
| |
| AdaptInlinedInstruction(result_id_map, ir_context, inlined_instruction); |
| } |
| |
| // If the function call's successor block contains OpPhi instructions that |
| // refer to the block containing the call then these will need to be rewritten |
| // to instead refer to the block associated with "returning" from the inlined |
| // function, as this block will be the predecessor of what used to be the |
| // function call's successor block. We look out for this block. |
| uint32_t new_return_block_id = 0; |
| |
| // Inline the |called_function| non-entry blocks. |
| for (auto& block : *called_function) { |
| if (&block == &*called_function->entry()) { |
| continue; |
| } |
| |
| // Check whether this is the function's return block. Take note if it is, |
| // so that OpPhi instructions in the successor of the original function call |
| // block can be re-written. |
| if (block.terminator()->IsReturn()) { |
| assert(new_return_block_id == 0 && |
| "There should be only one return block."); |
| new_return_block_id = result_id_map.at(block.id()); |
| } |
| |
| auto* cloned_block = block.Clone(ir_context); |
| cloned_block = caller_function->InsertBasicBlockBefore( |
| std::unique_ptr<opt::BasicBlock>(cloned_block), successor_block); |
| cloned_block->GetLabel()->SetResultId(result_id_map.at(cloned_block->id())); |
| fuzzerutil::UpdateModuleIdBound(ir_context, cloned_block->id()); |
| |
| for (auto& inlined_instruction : *cloned_block) { |
| AdaptInlinedInstruction(result_id_map, ir_context, &inlined_instruction); |
| } |
| } |
| |
| opt::BasicBlock* block_containing_function_call = |
| ir_context->get_instr_block(function_call_instruction); |
| |
| assert(((new_return_block_id == 0) == |
| called_function->entry()->terminator()->IsReturn()) && |
| "We should have found a return block unless the function being " |
| "inlined returns in its first block."); |
| if (new_return_block_id != 0) { |
| // Rewrite any OpPhi instructions in the successor block so that they refer |
| // to the new return block instead of the block that originally contained |
| // the function call. |
| ir_context->get_def_use_mgr()->ForEachUse( |
| block_containing_function_call->id(), |
| [ir_context, new_return_block_id, successor_block]( |
| opt::Instruction* use_instruction, uint32_t operand_index) { |
| if (use_instruction->opcode() == spv::Op::OpPhi && |
| ir_context->get_instr_block(use_instruction) == successor_block) { |
| use_instruction->SetOperand(operand_index, {new_return_block_id}); |
| } |
| }); |
| } |
| |
| // Removes the function call instruction and its block termination instruction |
| // from |caller_function|. |
| ir_context->KillInst(block_containing_function_call->terminator()); |
| ir_context->KillInst(function_call_instruction); |
| |
| // Since the SPIR-V module has changed, no analyses must be validated. |
| ir_context->InvalidateAnalysesExceptFor( |
| opt::IRContext::Analysis::kAnalysisNone); |
| } |
| |
| protobufs::Transformation TransformationInlineFunction::ToMessage() const { |
| protobufs::Transformation result; |
| *result.mutable_inline_function() = message_; |
| return result; |
| } |
| |
| bool TransformationInlineFunction::IsSuitableForInlining( |
| opt::IRContext* ir_context, opt::Instruction* function_call_instruction) { |
| // |function_call_instruction| must be defined and must be an OpFunctionCall |
| // instruction. |
| if (!function_call_instruction || |
| function_call_instruction->opcode() != spv::Op::OpFunctionCall) { |
| return false; |
| } |
| |
| // If |function_call_instruction| return type is void, then |
| // |function_call_instruction| must not have uses. |
| if (ir_context->get_type_mgr() |
| ->GetType(function_call_instruction->type_id()) |
| ->AsVoid() && |
| ir_context->get_def_use_mgr()->NumUses(function_call_instruction) != 0) { |
| return false; |
| } |
| |
| // |called_function| must not have an early return. |
| auto called_function = fuzzerutil::FindFunction( |
| ir_context, function_call_instruction->GetSingleWordInOperand(0)); |
| if (called_function->HasEarlyReturn()) { |
| return false; |
| } |
| |
| // |called_function| must not use OpKill or OpUnreachable. |
| if (fuzzerutil::FunctionContainsOpKillOrUnreachable(*called_function)) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void TransformationInlineFunction::AdaptInlinedInstruction( |
| const std::map<uint32_t, uint32_t>& result_id_map, |
| opt::IRContext* ir_context, |
| opt::Instruction* instruction_to_be_inlined) const { |
| auto* function_call_instruction = |
| ir_context->get_def_use_mgr()->GetDef(message_.function_call_id()); |
| auto* called_function = fuzzerutil::FindFunction( |
| ir_context, function_call_instruction->GetSingleWordInOperand(0)); |
| |
| const auto* function_call_block = |
| ir_context->get_instr_block(function_call_instruction); |
| assert(function_call_block && "OpFunctionCall must belong to some block"); |
| |
| // Replaces the operand ids with their mapped result ids. |
| instruction_to_be_inlined->ForEachInId( |
| [called_function, function_call_instruction, &result_id_map, |
| function_call_block](uint32_t* id) { |
| // We are not inlining the entry block of the |called_function|. |
| // |
| // We must check this condition first since we can't use the fresh id |
| // from |result_id_map| even if it has one. This is because that fresh |
| // id will never be added to the module since entry blocks are not |
| // inlined. |
| if (*id == called_function->entry()->id()) { |
| *id = function_call_block->id(); |
| return; |
| } |
| |
| // If |id| is mapped, then set it to its mapped value. |
| if (result_id_map.count(*id)) { |
| *id = result_id_map.at(*id); |
| return; |
| } |
| |
| uint32_t parameter_index = 0; |
| called_function->ForEachParam( |
| [id, function_call_instruction, |
| ¶meter_index](opt::Instruction* parameter_instruction) { |
| // If the id is a function parameter, then set it to the |
| // parameter value passed in the function call instruction. |
| if (*id == parameter_instruction->result_id()) { |
| // We do + 1 because the first in-operand for OpFunctionCall is |
| // the function id that is being called. |
| *id = function_call_instruction->GetSingleWordInOperand( |
| parameter_index + 1); |
| } |
| parameter_index++; |
| }); |
| }); |
| |
| // If |instruction_to_be_inlined| has result id, then set it to its mapped |
| // value. |
| if (instruction_to_be_inlined->HasResultId()) { |
| assert(result_id_map.count(instruction_to_be_inlined->result_id()) && |
| "Result id must be mapped to a fresh id."); |
| instruction_to_be_inlined->SetResultId( |
| result_id_map.at(instruction_to_be_inlined->result_id())); |
| fuzzerutil::UpdateModuleIdBound(ir_context, |
| instruction_to_be_inlined->result_id()); |
| } |
| |
| // The return instruction will be changed into an OpBranch to the basic |
| // block that follows the block containing the function call. |
| if (spvOpcodeIsReturn(instruction_to_be_inlined->opcode())) { |
| uint32_t successor_block_id = |
| ir_context->get_instr_block(function_call_instruction) |
| ->terminator() |
| ->GetSingleWordInOperand(0); |
| switch (instruction_to_be_inlined->opcode()) { |
| case spv::Op::OpReturn: |
| instruction_to_be_inlined->AddOperand( |
| {SPV_OPERAND_TYPE_ID, {successor_block_id}}); |
| break; |
| case spv::Op::OpReturnValue: { |
| instruction_to_be_inlined->InsertBefore(MakeUnique<opt::Instruction>( |
| ir_context, spv::Op::OpCopyObject, |
| function_call_instruction->type_id(), |
| function_call_instruction->result_id(), |
| opt::Instruction::OperandList( |
| {{SPV_OPERAND_TYPE_ID, |
| {instruction_to_be_inlined->GetSingleWordOperand(0)}}}))); |
| instruction_to_be_inlined->SetInOperand(0, {successor_block_id}); |
| break; |
| } |
| default: |
| break; |
| } |
| instruction_to_be_inlined->SetOpcode(spv::Op::OpBranch); |
| } |
| } |
| |
| std::unordered_set<uint32_t> TransformationInlineFunction::GetFreshIds() const { |
| std::unordered_set<uint32_t> result; |
| for (auto& pair : message_.result_id_map()) { |
| result.insert(pair.second()); |
| } |
| return result; |
| } |
| |
| } // namespace fuzz |
| } // namespace spvtools |