| // Copyright (c) 2018 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/opt/loop_unswitch_pass.h" | |
| #include <functional> | |
| #include <list> | |
| #include <memory> | |
| #include <unordered_map> | |
| #include <unordered_set> | |
| #include <utility> | |
| #include <vector> | |
| #include "source/opt/basic_block.h" | |
| #include "source/opt/dominator_tree.h" | |
| #include "source/opt/fold.h" | |
| #include "source/opt/function.h" | |
| #include "source/opt/instruction.h" | |
| #include "source/opt/ir_builder.h" | |
| #include "source/opt/ir_context.h" | |
| #include "source/opt/loop_descriptor.h" | |
| #include "source/opt/loop_utils.h" | |
| namespace spvtools { | |
| namespace opt { | |
| namespace { | |
| constexpr uint32_t kTypePointerStorageClassInIdx = 0; | |
| // This class handle the unswitch procedure for a given loop. | |
| // The unswitch will not happen if: | |
| // - The loop has any instruction that will prevent it; | |
| // - The loop invariant condition is not uniform. | |
| class LoopUnswitch { | |
| public: | |
| LoopUnswitch(IRContext* context, Function* function, Loop* loop, | |
| LoopDescriptor* loop_desc) | |
| : function_(function), | |
| loop_(loop), | |
| loop_desc_(*loop_desc), | |
| context_(context), | |
| switch_block_(nullptr) {} | |
| // Returns true if the loop can be unswitched. | |
| // Can be unswitch if: | |
| // - The loop has no instructions that prevents it (such as barrier); | |
| // - The loop has one conditional branch or switch that do not depends on the | |
| // loop; | |
| // - The loop invariant condition is uniform; | |
| bool CanUnswitchLoop() { | |
| if (switch_block_) return true; | |
| if (loop_->IsSafeToClone()) return false; | |
| CFG& cfg = *context_->cfg(); | |
| for (uint32_t bb_id : loop_->GetBlocks()) { | |
| BasicBlock* bb = cfg.block(bb_id); | |
| if (loop_->GetLatchBlock() == bb) { | |
| continue; | |
| } | |
| if (bb->terminator()->IsBranch() && | |
| bb->terminator()->opcode() != spv::Op::OpBranch) { | |
| if (IsConditionNonConstantLoopInvariant(bb->terminator())) { | |
| switch_block_ = bb; | |
| break; | |
| } | |
| } | |
| } | |
| return switch_block_; | |
| } | |
| // Return the iterator to the basic block |bb|. | |
| Function::iterator FindBasicBlockPosition(BasicBlock* bb_to_find) { | |
| Function::iterator it = function_->FindBlock(bb_to_find->id()); | |
| assert(it != function_->end() && "Basic Block not found"); | |
| return it; | |
| } | |
| // Creates a new basic block and insert it into the function |fn| at the | |
| // position |ip|. This function preserves the def/use and instr to block | |
| // managers. | |
| BasicBlock* CreateBasicBlock(Function::iterator ip) { | |
| analysis::DefUseManager* def_use_mgr = context_->get_def_use_mgr(); | |
| // TODO(1841): Handle id overflow. | |
| BasicBlock* bb = &*ip.InsertBefore(std::unique_ptr<BasicBlock>( | |
| new BasicBlock(std::unique_ptr<Instruction>(new Instruction( | |
| context_, spv::Op::OpLabel, 0, context_->TakeNextId(), {}))))); | |
| bb->SetParent(function_); | |
| def_use_mgr->AnalyzeInstDef(bb->GetLabelInst()); | |
| context_->set_instr_block(bb->GetLabelInst(), bb); | |
| return bb; | |
| } | |
| Instruction* GetValueForDefaultPathForSwitch(Instruction* switch_inst) { | |
| assert(switch_inst->opcode() == spv::Op::OpSwitch && | |
| "The given instructoin must be an OpSwitch."); | |
| // Find a value that can be used to select the default path. | |
| // If none are possible, then it will just use 0. The value does not matter | |
| // because this path will never be taken because the new switch outside of | |
| // the loop cannot select this path either. | |
| std::vector<uint32_t> existing_values; | |
| for (uint32_t i = 2; i < switch_inst->NumInOperands(); i += 2) { | |
| existing_values.push_back(switch_inst->GetSingleWordInOperand(i)); | |
| } | |
| std::sort(existing_values.begin(), existing_values.end()); | |
| uint32_t value_for_default_path = 0; | |
| if (existing_values.size() < std::numeric_limits<uint32_t>::max()) { | |
| for (value_for_default_path = 0; | |
| value_for_default_path < existing_values.size(); | |
| value_for_default_path++) { | |
| if (existing_values[value_for_default_path] != value_for_default_path) { | |
| break; | |
| } | |
| } | |
| } | |
| InstructionBuilder builder( | |
| context_, static_cast<Instruction*>(nullptr), | |
| IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping); | |
| return builder.GetUintConstant(value_for_default_path); | |
| } | |
| // Unswitches |loop_|. | |
| void PerformUnswitch() { | |
| assert(CanUnswitchLoop() && | |
| "Cannot unswitch if there is not constant condition"); | |
| assert(loop_->GetPreHeaderBlock() && "This loop has no pre-header block"); | |
| assert(loop_->IsLCSSA() && "This loop is not in LCSSA form"); | |
| CFG& cfg = *context_->cfg(); | |
| DominatorTree* dom_tree = | |
| &context_->GetDominatorAnalysis(function_)->GetDomTree(); | |
| analysis::DefUseManager* def_use_mgr = context_->get_def_use_mgr(); | |
| LoopUtils loop_utils(context_, loop_); | |
| ////////////////////////////////////////////////////////////////////////////// | |
| // Step 1: Create the if merge block for structured modules. | |
| // To do so, the |loop_| merge block will become the if's one and we | |
| // create a merge for the loop. This will limit the amount of duplicated | |
| // code the structured control flow imposes. | |
| // For non structured program, the new loop will be connected to | |
| // the old loop's exit blocks. | |
| ////////////////////////////////////////////////////////////////////////////// | |
| // Get the merge block if it exists. | |
| BasicBlock* if_merge_block = loop_->GetMergeBlock(); | |
| // The merge block is only created if the loop has a unique exit block. We | |
| // have this guarantee for structured loops, for compute loop it will | |
| // trivially help maintain both a structured-like form and LCSAA. | |
| BasicBlock* loop_merge_block = | |
| if_merge_block | |
| ? CreateBasicBlock(FindBasicBlockPosition(if_merge_block)) | |
| : nullptr; | |
| if (loop_merge_block) { | |
| // Add the instruction and update managers. | |
| InstructionBuilder builder( | |
| context_, loop_merge_block, | |
| IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping); | |
| builder.AddBranch(if_merge_block->id()); | |
| builder.SetInsertPoint(&*loop_merge_block->begin()); | |
| cfg.RegisterBlock(loop_merge_block); | |
| def_use_mgr->AnalyzeInstDef(loop_merge_block->GetLabelInst()); | |
| // Update CFG. | |
| if_merge_block->ForEachPhiInst( | |
| [loop_merge_block, &builder, this](Instruction* phi) { | |
| Instruction* cloned = phi->Clone(context_); | |
| cloned->SetResultId(TakeNextId()); | |
| builder.AddInstruction(std::unique_ptr<Instruction>(cloned)); | |
| phi->SetInOperand(0, {cloned->result_id()}); | |
| phi->SetInOperand(1, {loop_merge_block->id()}); | |
| for (uint32_t j = phi->NumInOperands() - 1; j > 1; j--) | |
| phi->RemoveInOperand(j); | |
| }); | |
| // Copy the predecessor list (will get invalidated otherwise). | |
| std::vector<uint32_t> preds = cfg.preds(if_merge_block->id()); | |
| for (uint32_t pid : preds) { | |
| if (pid == loop_merge_block->id()) continue; | |
| BasicBlock* p_bb = cfg.block(pid); | |
| p_bb->ForEachSuccessorLabel( | |
| [if_merge_block, loop_merge_block](uint32_t* id) { | |
| if (*id == if_merge_block->id()) *id = loop_merge_block->id(); | |
| }); | |
| cfg.AddEdge(pid, loop_merge_block->id()); | |
| } | |
| cfg.RemoveNonExistingEdges(if_merge_block->id()); | |
| // Update loop descriptor. | |
| if (Loop* ploop = loop_->GetParent()) { | |
| ploop->AddBasicBlock(loop_merge_block); | |
| loop_desc_.SetBasicBlockToLoop(loop_merge_block->id(), ploop); | |
| } | |
| // Update the dominator tree. | |
| DominatorTreeNode* loop_merge_dtn = | |
| dom_tree->GetOrInsertNode(loop_merge_block); | |
| DominatorTreeNode* if_merge_block_dtn = | |
| dom_tree->GetOrInsertNode(if_merge_block); | |
| loop_merge_dtn->parent_ = if_merge_block_dtn->parent_; | |
| loop_merge_dtn->children_.push_back(if_merge_block_dtn); | |
| loop_merge_dtn->parent_->children_.push_back(loop_merge_dtn); | |
| if_merge_block_dtn->parent_->children_.erase(std::find( | |
| if_merge_block_dtn->parent_->children_.begin(), | |
| if_merge_block_dtn->parent_->children_.end(), if_merge_block_dtn)); | |
| loop_->SetMergeBlock(loop_merge_block); | |
| } | |
| //////////////////////////////////////////////////////////////////////////// | |
| // Step 2: Build a new preheader for |loop_|, use the old one | |
| // for the invariant branch. | |
| //////////////////////////////////////////////////////////////////////////// | |
| BasicBlock* if_block = loop_->GetPreHeaderBlock(); | |
| // If this preheader is the parent loop header, | |
| // we need to create a dedicated block for the if. | |
| BasicBlock* loop_pre_header = | |
| CreateBasicBlock(++FindBasicBlockPosition(if_block)); | |
| InstructionBuilder( | |
| context_, loop_pre_header, | |
| IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping) | |
| .AddBranch(loop_->GetHeaderBlock()->id()); | |
| if_block->tail()->SetInOperand(0, {loop_pre_header->id()}); | |
| // Update loop descriptor. | |
| if (Loop* ploop = loop_desc_[if_block]) { | |
| ploop->AddBasicBlock(loop_pre_header); | |
| loop_desc_.SetBasicBlockToLoop(loop_pre_header->id(), ploop); | |
| } | |
| // Update the CFG. | |
| cfg.RegisterBlock(loop_pre_header); | |
| def_use_mgr->AnalyzeInstDef(loop_pre_header->GetLabelInst()); | |
| cfg.AddEdge(if_block->id(), loop_pre_header->id()); | |
| cfg.RemoveNonExistingEdges(loop_->GetHeaderBlock()->id()); | |
| loop_->GetHeaderBlock()->ForEachPhiInst( | |
| [loop_pre_header, if_block](Instruction* phi) { | |
| phi->ForEachInId([loop_pre_header, if_block](uint32_t* id) { | |
| if (*id == if_block->id()) { | |
| *id = loop_pre_header->id(); | |
| } | |
| }); | |
| }); | |
| loop_->SetPreHeaderBlock(loop_pre_header); | |
| // Update the dominator tree. | |
| DominatorTreeNode* loop_pre_header_dtn = | |
| dom_tree->GetOrInsertNode(loop_pre_header); | |
| DominatorTreeNode* if_block_dtn = dom_tree->GetTreeNode(if_block); | |
| loop_pre_header_dtn->parent_ = if_block_dtn; | |
| assert( | |
| if_block_dtn->children_.size() == 1 && | |
| "A loop preheader should only have the header block as a child in the " | |
| "dominator tree"); | |
| loop_pre_header_dtn->children_.push_back(if_block_dtn->children_[0]); | |
| if_block_dtn->children_.clear(); | |
| if_block_dtn->children_.push_back(loop_pre_header_dtn); | |
| // Make domination queries valid. | |
| dom_tree->ResetDFNumbering(); | |
| // Compute an ordered list of basic block to clone: loop blocks + pre-header | |
| // + merge block. | |
| loop_->ComputeLoopStructuredOrder(&ordered_loop_blocks_, true, true); | |
| ///////////////////////////// | |
| // Do the actual unswitch: // | |
| // - Clone the loop // | |
| // - Connect exits // | |
| // - Specialize the loop // | |
| ///////////////////////////// | |
| Instruction* iv_condition = &*switch_block_->tail(); | |
| spv::Op iv_opcode = iv_condition->opcode(); | |
| Instruction* condition = | |
| def_use_mgr->GetDef(iv_condition->GetOperand(0).words[0]); | |
| analysis::ConstantManager* cst_mgr = context_->get_constant_mgr(); | |
| const analysis::Type* cond_type = | |
| context_->get_type_mgr()->GetType(condition->type_id()); | |
| // Build the list of value for which we need to clone and specialize the | |
| // loop. | |
| std::vector<std::pair<Instruction*, BasicBlock*>> constant_branch; | |
| // Special case for the original loop | |
| Instruction* original_loop_constant_value; | |
| if (iv_opcode == spv::Op::OpBranchConditional) { | |
| constant_branch.emplace_back( | |
| cst_mgr->GetDefiningInstruction(cst_mgr->GetConstant(cond_type, {0})), | |
| nullptr); | |
| original_loop_constant_value = | |
| cst_mgr->GetDefiningInstruction(cst_mgr->GetConstant(cond_type, {1})); | |
| } else { | |
| // We are looking to take the default branch, so we can't provide a | |
| // specific value. | |
| original_loop_constant_value = | |
| GetValueForDefaultPathForSwitch(iv_condition); | |
| for (uint32_t i = 2; i < iv_condition->NumInOperands(); i += 2) { | |
| constant_branch.emplace_back( | |
| cst_mgr->GetDefiningInstruction(cst_mgr->GetConstant( | |
| cond_type, iv_condition->GetInOperand(i).words)), | |
| nullptr); | |
| } | |
| } | |
| // Get the loop landing pads. | |
| std::unordered_set<uint32_t> if_merging_blocks; | |
| std::function<bool(uint32_t)> is_from_original_loop; | |
| if (loop_->GetHeaderBlock()->GetLoopMergeInst()) { | |
| if_merging_blocks.insert(if_merge_block->id()); | |
| is_from_original_loop = [this](uint32_t id) { | |
| return loop_->IsInsideLoop(id) || loop_->GetMergeBlock()->id() == id; | |
| }; | |
| } else { | |
| loop_->GetExitBlocks(&if_merging_blocks); | |
| is_from_original_loop = [this](uint32_t id) { | |
| return loop_->IsInsideLoop(id); | |
| }; | |
| } | |
| for (auto& specialisation_pair : constant_branch) { | |
| Instruction* specialisation_value = specialisation_pair.first; | |
| ////////////////////////////////////////////////////////// | |
| // Step 3: Duplicate |loop_|. | |
| ////////////////////////////////////////////////////////// | |
| LoopUtils::LoopCloningResult clone_result; | |
| Loop* cloned_loop = | |
| loop_utils.CloneLoop(&clone_result, ordered_loop_blocks_); | |
| specialisation_pair.second = cloned_loop->GetPreHeaderBlock(); | |
| //////////////////////////////////// | |
| // Step 4: Specialize the loop. // | |
| //////////////////////////////////// | |
| { | |
| SpecializeLoop(cloned_loop, condition, specialisation_value); | |
| /////////////////////////////////////////////////////////// | |
| // Step 5: Connect convergent edges to the landing pads. // | |
| /////////////////////////////////////////////////////////// | |
| for (uint32_t merge_bb_id : if_merging_blocks) { | |
| BasicBlock* merge = context_->cfg()->block(merge_bb_id); | |
| // We are in LCSSA so we only care about phi instructions. | |
| merge->ForEachPhiInst( | |
| [is_from_original_loop, &clone_result](Instruction* phi) { | |
| uint32_t num_in_operands = phi->NumInOperands(); | |
| for (uint32_t i = 0; i < num_in_operands; i += 2) { | |
| uint32_t pred = phi->GetSingleWordInOperand(i + 1); | |
| if (is_from_original_loop(pred)) { | |
| pred = clone_result.value_map_.at(pred); | |
| uint32_t incoming_value_id = phi->GetSingleWordInOperand(i); | |
| // Not all the incoming values are coming from the loop. | |
| ValueMapTy::iterator new_value = | |
| clone_result.value_map_.find(incoming_value_id); | |
| if (new_value != clone_result.value_map_.end()) { | |
| incoming_value_id = new_value->second; | |
| } | |
| phi->AddOperand({SPV_OPERAND_TYPE_ID, {incoming_value_id}}); | |
| phi->AddOperand({SPV_OPERAND_TYPE_ID, {pred}}); | |
| } | |
| } | |
| }); | |
| } | |
| } | |
| function_->AddBasicBlocks(clone_result.cloned_bb_.begin(), | |
| clone_result.cloned_bb_.end(), | |
| ++FindBasicBlockPosition(if_block)); | |
| } | |
| // Specialize the existing loop. | |
| SpecializeLoop(loop_, condition, original_loop_constant_value); | |
| BasicBlock* original_loop_target = loop_->GetPreHeaderBlock(); | |
| ///////////////////////////////////// | |
| // Finally: connect the new loops. // | |
| ///////////////////////////////////// | |
| // Delete the old jump | |
| context_->KillInst(&*if_block->tail()); | |
| InstructionBuilder builder(context_, if_block); | |
| if (iv_opcode == spv::Op::OpBranchConditional) { | |
| assert(constant_branch.size() == 1); | |
| builder.AddConditionalBranch( | |
| condition->result_id(), original_loop_target->id(), | |
| constant_branch[0].second->id(), | |
| if_merge_block ? if_merge_block->id() : kInvalidId); | |
| } else { | |
| std::vector<std::pair<Operand::OperandData, uint32_t>> targets; | |
| for (auto& t : constant_branch) { | |
| targets.emplace_back(t.first->GetInOperand(0).words, t.second->id()); | |
| } | |
| builder.AddSwitch(condition->result_id(), original_loop_target->id(), | |
| targets, | |
| if_merge_block ? if_merge_block->id() : kInvalidId); | |
| } | |
| switch_block_ = nullptr; | |
| ordered_loop_blocks_.clear(); | |
| context_->InvalidateAnalysesExceptFor( | |
| IRContext::Analysis::kAnalysisLoopAnalysis); | |
| } | |
| private: | |
| using ValueMapTy = std::unordered_map<uint32_t, uint32_t>; | |
| using BlockMapTy = std::unordered_map<uint32_t, BasicBlock*>; | |
| Function* function_; | |
| Loop* loop_; | |
| LoopDescriptor& loop_desc_; | |
| IRContext* context_; | |
| BasicBlock* switch_block_; | |
| // Map between instructions and if they are dynamically uniform. | |
| std::unordered_map<uint32_t, bool> dynamically_uniform_; | |
| // The loop basic blocks in structured order. | |
| std::vector<BasicBlock*> ordered_loop_blocks_; | |
| // Returns the next usable id for the context. | |
| uint32_t TakeNextId() { | |
| // TODO(1841): Handle id overflow. | |
| return context_->TakeNextId(); | |
| } | |
| // Simplifies |loop| assuming the instruction |to_version_insn| takes the | |
| // value |cst_value|. |block_range| is an iterator range returning the loop | |
| // basic blocks in a structured order (dominator first). | |
| // The function will ignore basic blocks returned by |block_range| if they | |
| // does not belong to the loop. | |
| // The set |dead_blocks| will contain all the dead basic blocks. | |
| // | |
| // Requirements: | |
| // - |loop| must be in the LCSSA form; | |
| // - |cst_value| must be constant. | |
| void SpecializeLoop(Loop* loop, Instruction* to_version_insn, | |
| Instruction* cst_value) { | |
| analysis::DefUseManager* def_use_mgr = context_->get_def_use_mgr(); | |
| std::function<bool(uint32_t)> ignore_node; | |
| ignore_node = [loop](uint32_t bb_id) { return !loop->IsInsideLoop(bb_id); }; | |
| std::vector<std::pair<Instruction*, uint32_t>> use_list; | |
| def_use_mgr->ForEachUse(to_version_insn, | |
| [&use_list, &ignore_node, this]( | |
| Instruction* inst, uint32_t operand_index) { | |
| BasicBlock* bb = context_->get_instr_block(inst); | |
| if (!bb || ignore_node(bb->id())) { | |
| // Out of the loop, the specialization does not | |
| // apply any more. | |
| return; | |
| } | |
| use_list.emplace_back(inst, operand_index); | |
| }); | |
| // First pass: inject the specialized value into the loop (and only the | |
| // loop). | |
| for (auto use : use_list) { | |
| Instruction* inst = use.first; | |
| uint32_t operand_index = use.second; | |
| // To also handle switch, cst_value can be nullptr: this case | |
| // means that we are looking to branch to the default target of | |
| // the switch. We don't actually know its value so we don't touch | |
| // it if it not a switch. | |
| assert(cst_value && "We do not have a value to use."); | |
| inst->SetOperand(operand_index, {cst_value->result_id()}); | |
| def_use_mgr->AnalyzeInstUse(inst); | |
| } | |
| } | |
| // Returns true if |var| is dynamically uniform. | |
| // Note: this is currently approximated as uniform. | |
| bool IsDynamicallyUniform(Instruction* var, const BasicBlock* entry, | |
| const DominatorTree& post_dom_tree) { | |
| assert(post_dom_tree.IsPostDominator()); | |
| analysis::DefUseManager* def_use_mgr = context_->get_def_use_mgr(); | |
| auto it = dynamically_uniform_.find(var->result_id()); | |
| if (it != dynamically_uniform_.end()) return it->second; | |
| analysis::DecorationManager* dec_mgr = context_->get_decoration_mgr(); | |
| bool& is_uniform = dynamically_uniform_[var->result_id()]; | |
| is_uniform = false; | |
| dec_mgr->WhileEachDecoration(var->result_id(), | |
| uint32_t(spv::Decoration::Uniform), | |
| [&is_uniform](const Instruction&) { | |
| is_uniform = true; | |
| return false; | |
| }); | |
| if (is_uniform) { | |
| return is_uniform; | |
| } | |
| BasicBlock* parent = context_->get_instr_block(var); | |
| if (!parent) { | |
| return is_uniform = true; | |
| } | |
| if (!post_dom_tree.Dominates(parent->id(), entry->id())) { | |
| return is_uniform = false; | |
| } | |
| if (var->opcode() == spv::Op::OpLoad) { | |
| const uint32_t PtrTypeId = | |
| def_use_mgr->GetDef(var->GetSingleWordInOperand(0))->type_id(); | |
| const Instruction* PtrTypeInst = def_use_mgr->GetDef(PtrTypeId); | |
| auto storage_class = spv::StorageClass( | |
| PtrTypeInst->GetSingleWordInOperand(kTypePointerStorageClassInIdx)); | |
| if (storage_class != spv::StorageClass::Uniform && | |
| storage_class != spv::StorageClass::UniformConstant) { | |
| return is_uniform = false; | |
| } | |
| } else { | |
| if (!context_->IsCombinatorInstruction(var)) { | |
| return is_uniform = false; | |
| } | |
| } | |
| return is_uniform = var->WhileEachInId([entry, &post_dom_tree, | |
| this](const uint32_t* id) { | |
| return IsDynamicallyUniform(context_->get_def_use_mgr()->GetDef(*id), | |
| entry, post_dom_tree); | |
| }); | |
| } | |
| // Returns true if |insn| is not a constant, but is loop invariant and | |
| // dynamically uniform. | |
| bool IsConditionNonConstantLoopInvariant(Instruction* insn) { | |
| assert(insn->IsBranch()); | |
| assert(insn->opcode() != spv::Op::OpBranch); | |
| analysis::DefUseManager* def_use_mgr = context_->get_def_use_mgr(); | |
| Instruction* condition = def_use_mgr->GetDef(insn->GetOperand(0).words[0]); | |
| if (condition->IsConstant()) { | |
| return false; | |
| } | |
| if (loop_->IsInsideLoop(condition)) { | |
| return false; | |
| } | |
| return IsDynamicallyUniform( | |
| condition, function_->entry().get(), | |
| context_->GetPostDominatorAnalysis(function_)->GetDomTree()); | |
| } | |
| }; | |
| } // namespace | |
| Pass::Status LoopUnswitchPass::Process() { | |
| bool modified = false; | |
| Module* module = context()->module(); | |
| // Process each function in the module | |
| for (Function& f : *module) { | |
| modified |= ProcessFunction(&f); | |
| } | |
| return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange; | |
| } | |
| bool LoopUnswitchPass::ProcessFunction(Function* f) { | |
| bool modified = false; | |
| std::unordered_set<Loop*> processed_loop; | |
| LoopDescriptor& loop_descriptor = *context()->GetLoopDescriptor(f); | |
| bool loop_changed = true; | |
| while (loop_changed) { | |
| loop_changed = false; | |
| for (Loop& loop : make_range( | |
| ++TreeDFIterator<Loop>(loop_descriptor.GetPlaceholderRootLoop()), | |
| TreeDFIterator<Loop>())) { | |
| if (processed_loop.count(&loop)) continue; | |
| processed_loop.insert(&loop); | |
| LoopUnswitch unswitcher(context(), f, &loop, &loop_descriptor); | |
| while (unswitcher.CanUnswitchLoop()) { | |
| if (!loop.IsLCSSA()) { | |
| LoopUtils(context(), &loop).MakeLoopClosedSSA(); | |
| } | |
| modified = true; | |
| loop_changed = true; | |
| unswitcher.PerformUnswitch(); | |
| } | |
| if (loop_changed) break; | |
| } | |
| } | |
| return modified; | |
| } | |
| } // namespace opt | |
| } // namespace spvtools |