// Copyright (c) 2017 Google Inc. // // 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/opt/merge_return_pass.h" #include #include #include #include "source/opt/instruction.h" #include "source/opt/ir_builder.h" #include "source/opt/ir_context.h" #include "source/opt/reflect.h" #include "source/util/bit_vector.h" #include "source/util/make_unique.h" namespace spvtools { namespace opt { Pass::Status MergeReturnPass::Process() { bool is_shader = context()->get_feature_mgr()->HasCapability(spv::Capability::Shader); bool failed = false; ProcessFunction pfn = [&failed, is_shader, this](Function* function) { std::vector return_blocks = CollectReturnBlocks(function); if (return_blocks.size() <= 1) { if (!is_shader || return_blocks.size() == 0) { return false; } bool isInConstruct = context()->GetStructuredCFGAnalysis()->ContainingConstruct( return_blocks[0]->id()) != 0; bool endsWithReturn = return_blocks[0] == function->tail(); if (!isInConstruct && endsWithReturn) { return false; } } function_ = function; return_flag_ = nullptr; return_value_ = nullptr; final_return_block_ = nullptr; if (is_shader) { if (!ProcessStructured(function, return_blocks)) { failed = true; } } else { MergeReturnBlocks(function, return_blocks); } return true; }; bool modified = context()->ProcessReachableCallTree(pfn); if (failed) { return Status::Failure; } return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange; } void MergeReturnPass::GenerateState(BasicBlock* block) { if (Instruction* mergeInst = block->GetMergeInst()) { if (mergeInst->opcode() == spv::Op::OpLoopMerge) { // If new loop, break to this loop merge block state_.emplace_back(mergeInst, mergeInst); } else { auto branchInst = mergeInst->NextNode(); if (branchInst->opcode() == spv::Op::OpSwitch) { // If switch inside of loop, break to innermost loop merge block. // Otherwise need to break to this switch merge block. auto lastMergeInst = state_.back().BreakMergeInst(); if (lastMergeInst && lastMergeInst->opcode() == spv::Op::OpLoopMerge) state_.emplace_back(lastMergeInst, mergeInst); else state_.emplace_back(mergeInst, mergeInst); } else { // If branch conditional inside loop, always break to innermost // loop merge block. If branch conditional inside switch, break to // innermost switch merge block. auto lastMergeInst = state_.back().BreakMergeInst(); state_.emplace_back(lastMergeInst, mergeInst); } } } } bool MergeReturnPass::ProcessStructured( Function* function, const std::vector& return_blocks) { if (HasNontrivialUnreachableBlocks(function)) { if (consumer()) { std::string message = "Module contains unreachable blocks during merge return. Run dead " "branch elimination before merge return."; consumer()(SPV_MSG_ERROR, 0, {0, 0, 0}, message.c_str()); } return false; } RecordImmediateDominators(function); if (!AddSingleCaseSwitchAroundFunction()) { return false; } std::list order; cfg()->ComputeStructuredOrder(function, &*function->begin(), &order); state_.clear(); state_.emplace_back(nullptr, nullptr); for (auto block : order) { if (cfg()->IsPseudoEntryBlock(block) || cfg()->IsPseudoExitBlock(block) || block == final_return_block_) { continue; } auto blockId = block->GetLabelInst()->result_id(); if (blockId == CurrentState().CurrentMergeId()) { // Pop the current state as we've hit the merge state_.pop_back(); } ProcessStructuredBlock(block); // Generate state for next block if warranted GenerateState(block); } state_.clear(); state_.emplace_back(nullptr, nullptr); std::unordered_set predicated; for (auto block : order) { if (cfg()->IsPseudoEntryBlock(block) || cfg()->IsPseudoExitBlock(block)) { continue; } auto blockId = block->id(); if (blockId == CurrentState().CurrentMergeId()) { // Pop the current state as we've hit the merge state_.pop_back(); } // Predicate successors of the original return blocks as necessary. if (std::find(return_blocks.begin(), return_blocks.end(), block) != return_blocks.end()) { if (!PredicateBlocks(block, &predicated, &order)) { return false; } } // Generate state for next block if warranted GenerateState(block); } // We have not kept the dominator tree up-to-date. // Invalidate it at this point to make sure it will be rebuilt. context()->RemoveDominatorAnalysis(function); AddNewPhiNodes(); return true; } void MergeReturnPass::CreateReturnBlock() { // Create a label for the new return block std::unique_ptr return_label( new Instruction(context(), spv::Op::OpLabel, 0u, TakeNextId(), {})); // Create the new basic block std::unique_ptr return_block( new BasicBlock(std::move(return_label))); function_->AddBasicBlock(std::move(return_block)); final_return_block_ = &*(--function_->end()); context()->AnalyzeDefUse(final_return_block_->GetLabelInst()); context()->set_instr_block(final_return_block_->GetLabelInst(), final_return_block_); assert(final_return_block_->GetParent() == function_ && "The function should have been set when the block was created."); } void MergeReturnPass::CreateReturn(BasicBlock* block) { AddReturnValue(); if (return_value_) { // Load and return the final return value uint32_t loadId = TakeNextId(); block->AddInstruction(MakeUnique( context(), spv::Op::OpLoad, function_->type_id(), loadId, std::initializer_list{ {SPV_OPERAND_TYPE_ID, {return_value_->result_id()}}})); Instruction* var_inst = block->terminator(); context()->AnalyzeDefUse(var_inst); context()->set_instr_block(var_inst, block); context()->get_decoration_mgr()->CloneDecorations( return_value_->result_id(), loadId, {spv::Decoration::RelaxedPrecision}); block->AddInstruction(MakeUnique( context(), spv::Op::OpReturnValue, 0, 0, std::initializer_list{{SPV_OPERAND_TYPE_ID, {loadId}}})); context()->AnalyzeDefUse(block->terminator()); context()->set_instr_block(block->terminator(), block); } else { block->AddInstruction( MakeUnique(context(), spv::Op::OpReturn)); context()->AnalyzeDefUse(block->terminator()); context()->set_instr_block(block->terminator(), block); } } void MergeReturnPass::ProcessStructuredBlock(BasicBlock* block) { spv::Op tail_opcode = block->tail()->opcode(); if (tail_opcode == spv::Op::OpReturn || tail_opcode == spv::Op::OpReturnValue) { if (!return_flag_) { AddReturnFlag(); } } if (tail_opcode == spv::Op::OpReturn || tail_opcode == spv::Op::OpReturnValue || tail_opcode == spv::Op::OpUnreachable) { assert(CurrentState().InBreakable() && "Should be in the placeholder construct."); BranchToBlock(block, CurrentState().BreakMergeId()); return_blocks_.insert(block->id()); } } void MergeReturnPass::BranchToBlock(BasicBlock* block, uint32_t target) { if (block->tail()->opcode() == spv::Op::OpReturn || block->tail()->opcode() == spv::Op::OpReturnValue) { RecordReturned(block); RecordReturnValue(block); } BasicBlock* target_block = context()->get_instr_block(target); if (target_block->GetLoopMergeInst()) { cfg()->SplitLoopHeader(target_block); } UpdatePhiNodes(block, target_block); Instruction* return_inst = block->terminator(); return_inst->SetOpcode(spv::Op::OpBranch); return_inst->ReplaceOperands({{SPV_OPERAND_TYPE_ID, {target}}}); context()->get_def_use_mgr()->AnalyzeInstDefUse(return_inst); new_edges_[target_block].insert(block->id()); cfg()->AddEdge(block->id(), target); } void MergeReturnPass::UpdatePhiNodes(BasicBlock* new_source, BasicBlock* target) { target->ForEachPhiInst([this, new_source](Instruction* inst) { uint32_t undefId = Type2Undef(inst->type_id()); inst->AddOperand({SPV_OPERAND_TYPE_ID, {undefId}}); inst->AddOperand({SPV_OPERAND_TYPE_ID, {new_source->id()}}); context()->UpdateDefUse(inst); }); } void MergeReturnPass::CreatePhiNodesForInst(BasicBlock* merge_block, Instruction& inst) { DominatorAnalysis* dom_tree = context()->GetDominatorAnalysis(merge_block->GetParent()); if (inst.result_id() != 0) { BasicBlock* inst_bb = context()->get_instr_block(&inst); std::vector users_to_update; context()->get_def_use_mgr()->ForEachUser( &inst, [&users_to_update, &dom_tree, &inst, inst_bb, this](Instruction* user) { BasicBlock* user_bb = nullptr; if (user->opcode() != spv::Op::OpPhi) { user_bb = context()->get_instr_block(user); } else { // For OpPhi, the use should be considered to be in the predecessor. for (uint32_t i = 0; i < user->NumInOperands(); i += 2) { if (user->GetSingleWordInOperand(i) == inst.result_id()) { uint32_t user_bb_id = user->GetSingleWordInOperand(i + 1); user_bb = context()->get_instr_block(user_bb_id); break; } } } // If |user_bb| is nullptr, then |user| is not in the function. It is // something like an OpName or decoration, which should not be // replaced with the result of the OpPhi. if (user_bb && !dom_tree->Dominates(inst_bb, user_bb)) { users_to_update.push_back(user); } }); if (users_to_update.empty()) { return; } // There is at least one values that needs to be replaced. // First create the OpPhi instruction. uint32_t undef_id = Type2Undef(inst.type_id()); std::vector phi_operands; const std::set& new_edges = new_edges_[merge_block]; // Add the OpPhi operands. If the predecessor is a return block use undef, // otherwise use |inst|'s id. std::vector preds = cfg()->preds(merge_block->id()); for (uint32_t pred_id : preds) { if (new_edges.count(pred_id)) { phi_operands.push_back(undef_id); } else { phi_operands.push_back(inst.result_id()); } phi_operands.push_back(pred_id); } Instruction* new_phi = nullptr; // If the instruction is a pointer and variable pointers are not an option, // then we have to regenerate the instruction instead of creating an OpPhi // instruction. If not, the Spir-V will be invalid. Instruction* inst_type = get_def_use_mgr()->GetDef(inst.type_id()); bool regenerateInstruction = false; if (inst_type->opcode() == spv::Op::OpTypePointer) { if (!context()->get_feature_mgr()->HasCapability( spv::Capability::VariablePointers)) { regenerateInstruction = true; } auto storage_class = spv::StorageClass(inst_type->GetSingleWordInOperand(0)); if (storage_class != spv::StorageClass::Workgroup && storage_class != spv::StorageClass::StorageBuffer) { regenerateInstruction = true; } } if (regenerateInstruction) { std::unique_ptr regen_inst(inst.Clone(context())); uint32_t new_id = TakeNextId(); regen_inst->SetResultId(new_id); Instruction* insert_pos = &*merge_block->begin(); while (insert_pos->opcode() == spv::Op::OpPhi) { insert_pos = insert_pos->NextNode(); } new_phi = insert_pos->InsertBefore(std::move(regen_inst)); get_def_use_mgr()->AnalyzeInstDefUse(new_phi); context()->set_instr_block(new_phi, merge_block); new_phi->ForEachInId([dom_tree, merge_block, this](uint32_t* use_id) { Instruction* use = get_def_use_mgr()->GetDef(*use_id); BasicBlock* use_bb = context()->get_instr_block(use); if (use_bb != nullptr && !dom_tree->Dominates(use_bb, merge_block)) { CreatePhiNodesForInst(merge_block, *use); } }); } else { InstructionBuilder builder( context(), &*merge_block->begin(), IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping); new_phi = builder.AddPhi(inst.type_id(), phi_operands); } uint32_t result_of_phi = new_phi->result_id(); // Update all of the users to use the result of the new OpPhi. for (Instruction* user : users_to_update) { user->ForEachInId([&inst, result_of_phi](uint32_t* id) { if (*id == inst.result_id()) { *id = result_of_phi; } }); context()->AnalyzeUses(user); } } } bool MergeReturnPass::PredicateBlocks( BasicBlock* return_block, std::unordered_set* predicated, std::list* order) { // The CFG is being modified as the function proceeds so avoid caching // successors. if (predicated->count(return_block)) { return true; } BasicBlock* block = nullptr; const BasicBlock* const_block = const_cast(return_block); const_block->ForEachSuccessorLabel([this, &block](const uint32_t idx) { BasicBlock* succ_block = context()->get_instr_block(idx); assert(block == nullptr); block = succ_block; }); assert(block && "Return blocks should have returns already replaced by a single " "unconditional branch."); auto state = state_.rbegin(); std::unordered_set seen; if (block->id() == state->CurrentMergeId()) { state++; } else if (block->id() == state->BreakMergeId()) { while (state->BreakMergeId() == block->id()) { state++; } } while (block != nullptr && block != final_return_block_) { if (!predicated->insert(block).second) break; // Skip structured subgraphs. assert(state->InBreakable() && "Should be in the placeholder construct at the very least."); Instruction* break_merge_inst = state->BreakMergeInst(); uint32_t merge_block_id = break_merge_inst->GetSingleWordInOperand(0); while (state->BreakMergeId() == merge_block_id) { state++; } if (!BreakFromConstruct(block, predicated, order, break_merge_inst)) { return false; } block = context()->get_instr_block(merge_block_id); } return true; } bool MergeReturnPass::BreakFromConstruct( BasicBlock* block, std::unordered_set* predicated, std::list* order, Instruction* break_merge_inst) { // Make sure the CFG is build here. If we don't then it becomes very hard // to know which new blocks need to be updated. context()->InvalidateAnalyses(IRContext::kAnalysisCFG); context()->BuildInvalidAnalyses(IRContext::kAnalysisCFG); // When predicating, be aware of whether this block is a header block, a // merge block or both. // // If this block is a merge block, ensure the appropriate header stays // up-to-date with any changes (i.e. points to the pre-header). // // If this block is a header block, predicate the entire structured // subgraph. This can act recursively. // If |block| is a loop header, then the back edge must jump to the original // code, not the new header. if (block->GetLoopMergeInst()) { if (cfg()->SplitLoopHeader(block) == nullptr) { return false; } } uint32_t merge_block_id = break_merge_inst->GetSingleWordInOperand(0); BasicBlock* merge_block = context()->get_instr_block(merge_block_id); if (merge_block->GetLoopMergeInst()) { cfg()->SplitLoopHeader(merge_block); } // Leave the phi instructions behind. auto iter = block->begin(); while (iter->opcode() == spv::Op::OpPhi) { ++iter; } // Forget about the edges leaving block. They will be removed. cfg()->RemoveSuccessorEdges(block); auto old_body_id = TakeNextId(); BasicBlock* old_body = block->SplitBasicBlock(context(), old_body_id, iter); predicated->insert(old_body); // If a return block is being split, mark the new body block also as a return // block. if (return_blocks_.count(block->id())) { return_blocks_.insert(old_body_id); } // If |block| was a continue target for a loop |old_body| is now the correct // continue target. if (break_merge_inst->opcode() == spv::Op::OpLoopMerge && break_merge_inst->GetSingleWordInOperand(1) == block->id()) { break_merge_inst->SetInOperand(1, {old_body->id()}); context()->UpdateDefUse(break_merge_inst); } // Update |order| so old_block will be traversed. InsertAfterElement(block, old_body, order); // Within the new header we need the following: // 1. Load of the return status flag // 2. Branch to |merge_block| (true) or old body (false) // 3. Update OpPhi instructions in |merge_block|. // 4. Update the CFG. // // Since we are branching to the merge block of the current construct, there // is no need for an OpSelectionMerge. InstructionBuilder builder( context(), block, IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping); // 1. Load of the return status flag analysis::Bool bool_type; uint32_t bool_id = context()->get_type_mgr()->GetId(&bool_type); assert(bool_id != 0); uint32_t load_id = builder.AddLoad(bool_id, return_flag_->result_id())->result_id(); // 2. Branch to |merge_block| (true) or |old_body| (false) builder.AddConditionalBranch(load_id, merge_block->id(), old_body->id(), old_body->id()); if (!new_edges_[merge_block].insert(block->id()).second) { // It is possible that we already inserted a new edge to the merge block. // If so, that edge now goes from |old_body| to |merge_block|. new_edges_[merge_block].insert(old_body->id()); } // 3. Update OpPhi instructions in |merge_block|. UpdatePhiNodes(block, merge_block); // 4. Update the CFG. We do this after updating the OpPhi instructions // because |UpdatePhiNodes| assumes the edge from |block| has not been added // to the CFG yet. cfg()->AddEdges(block); cfg()->RegisterBlock(old_body); assert(old_body->begin() != old_body->end()); assert(block->begin() != block->end()); return true; } void MergeReturnPass::RecordReturned(BasicBlock* block) { if (block->tail()->opcode() != spv::Op::OpReturn && block->tail()->opcode() != spv::Op::OpReturnValue) return; assert(return_flag_ && "Did not generate the return flag variable."); if (!constant_true_) { analysis::Bool temp; const analysis::Bool* bool_type = context()->get_type_mgr()->GetRegisteredType(&temp)->AsBool(); analysis::ConstantManager* const_mgr = context()->get_constant_mgr(); const analysis::Constant* true_const = const_mgr->GetConstant(bool_type, {true}); constant_true_ = const_mgr->GetDefiningInstruction(true_const); context()->UpdateDefUse(constant_true_); } std::unique_ptr return_store(new Instruction( context(), spv::Op::OpStore, 0, 0, std::initializer_list{ {SPV_OPERAND_TYPE_ID, {return_flag_->result_id()}}, {SPV_OPERAND_TYPE_ID, {constant_true_->result_id()}}})); Instruction* store_inst = &*block->tail().InsertBefore(std::move(return_store)); context()->set_instr_block(store_inst, block); context()->AnalyzeDefUse(store_inst); } void MergeReturnPass::RecordReturnValue(BasicBlock* block) { auto terminator = *block->tail(); if (terminator.opcode() != spv::Op::OpReturnValue) { return; } assert(return_value_ && "Did not generate the variable to hold the return value."); std::unique_ptr value_store(new Instruction( context(), spv::Op::OpStore, 0, 0, std::initializer_list{ {SPV_OPERAND_TYPE_ID, {return_value_->result_id()}}, {SPV_OPERAND_TYPE_ID, {terminator.GetSingleWordInOperand(0u)}}})); Instruction* store_inst = &*block->tail().InsertBefore(std::move(value_store)); context()->set_instr_block(store_inst, block); context()->AnalyzeDefUse(store_inst); } void MergeReturnPass::AddReturnValue() { if (return_value_) return; uint32_t return_type_id = function_->type_id(); if (get_def_use_mgr()->GetDef(return_type_id)->opcode() == spv::Op::OpTypeVoid) return; uint32_t return_ptr_type = context()->get_type_mgr()->FindPointerToType( return_type_id, spv::StorageClass::Function); uint32_t var_id = TakeNextId(); std::unique_ptr returnValue( new Instruction(context(), spv::Op::OpVariable, return_ptr_type, var_id, std::initializer_list{ {SPV_OPERAND_TYPE_STORAGE_CLASS, {uint32_t(spv::StorageClass::Function)}}})); auto insert_iter = function_->begin()->begin(); insert_iter.InsertBefore(std::move(returnValue)); BasicBlock* entry_block = &*function_->begin(); return_value_ = &*entry_block->begin(); context()->AnalyzeDefUse(return_value_); context()->set_instr_block(return_value_, entry_block); context()->get_decoration_mgr()->CloneDecorations( function_->result_id(), var_id, {spv::Decoration::RelaxedPrecision}); } void MergeReturnPass::AddReturnFlag() { if (return_flag_) return; analysis::TypeManager* type_mgr = context()->get_type_mgr(); analysis::ConstantManager* const_mgr = context()->get_constant_mgr(); analysis::Bool temp; uint32_t bool_id = type_mgr->GetTypeInstruction(&temp); analysis::Bool* bool_type = type_mgr->GetType(bool_id)->AsBool(); const analysis::Constant* false_const = const_mgr->GetConstant(bool_type, {false}); uint32_t const_false_id = const_mgr->GetDefiningInstruction(false_const)->result_id(); uint32_t bool_ptr_id = type_mgr->FindPointerToType(bool_id, spv::StorageClass::Function); uint32_t var_id = TakeNextId(); std::unique_ptr returnFlag(new Instruction( context(), spv::Op::OpVariable, bool_ptr_id, var_id, std::initializer_list{{SPV_OPERAND_TYPE_STORAGE_CLASS, {uint32_t(spv::StorageClass::Function)}}, {SPV_OPERAND_TYPE_ID, {const_false_id}}})); auto insert_iter = function_->begin()->begin(); insert_iter.InsertBefore(std::move(returnFlag)); BasicBlock* entry_block = &*function_->begin(); return_flag_ = &*entry_block->begin(); context()->AnalyzeDefUse(return_flag_); context()->set_instr_block(return_flag_, entry_block); } std::vector MergeReturnPass::CollectReturnBlocks( Function* function) { std::vector return_blocks; for (auto& block : *function) { Instruction& terminator = *block.tail(); if (terminator.opcode() == spv::Op::OpReturn || terminator.opcode() == spv::Op::OpReturnValue) { return_blocks.push_back(&block); } } return return_blocks; } void MergeReturnPass::MergeReturnBlocks( Function* function, const std::vector& return_blocks) { if (return_blocks.size() <= 1) { // No work to do. return; } CreateReturnBlock(); uint32_t return_id = final_return_block_->id(); auto ret_block_iter = --function->end(); // Create the PHI for the merged block (if necessary). // Create new return. std::vector phi_ops; for (auto block : return_blocks) { if (block->tail()->opcode() == spv::Op::OpReturnValue) { phi_ops.push_back( {SPV_OPERAND_TYPE_ID, {block->tail()->GetSingleWordInOperand(0u)}}); phi_ops.push_back({SPV_OPERAND_TYPE_ID, {block->id()}}); } } if (!phi_ops.empty()) { // Need a PHI node to select the correct return value. uint32_t phi_result_id = TakeNextId(); uint32_t phi_type_id = function->type_id(); std::unique_ptr phi_inst(new Instruction( context(), spv::Op::OpPhi, phi_type_id, phi_result_id, phi_ops)); ret_block_iter->AddInstruction(std::move(phi_inst)); BasicBlock::iterator phiIter = ret_block_iter->tail(); std::unique_ptr return_inst( new Instruction(context(), spv::Op::OpReturnValue, 0u, 0u, {{SPV_OPERAND_TYPE_ID, {phi_result_id}}})); ret_block_iter->AddInstruction(std::move(return_inst)); BasicBlock::iterator ret = ret_block_iter->tail(); // Register the phi def and mark instructions for use updates. get_def_use_mgr()->AnalyzeInstDefUse(&*phiIter); get_def_use_mgr()->AnalyzeInstDef(&*ret); } else { std::unique_ptr return_inst( new Instruction(context(), spv::Op::OpReturn)); ret_block_iter->AddInstruction(std::move(return_inst)); } // Replace returns with branches for (auto block : return_blocks) { context()->ForgetUses(block->terminator()); block->tail()->SetOpcode(spv::Op::OpBranch); block->tail()->ReplaceOperands({{SPV_OPERAND_TYPE_ID, {return_id}}}); get_def_use_mgr()->AnalyzeInstUse(block->terminator()); get_def_use_mgr()->AnalyzeInstUse(block->GetLabelInst()); } get_def_use_mgr()->AnalyzeInstDefUse(ret_block_iter->GetLabelInst()); } void MergeReturnPass::AddNewPhiNodes() { std::list order; cfg()->ComputeStructuredOrder(function_, &*function_->begin(), &order); for (BasicBlock* bb : order) { AddNewPhiNodes(bb); } } void MergeReturnPass::AddNewPhiNodes(BasicBlock* bb) { // New phi nodes are needed for any id whose definition used to dominate |bb|, // but no longer dominates |bb|. These are found by walking the dominator // tree starting at the original immediate dominator of |bb| and ending at its // current dominator. // Because we are walking the updated dominator tree it is important that the // new phi nodes for the original dominators of |bb| have already been added. // Otherwise some ids might be missed. Consider the case where bb1 dominates // bb2, and bb2 dominates bb3. Suppose there are changes such that bb1 no // longer dominates bb2 and the same for bb2 and bb3. This algorithm will not // look at the ids defined in bb1. However, calling |AddNewPhiNodes(bb2)| // first will add a phi node in bb2 for that value. Then a call to // |AddNewPhiNodes(bb3)| will process that value by processing the phi in bb2. DominatorAnalysis* dom_tree = context()->GetDominatorAnalysis(function_); BasicBlock* dominator = dom_tree->ImmediateDominator(bb); if (dominator == nullptr) { return; } BasicBlock* current_bb = context()->get_instr_block(original_dominator_[bb]); while (current_bb != nullptr && current_bb != dominator) { for (Instruction& inst : *current_bb) { CreatePhiNodesForInst(bb, inst); } current_bb = dom_tree->ImmediateDominator(current_bb); } } void MergeReturnPass::RecordImmediateDominators(Function* function) { DominatorAnalysis* dom_tree = context()->GetDominatorAnalysis(function); for (BasicBlock& bb : *function) { BasicBlock* dominator_bb = dom_tree->ImmediateDominator(&bb); if (dominator_bb && dominator_bb != cfg()->pseudo_entry_block()) { original_dominator_[&bb] = dominator_bb->terminator(); } else { original_dominator_[&bb] = nullptr; } } } void MergeReturnPass::InsertAfterElement(BasicBlock* element, BasicBlock* new_element, std::list* list) { auto pos = std::find(list->begin(), list->end(), element); assert(pos != list->end()); ++pos; list->insert(pos, new_element); } bool MergeReturnPass::AddSingleCaseSwitchAroundFunction() { CreateReturnBlock(); CreateReturn(final_return_block_); if (context()->AreAnalysesValid(IRContext::kAnalysisCFG)) { cfg()->RegisterBlock(final_return_block_); } if (!CreateSingleCaseSwitch(final_return_block_)) { return false; } return true; } BasicBlock* MergeReturnPass::CreateContinueTarget(uint32_t header_label_id) { std::unique_ptr label( new Instruction(context(), spv::Op::OpLabel, 0u, TakeNextId(), {})); // Create the new basic block std::unique_ptr block(new BasicBlock(std::move(label))); // Insert the new block just before the return block auto pos = function_->end(); assert(pos != function_->begin()); pos--; assert(pos != function_->begin()); assert(&*pos == final_return_block_); auto new_block = &*pos.InsertBefore(std::move(block)); new_block->SetParent(function_); context()->AnalyzeDefUse(new_block->GetLabelInst()); context()->set_instr_block(new_block->GetLabelInst(), new_block); InstructionBuilder builder( context(), new_block, IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping); builder.AddBranch(header_label_id); if (context()->AreAnalysesValid(IRContext::kAnalysisCFG)) { cfg()->RegisterBlock(new_block); } return new_block; } bool MergeReturnPass::CreateSingleCaseSwitch(BasicBlock* merge_target) { // Insert the switch before any code is run. We have to split the entry // block to make sure the OpVariable instructions remain in the entry block. BasicBlock* start_block = &*function_->begin(); auto split_pos = start_block->begin(); while (split_pos->opcode() == spv::Op::OpVariable) { ++split_pos; } BasicBlock* old_block = start_block->SplitBasicBlock(context(), TakeNextId(), split_pos); // Add the switch to the end of the entry block. InstructionBuilder builder( context(), start_block, IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping); uint32_t const_zero_id = builder.GetUintConstantId(0u); if (const_zero_id == 0) { return false; } builder.AddSwitch(const_zero_id, old_block->id(), {}, merge_target->id()); if (context()->AreAnalysesValid(IRContext::kAnalysisCFG)) { cfg()->RegisterBlock(old_block); cfg()->AddEdges(start_block); } return true; } bool MergeReturnPass::HasNontrivialUnreachableBlocks(Function* function) { utils::BitVector reachable_blocks; cfg()->ForEachBlockInPostOrder( function->entry().get(), [&reachable_blocks](BasicBlock* bb) { reachable_blocks.Set(bb->id()); }); for (auto& bb : *function) { if (reachable_blocks.Get(bb.id())) { continue; } StructuredCFGAnalysis* struct_cfg_analysis = context()->GetStructuredCFGAnalysis(); if (struct_cfg_analysis->IsContinueBlock(bb.id())) { // |bb| must be an empty block ending with a branch to the header. Instruction* inst = &*bb.begin(); if (inst->opcode() != spv::Op::OpBranch) { return true; } if (inst->GetSingleWordInOperand(0) != struct_cfg_analysis->ContainingLoop(bb.id())) { return true; } } else if (struct_cfg_analysis->IsMergeBlock(bb.id())) { // |bb| must be an empty block ending with OpUnreachable. if (bb.begin()->opcode() != spv::Op::OpUnreachable) { return true; } } else { return true; } } return false; } } // namespace opt } // namespace spvtools