// 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 #include #include #include #include #include #include #include #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 { static const uint32_t kTypePointerStorageClassInIdx = 0; } // anonymous namespace namespace { // 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() != SpvOpBranch) { 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( new BasicBlock(std::unique_ptr(new Instruction( context_, SpvOpLabel, 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() == SpvOpSwitch && "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 becaues the new switch outside of // the loop cannot select this path either. std::vector 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::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(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(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 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(); SpvOp 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> constant_branch; // Special case for the original loop Instruction* original_loop_constant_value; if (iv_opcode == SpvOpBranchConditional) { 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 if_merging_blocks; std::function 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 == SpvOpBranchConditional) { 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> 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; using BlockMapTy = std::unordered_map; Function* function_; Loop* loop_; LoopDescriptor& loop_desc_; IRContext* context_; BasicBlock* switch_block_; // Map between instructions and if they are dynamically uniform. std::unordered_map dynamically_uniform_; // The loop basic blocks in structured order. std::vector 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 ignore_node; ignore_node = [loop](uint32_t bb_id) { return !loop->IsInsideLoop(bb_id); }; std::vector> 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(), SpvDecorationUniform, [&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() == SpvOpLoad) { const uint32_t PtrTypeId = def_use_mgr->GetDef(var->GetSingleWordInOperand(0))->type_id(); const Instruction* PtrTypeInst = def_use_mgr->GetDef(PtrTypeId); uint32_t storage_class = PtrTypeInst->GetSingleWordInOperand(kTypePointerStorageClassInIdx); if (storage_class != SpvStorageClassUniform && storage_class != SpvStorageClassUniformConstant) { 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() != SpvOpBranch); 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 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_descriptor.GetPlaceholderRootLoop()), TreeDFIterator())) { 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