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