| // 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/transformation_duplicate_region_with_selection.h" | |
| #include "source/fuzz/fuzzer_util.h" | |
| namespace spvtools { | |
| namespace fuzz { | |
| TransformationDuplicateRegionWithSelection:: | |
| TransformationDuplicateRegionWithSelection( | |
| protobufs::TransformationDuplicateRegionWithSelection message) | |
| : message_(std::move(message)) {} | |
| TransformationDuplicateRegionWithSelection:: | |
| TransformationDuplicateRegionWithSelection( | |
| uint32_t new_entry_fresh_id, uint32_t condition_id, | |
| uint32_t merge_label_fresh_id, uint32_t entry_block_id, | |
| uint32_t exit_block_id, | |
| const std::map<uint32_t, uint32_t>& original_label_to_duplicate_label, | |
| const std::map<uint32_t, uint32_t>& original_id_to_duplicate_id, | |
| const std::map<uint32_t, uint32_t>& original_id_to_phi_id) { | |
| message_.set_new_entry_fresh_id(new_entry_fresh_id); | |
| message_.set_condition_id(condition_id); | |
| message_.set_merge_label_fresh_id(merge_label_fresh_id); | |
| message_.set_entry_block_id(entry_block_id); | |
| message_.set_exit_block_id(exit_block_id); | |
| *message_.mutable_original_label_to_duplicate_label() = | |
| fuzzerutil::MapToRepeatedUInt32Pair(original_label_to_duplicate_label); | |
| *message_.mutable_original_id_to_duplicate_id() = | |
| fuzzerutil::MapToRepeatedUInt32Pair(original_id_to_duplicate_id); | |
| *message_.mutable_original_id_to_phi_id() = | |
| fuzzerutil::MapToRepeatedUInt32Pair(original_id_to_phi_id); | |
| } | |
| bool TransformationDuplicateRegionWithSelection::IsApplicable( | |
| opt::IRContext* ir_context, | |
| const TransformationContext& transformation_context) const { | |
| // Instruction with the id |condition_id| must exist and must be of a bool | |
| // type. | |
| auto bool_instr = | |
| ir_context->get_def_use_mgr()->GetDef(message_.condition_id()); | |
| if (bool_instr == nullptr || !bool_instr->type_id()) { | |
| return false; | |
| } | |
| if (!ir_context->get_type_mgr()->GetType(bool_instr->type_id())->AsBool()) { | |
| return false; | |
| } | |
| // The |new_entry_fresh_id| must be fresh and distinct. | |
| std::set<uint32_t> ids_used_by_this_transformation; | |
| if (!CheckIdIsFreshAndNotUsedByThisTransformation( | |
| message_.new_entry_fresh_id(), ir_context, | |
| &ids_used_by_this_transformation)) { | |
| return false; | |
| } | |
| // The |merge_label_fresh_id| must be fresh and distinct. | |
| if (!CheckIdIsFreshAndNotUsedByThisTransformation( | |
| message_.merge_label_fresh_id(), ir_context, | |
| &ids_used_by_this_transformation)) { | |
| return false; | |
| } | |
| // The entry and exit block ids must refer to blocks. | |
| for (auto block_id : {message_.entry_block_id(), message_.exit_block_id()}) { | |
| auto block_label = ir_context->get_def_use_mgr()->GetDef(block_id); | |
| if (!block_label || block_label->opcode() != spv::Op::OpLabel) { | |
| return false; | |
| } | |
| } | |
| auto entry_block = ir_context->cfg()->block(message_.entry_block_id()); | |
| auto exit_block = ir_context->cfg()->block(message_.exit_block_id()); | |
| // The |entry_block| and the |exit_block| must be in the same function. | |
| if (entry_block->GetParent() != exit_block->GetParent()) { | |
| return false; | |
| } | |
| // The |entry_block| must dominate the |exit_block|. | |
| auto dominator_analysis = | |
| ir_context->GetDominatorAnalysis(entry_block->GetParent()); | |
| if (!dominator_analysis->Dominates(entry_block, exit_block)) { | |
| return false; | |
| } | |
| // The |exit_block| must post-dominate the |entry_block|. | |
| auto postdominator_analysis = | |
| ir_context->GetPostDominatorAnalysis(entry_block->GetParent()); | |
| if (!postdominator_analysis->Dominates(exit_block, entry_block)) { | |
| return false; | |
| } | |
| auto enclosing_function = entry_block->GetParent(); | |
| // |entry_block| cannot be the first block of the |enclosing_function|. | |
| if (&*enclosing_function->begin() == entry_block) { | |
| return false; | |
| } | |
| // To make the process of resolving OpPhi instructions easier, we require that | |
| // the entry block has only one predecessor. | |
| auto entry_block_preds = ir_context->cfg()->preds(entry_block->id()); | |
| std::sort(entry_block_preds.begin(), entry_block_preds.end()); | |
| entry_block_preds.erase( | |
| std::unique(entry_block_preds.begin(), entry_block_preds.end()), | |
| entry_block_preds.end()); | |
| if (entry_block_preds.size() > 1) { | |
| return false; | |
| } | |
| // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3785): | |
| // The following code has been copied from TransformationOutlineFunction. | |
| // Consider refactoring to avoid duplication. | |
| auto region_set = GetRegionBlocks(ir_context, entry_block, exit_block); | |
| // Check whether |region_set| really is a single-entry single-exit region, and | |
| // also check whether structured control flow constructs and their merge | |
| // and continue constructs are either wholly in or wholly out of the region - | |
| // e.g. avoid the situation where the region contains the head of a loop but | |
| // not the loop's continue construct. | |
| // | |
| // This is achieved by going through every block in the |enclosing_function| | |
| for (auto& block : *enclosing_function) { | |
| if (&block == exit_block) { | |
| // It is not OK for the exit block to head a loop construct or a | |
| // conditional construct. | |
| if (block.GetMergeInst()) { | |
| return false; | |
| } | |
| continue; | |
| } | |
| if (region_set.count(&block) != 0) { | |
| // The block is in the region and is not the region's exit block. Let's | |
| // see whether all of the block's successors are in the region. If they | |
| // are not, the region is not single-entry single-exit. | |
| bool all_successors_in_region = true; | |
| block.WhileEachSuccessorLabel([&all_successors_in_region, ir_context, | |
| ®ion_set](uint32_t successor) -> bool { | |
| if (region_set.count(ir_context->cfg()->block(successor)) == 0) { | |
| all_successors_in_region = false; | |
| return false; | |
| } | |
| return true; | |
| }); | |
| if (!all_successors_in_region) { | |
| return false; | |
| } | |
| } | |
| if (auto merge = block.GetMergeInst()) { | |
| // The block is a loop or selection header. The header and its | |
| // associated merge block must be both in the region or both be | |
| // outside the region. | |
| auto merge_block = | |
| ir_context->cfg()->block(merge->GetSingleWordOperand(0)); | |
| if (region_set.count(&block) != region_set.count(merge_block)) { | |
| return false; | |
| } | |
| } | |
| if (auto loop_merge = block.GetLoopMergeInst()) { | |
| // The continue target of a loop must be within the region if and only if | |
| // the header of the loop is. | |
| auto continue_target = | |
| ir_context->cfg()->block(loop_merge->GetSingleWordOperand(1)); | |
| // The continue target is a single-entry, single-exit region. Therefore, | |
| // if the continue target is the exit block, the region might not contain | |
| // the loop header. However, we would like to exclude this situation, | |
| // since it would be impossible for the modified exit block to branch to | |
| // the new selection merge block. In this scenario the exit block is | |
| // required to branch to the loop header. | |
| if (region_set.count(&block) != region_set.count(continue_target)) { | |
| return false; | |
| } | |
| } | |
| } | |
| // Get the maps from the protobuf. | |
| std::map<uint32_t, uint32_t> original_label_to_duplicate_label = | |
| fuzzerutil::RepeatedUInt32PairToMap( | |
| message_.original_label_to_duplicate_label()); | |
| std::map<uint32_t, uint32_t> original_id_to_duplicate_id = | |
| fuzzerutil::RepeatedUInt32PairToMap( | |
| message_.original_id_to_duplicate_id()); | |
| std::map<uint32_t, uint32_t> original_id_to_phi_id = | |
| fuzzerutil::RepeatedUInt32PairToMap(message_.original_id_to_phi_id()); | |
| for (auto block : region_set) { | |
| // The label of every block in the region must be present in the map | |
| // |original_label_to_duplicate_label|, unless overflow ids are present. | |
| if (original_label_to_duplicate_label.count(block->id()) == 0) { | |
| if (!transformation_context.GetOverflowIdSource()->HasOverflowIds()) { | |
| return false; | |
| } | |
| } else { | |
| auto duplicate_label = original_label_to_duplicate_label.at(block->id()); | |
| // Each id assigned to labels in the region must be distinct and fresh. | |
| if (!duplicate_label || | |
| !CheckIdIsFreshAndNotUsedByThisTransformation( | |
| duplicate_label, ir_context, &ids_used_by_this_transformation)) { | |
| return false; | |
| } | |
| } | |
| for (auto& instr : *block) { | |
| if (!instr.HasResultId()) { | |
| continue; | |
| } | |
| // Every instruction with a result id in the region must be present in the | |
| // map |original_id_to_duplicate_id|, unless overflow ids are present. | |
| if (original_id_to_duplicate_id.count(instr.result_id()) == 0) { | |
| if (!transformation_context.GetOverflowIdSource()->HasOverflowIds()) { | |
| return false; | |
| } | |
| } else { | |
| auto duplicate_id = original_id_to_duplicate_id.at(instr.result_id()); | |
| // Id assigned to this result id in the region must be distinct and | |
| // fresh. | |
| if (!duplicate_id || | |
| !CheckIdIsFreshAndNotUsedByThisTransformation( | |
| duplicate_id, ir_context, &ids_used_by_this_transformation)) { | |
| return false; | |
| } | |
| } | |
| // If the instruction is available at the end of the region then we would | |
| // like to be able to add an OpPhi instruction at the merge point of the | |
| // duplicated region to capture the values computed by both duplicates of | |
| // the instruction, so that this is also available after the region. We | |
| // do this not just for instructions that are already used after the | |
| // region, but for all instructions so that the phi is available to future | |
| // transformations. | |
| if (AvailableAfterRegion(instr, exit_block, ir_context)) { | |
| if (!ValidOpPhiArgument(instr, ir_context)) { | |
| // The instruction cannot be used as an OpPhi argument. This is a | |
| // blocker if there are uses of the instruction after the region. | |
| // Otherwise we can simply avoid generating an OpPhi for this | |
| // instruction and its duplicate. | |
| if (!ir_context->get_def_use_mgr()->WhileEachUser( | |
| &instr, | |
| [ir_context, | |
| ®ion_set](opt::Instruction* use_instr) -> bool { | |
| opt::BasicBlock* use_block = | |
| ir_context->get_instr_block(use_instr); | |
| return use_block == nullptr || | |
| region_set.count(use_block) > 0; | |
| })) { | |
| return false; | |
| } | |
| } else { | |
| // Every instruction with a result id available at the end of the | |
| // region must be present in the map |original_id_to_phi_id|, unless | |
| // overflow ids are present. | |
| if (original_id_to_phi_id.count(instr.result_id()) == 0) { | |
| if (!transformation_context.GetOverflowIdSource() | |
| ->HasOverflowIds()) { | |
| return false; | |
| } | |
| } else { | |
| auto phi_id = original_id_to_phi_id.at(instr.result_id()); | |
| // Id assigned to this result id in the region must be distinct and | |
| // fresh. | |
| if (!phi_id || | |
| !CheckIdIsFreshAndNotUsedByThisTransformation( | |
| phi_id, ir_context, &ids_used_by_this_transformation)) { | |
| return false; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| return true; | |
| } | |
| void TransformationDuplicateRegionWithSelection::Apply( | |
| opt::IRContext* ir_context, | |
| TransformationContext* transformation_context) const { | |
| fuzzerutil::UpdateModuleIdBound(ir_context, message_.new_entry_fresh_id()); | |
| fuzzerutil::UpdateModuleIdBound(ir_context, message_.merge_label_fresh_id()); | |
| // Create the new entry block containing the main conditional instruction. Set | |
| // its parent to the parent of the original entry block, since it is located | |
| // in the same function. | |
| std::unique_ptr<opt::BasicBlock> new_entry_block = | |
| MakeUnique<opt::BasicBlock>(MakeUnique<opt::Instruction>( | |
| ir_context, spv::Op::OpLabel, 0, message_.new_entry_fresh_id(), | |
| opt::Instruction::OperandList())); | |
| auto entry_block = ir_context->cfg()->block(message_.entry_block_id()); | |
| auto enclosing_function = entry_block->GetParent(); | |
| auto exit_block = ir_context->cfg()->block(message_.exit_block_id()); | |
| // Get the blocks contained in the region. | |
| std::set<opt::BasicBlock*> region_blocks = | |
| GetRegionBlocks(ir_context, entry_block, exit_block); | |
| // Construct the merge block. | |
| std::unique_ptr<opt::BasicBlock> merge_block = | |
| MakeUnique<opt::BasicBlock>(MakeUnique<opt::Instruction>( | |
| ir_context, spv::Op::OpLabel, 0, message_.merge_label_fresh_id(), | |
| opt::Instruction::OperandList())); | |
| // Get the maps from the protobuf. | |
| std::map<uint32_t, uint32_t> original_label_to_duplicate_label = | |
| fuzzerutil::RepeatedUInt32PairToMap( | |
| message_.original_label_to_duplicate_label()); | |
| std::map<uint32_t, uint32_t> original_id_to_duplicate_id = | |
| fuzzerutil::RepeatedUInt32PairToMap( | |
| message_.original_id_to_duplicate_id()); | |
| std::map<uint32_t, uint32_t> original_id_to_phi_id = | |
| fuzzerutil::RepeatedUInt32PairToMap(message_.original_id_to_phi_id()); | |
| // Use overflow ids to fill in any required ids that are missing from these | |
| // maps. | |
| for (auto block : region_blocks) { | |
| if (original_label_to_duplicate_label.count(block->id()) == 0) { | |
| original_label_to_duplicate_label.insert( | |
| {block->id(), | |
| transformation_context->GetOverflowIdSource()->GetNextOverflowId()}); | |
| } | |
| for (auto& instr : *block) { | |
| if (!instr.HasResultId()) { | |
| continue; | |
| } | |
| if (original_id_to_duplicate_id.count(instr.result_id()) == 0) { | |
| original_id_to_duplicate_id.insert( | |
| {instr.result_id(), transformation_context->GetOverflowIdSource() | |
| ->GetNextOverflowId()}); | |
| } | |
| if (AvailableAfterRegion(instr, exit_block, ir_context) && | |
| ValidOpPhiArgument(instr, ir_context)) { | |
| if (original_id_to_phi_id.count(instr.result_id()) == 0) { | |
| original_id_to_phi_id.insert( | |
| {instr.result_id(), transformation_context->GetOverflowIdSource() | |
| ->GetNextOverflowId()}); | |
| } | |
| } | |
| } | |
| } | |
| // Before adding duplicate blocks, we need to update the OpPhi instructions in | |
| // the successors of the |exit_block|. We know that the execution of the | |
| // transformed region will end in |merge_block|. Hence, we need to change all | |
| // occurrences of the label id of the |exit_block| to the label id of the | |
| // |merge_block|. | |
| exit_block->ForEachSuccessorLabel([this, ir_context](uint32_t label_id) { | |
| auto block = ir_context->cfg()->block(label_id); | |
| for (auto& instr : *block) { | |
| if (instr.opcode() == spv::Op::OpPhi) { | |
| instr.ForEachId([this](uint32_t* id) { | |
| if (*id == message_.exit_block_id()) { | |
| *id = message_.merge_label_fresh_id(); | |
| } | |
| }); | |
| } | |
| } | |
| }); | |
| // Get vector of predecessors id of |entry_block|. Remove any duplicate | |
| // values. | |
| auto entry_block_preds = ir_context->cfg()->preds(entry_block->id()); | |
| std::sort(entry_block_preds.begin(), entry_block_preds.end()); | |
| entry_block_preds.erase( | |
| unique(entry_block_preds.begin(), entry_block_preds.end()), | |
| entry_block_preds.end()); | |
| // We know that |entry_block| has only one predecessor, since the region is | |
| // single-entry, single-exit and its constructs and their merge blocks must be | |
| // either wholly within or wholly outside of the region. | |
| assert(entry_block_preds.size() == 1 && | |
| "The entry of the region to be duplicated can have only one " | |
| "predecessor."); | |
| uint32_t entry_block_pred_id = | |
| ir_context->get_instr_block(entry_block_preds[0])->id(); | |
| // Update all the OpPhi instructions in the |entry_block|. Change every | |
| // occurrence of |entry_block_pred_id| to the id of |new_entry|, because we | |
| // will insert |new_entry| before |entry_block|. | |
| for (auto& instr : *entry_block) { | |
| if (instr.opcode() == spv::Op::OpPhi) { | |
| instr.ForEachId([this, entry_block_pred_id](uint32_t* id) { | |
| if (*id == entry_block_pred_id) { | |
| *id = message_.new_entry_fresh_id(); | |
| } | |
| }); | |
| } | |
| } | |
| // Duplication of blocks will invalidate iterators. Store all the blocks from | |
| // the enclosing function. | |
| std::vector<opt::BasicBlock*> blocks; | |
| for (auto& block : *enclosing_function) { | |
| blocks.push_back(&block); | |
| } | |
| opt::BasicBlock* previous_block = nullptr; | |
| opt::BasicBlock* duplicated_exit_block = nullptr; | |
| // Iterate over all blocks of the function to duplicate blocks of the original | |
| // region and their instructions. | |
| for (auto& block : blocks) { | |
| // The block must be contained in the region. | |
| if (region_blocks.count(block) == 0) { | |
| continue; | |
| } | |
| fuzzerutil::UpdateModuleIdBound( | |
| ir_context, original_label_to_duplicate_label.at(block->id())); | |
| std::unique_ptr<opt::BasicBlock> duplicated_block = | |
| MakeUnique<opt::BasicBlock>(MakeUnique<opt::Instruction>( | |
| ir_context, spv::Op::OpLabel, 0, | |
| original_label_to_duplicate_label.at(block->id()), | |
| opt::Instruction::OperandList())); | |
| for (auto& instr : *block) { | |
| // Case where an instruction is the terminator of the exit block is | |
| // handled separately. | |
| if (block == exit_block && instr.IsBlockTerminator()) { | |
| switch (instr.opcode()) { | |
| case spv::Op::OpBranch: | |
| case spv::Op::OpBranchConditional: | |
| case spv::Op::OpReturn: | |
| case spv::Op::OpReturnValue: | |
| case spv::Op::OpUnreachable: | |
| case spv::Op::OpKill: | |
| continue; | |
| default: | |
| assert(false && | |
| "Unexpected terminator for |exit_block| of the region."); | |
| } | |
| } | |
| // Duplicate the instruction. | |
| auto cloned_instr = instr.Clone(ir_context); | |
| duplicated_block->AddInstruction( | |
| std::unique_ptr<opt::Instruction>(cloned_instr)); | |
| if (instr.HasResultId()) { | |
| fuzzerutil::UpdateModuleIdBound( | |
| ir_context, original_id_to_duplicate_id.at(instr.result_id())); | |
| } | |
| // If an id from the original region was used in this instruction, | |
| // replace it with the value from |original_id_to_duplicate_id|. | |
| // If a label from the original region was used in this instruction, | |
| // replace it with the value from |original_label_to_duplicate_label|. | |
| cloned_instr->ForEachId( | |
| [original_id_to_duplicate_id, | |
| original_label_to_duplicate_label](uint32_t* op) { | |
| if (original_id_to_duplicate_id.count(*op) != 0) { | |
| *op = original_id_to_duplicate_id.at(*op); | |
| } else if (original_label_to_duplicate_label.count(*op) != 0) { | |
| *op = original_label_to_duplicate_label.at(*op); | |
| } | |
| }); | |
| } | |
| // If the block is the first duplicated block, insert it after the exit | |
| // block of the original region. Otherwise, insert it after the preceding | |
| // one. | |
| auto duplicated_block_ptr = duplicated_block.get(); | |
| if (previous_block) { | |
| enclosing_function->InsertBasicBlockAfter(std::move(duplicated_block), | |
| previous_block); | |
| } else { | |
| enclosing_function->InsertBasicBlockAfter(std::move(duplicated_block), | |
| exit_block); | |
| } | |
| previous_block = duplicated_block_ptr; | |
| if (block == exit_block) { | |
| // After execution of the loop, this variable stores a pointer to the last | |
| // duplicated block. | |
| duplicated_exit_block = duplicated_block_ptr; | |
| } | |
| } | |
| for (auto& block : region_blocks) { | |
| for (auto& instr : *block) { | |
| if (instr.result_id() == 0) { | |
| continue; | |
| } | |
| if (AvailableAfterRegion(instr, exit_block, ir_context) && | |
| ValidOpPhiArgument(instr, ir_context)) { | |
| // Add an OpPhi instruction for every result id that is available at | |
| // the end of the region, as long as the result id is valid for use | |
| // with OpPhi. | |
| merge_block->AddInstruction(MakeUnique<opt::Instruction>( | |
| ir_context, spv::Op::OpPhi, instr.type_id(), | |
| original_id_to_phi_id.at(instr.result_id()), | |
| opt::Instruction::OperandList({ | |
| {SPV_OPERAND_TYPE_ID, {instr.result_id()}}, | |
| {SPV_OPERAND_TYPE_ID, {exit_block->id()}}, | |
| {SPV_OPERAND_TYPE_ID, | |
| {original_id_to_duplicate_id.at(instr.result_id())}}, | |
| {SPV_OPERAND_TYPE_ID, {duplicated_exit_block->id()}}, | |
| }))); | |
| fuzzerutil::UpdateModuleIdBound( | |
| ir_context, original_id_to_phi_id.at(instr.result_id())); | |
| // If the instruction has been remapped by an OpPhi, look | |
| // for all its uses outside of the region and outside of the | |
| // merge block (to not overwrite just added instructions in | |
| // the merge block) and replace the original instruction id | |
| // with the id of the corresponding OpPhi instruction. | |
| ir_context->get_def_use_mgr()->ForEachUse( | |
| &instr, | |
| [ir_context, &instr, region_blocks, original_id_to_phi_id, | |
| &merge_block](opt::Instruction* user, uint32_t operand_index) { | |
| auto user_block = ir_context->get_instr_block(user); | |
| if ((region_blocks.find(user_block) != region_blocks.end()) || | |
| user_block == merge_block.get()) { | |
| return; | |
| } | |
| user->SetOperand(operand_index, | |
| {original_id_to_phi_id.at(instr.result_id())}); | |
| }); | |
| } | |
| } | |
| } | |
| // Construct a conditional instruction in the |new_entry_block|. | |
| // If the condition is true, the execution proceeds in the | |
| // |entry_block| of the original region. If the condition is | |
| // false, the execution proceeds in the first block of the | |
| // duplicated region. | |
| new_entry_block->AddInstruction(MakeUnique<opt::Instruction>( | |
| ir_context, spv::Op::OpSelectionMerge, 0, 0, | |
| opt::Instruction::OperandList( | |
| {{SPV_OPERAND_TYPE_ID, {message_.merge_label_fresh_id()}}, | |
| {SPV_OPERAND_TYPE_SELECTION_CONTROL, | |
| {uint32_t(spv::SelectionControlMask::MaskNone)}}}))); | |
| new_entry_block->AddInstruction(MakeUnique<opt::Instruction>( | |
| ir_context, spv::Op::OpBranchConditional, 0, 0, | |
| opt::Instruction::OperandList( | |
| {{SPV_OPERAND_TYPE_ID, {message_.condition_id()}}, | |
| {SPV_OPERAND_TYPE_ID, {message_.entry_block_id()}}, | |
| {SPV_OPERAND_TYPE_ID, | |
| {original_label_to_duplicate_label.at( | |
| message_.entry_block_id())}}}))); | |
| // Move the terminator of |exit_block| to the end of | |
| // |merge_block|. | |
| auto exit_block_terminator = exit_block->terminator(); | |
| auto cloned_instr = exit_block_terminator->Clone(ir_context); | |
| merge_block->AddInstruction(std::unique_ptr<opt::Instruction>(cloned_instr)); | |
| ir_context->KillInst(exit_block_terminator); | |
| // Add OpBranch instruction to the merge block at the end of | |
| // |exit_block| and at the end of |duplicated_exit_block|, so that | |
| // the execution proceeds in the |merge_block|. | |
| opt::Instruction merge_branch_instr = opt::Instruction( | |
| ir_context, spv::Op::OpBranch, 0, 0, | |
| opt::Instruction::OperandList( | |
| {{SPV_OPERAND_TYPE_ID, {message_.merge_label_fresh_id()}}})); | |
| exit_block->AddInstruction(MakeUnique<opt::Instruction>(merge_branch_instr)); | |
| duplicated_exit_block->AddInstruction( | |
| std::unique_ptr<opt::Instruction>(merge_branch_instr.Clone(ir_context))); | |
| // Execution needs to start in the |new_entry_block|. Change all | |
| // the uses of |entry_block_label_instr| outside of the original | |
| // region to |message_.new_entry_fresh_id|. | |
| auto entry_block_label_instr = | |
| ir_context->get_def_use_mgr()->GetDef(message_.entry_block_id()); | |
| ir_context->get_def_use_mgr()->ForEachUse( | |
| entry_block_label_instr, | |
| [this, ir_context, region_blocks](opt::Instruction* user, | |
| uint32_t operand_index) { | |
| auto user_block = ir_context->get_instr_block(user); | |
| if ((region_blocks.count(user_block) != 0)) { | |
| return; | |
| } | |
| switch (user->opcode()) { | |
| case spv::Op::OpSwitch: | |
| case spv::Op::OpBranch: | |
| case spv::Op::OpBranchConditional: | |
| case spv::Op::OpLoopMerge: | |
| case spv::Op::OpSelectionMerge: { | |
| user->SetOperand(operand_index, {message_.new_entry_fresh_id()}); | |
| } break; | |
| case spv::Op::OpName: | |
| break; | |
| default: | |
| assert(false && | |
| "The label id cannot be used by instructions " | |
| "other than " | |
| "OpSwitch, OpBranch, OpBranchConditional, " | |
| "OpLoopMerge, " | |
| "OpSelectionMerge"); | |
| } | |
| }); | |
| opt::Instruction* merge_block_terminator = merge_block->terminator(); | |
| switch (merge_block_terminator->opcode()) { | |
| case spv::Op::OpReturnValue: | |
| case spv::Op::OpBranchConditional: { | |
| uint32_t operand = merge_block_terminator->GetSingleWordInOperand(0); | |
| if (original_id_to_phi_id.count(operand)) { | |
| merge_block_terminator->SetInOperand( | |
| 0, {original_id_to_phi_id.at(operand)}); | |
| } | |
| break; | |
| } | |
| default: | |
| break; | |
| } | |
| // Insert the merge block after the |duplicated_exit_block| (the | |
| // last duplicated block). | |
| enclosing_function->InsertBasicBlockAfter(std::move(merge_block), | |
| duplicated_exit_block); | |
| // Insert the |new_entry_block| before the entry block of the | |
| // original region. | |
| enclosing_function->InsertBasicBlockBefore(std::move(new_entry_block), | |
| entry_block); | |
| // Since we have changed the module, most of the analysis are now | |
| // invalid. We can invalidate analyses now after all of the blocks | |
| // have been registered. | |
| ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone); | |
| } | |
| // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3785): | |
| // The following method has been copied from | |
| // TransformationOutlineFunction. Consider refactoring to avoid | |
| // duplication. | |
| std::set<opt::BasicBlock*> | |
| TransformationDuplicateRegionWithSelection::GetRegionBlocks( | |
| opt::IRContext* ir_context, opt::BasicBlock* entry_block, | |
| opt::BasicBlock* exit_block) { | |
| auto enclosing_function = entry_block->GetParent(); | |
| auto dominator_analysis = | |
| ir_context->GetDominatorAnalysis(enclosing_function); | |
| auto postdominator_analysis = | |
| ir_context->GetPostDominatorAnalysis(enclosing_function); | |
| // A block belongs to a region between the entry block and the exit | |
| // block if and only if it is dominated by the entry block and | |
| // post-dominated by the exit block. | |
| std::set<opt::BasicBlock*> result; | |
| for (auto& block : *enclosing_function) { | |
| if (dominator_analysis->Dominates(entry_block, &block) && | |
| postdominator_analysis->Dominates(exit_block, &block)) { | |
| result.insert(&block); | |
| } | |
| } | |
| return result; | |
| } | |
| protobufs::Transformation | |
| TransformationDuplicateRegionWithSelection::ToMessage() const { | |
| protobufs::Transformation result; | |
| *result.mutable_duplicate_region_with_selection() = message_; | |
| return result; | |
| } | |
| std::unordered_set<uint32_t> | |
| TransformationDuplicateRegionWithSelection::GetFreshIds() const { | |
| std::unordered_set<uint32_t> result = {message_.new_entry_fresh_id(), | |
| message_.merge_label_fresh_id()}; | |
| for (auto& pair : message_.original_label_to_duplicate_label()) { | |
| result.insert(pair.second()); | |
| } | |
| for (auto& pair : message_.original_id_to_duplicate_id()) { | |
| result.insert(pair.second()); | |
| } | |
| for (auto& pair : message_.original_id_to_phi_id()) { | |
| result.insert(pair.second()); | |
| } | |
| return result; | |
| } | |
| bool TransformationDuplicateRegionWithSelection::AvailableAfterRegion( | |
| const opt::Instruction& instr, opt::BasicBlock* exit_block, | |
| opt::IRContext* ir_context) { | |
| opt::Instruction* final_instruction_in_region = &*exit_block->tail(); | |
| return &instr == final_instruction_in_region || | |
| fuzzerutil::IdIsAvailableBeforeInstruction( | |
| ir_context, final_instruction_in_region, instr.result_id()); | |
| } | |
| bool TransformationDuplicateRegionWithSelection::ValidOpPhiArgument( | |
| const opt::Instruction& instr, opt::IRContext* ir_context) { | |
| opt::Instruction* instr_type = | |
| ir_context->get_def_use_mgr()->GetDef(instr.type_id()); | |
| // It is invalid to apply OpPhi to void-typed values. | |
| if (instr_type->opcode() == spv::Op::OpTypeVoid) { | |
| return false; | |
| } | |
| // Using pointers with OpPhi requires capability VariablePointers. | |
| if (instr_type->opcode() == spv::Op::OpTypePointer && | |
| !ir_context->get_feature_mgr()->HasCapability( | |
| spv::Capability::VariablePointers)) { | |
| return false; | |
| } | |
| // OpTypeSampledImage cannot be the result type of an OpPhi instruction. | |
| if (instr_type->opcode() == spv::Op::OpTypeSampledImage) { | |
| return false; | |
| } | |
| return true; | |
| } | |
| } // namespace fuzz | |
| } // namespace spvtools |