| 1 | // Copyright (c) 2017 The Khronos Group Inc. |
| 2 | // Copyright (c) 2017 Valve Corporation |
| 3 | // Copyright (c) 2017 LunarG Inc. |
| 4 | // Copyright (c) 2018 Google Inc. |
| 5 | // |
| 6 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 7 | // you may not use this file except in compliance with the License. |
| 8 | // You may obtain a copy of the License at |
| 9 | // |
| 10 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 11 | // |
| 12 | // Unless required by applicable law or agreed to in writing, software |
| 13 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 15 | // See the License for the specific language governing permissions and |
| 16 | // limitations under the License. |
| 17 | |
| 18 | #include "source/opt/dead_branch_elim_pass.h" |
| 19 | |
| 20 | #include <list> |
| 21 | #include <memory> |
| 22 | #include <vector> |
| 23 | |
| 24 | #include "source/cfa.h" |
| 25 | #include "source/opt/ir_context.h" |
| 26 | #include "source/opt/iterator.h" |
| 27 | #include "source/opt/struct_cfg_analysis.h" |
| 28 | #include "source/util/make_unique.h" |
| 29 | |
| 30 | namespace spvtools { |
| 31 | namespace opt { |
| 32 | |
| 33 | namespace { |
| 34 | |
| 35 | const uint32_t kBranchCondTrueLabIdInIdx = 1; |
| 36 | const uint32_t kBranchCondFalseLabIdInIdx = 2; |
| 37 | |
| 38 | } // anonymous namespace |
| 39 | |
| 40 | bool DeadBranchElimPass::GetConstCondition(uint32_t condId, bool* condVal) { |
| 41 | bool condIsConst; |
| 42 | Instruction* cInst = get_def_use_mgr()->GetDef(condId); |
| 43 | switch (cInst->opcode()) { |
| 44 | case SpvOpConstantFalse: { |
| 45 | *condVal = false; |
| 46 | condIsConst = true; |
| 47 | } break; |
| 48 | case SpvOpConstantTrue: { |
| 49 | *condVal = true; |
| 50 | condIsConst = true; |
| 51 | } break; |
| 52 | case SpvOpLogicalNot: { |
| 53 | bool negVal; |
| 54 | condIsConst = |
| 55 | GetConstCondition(cInst->GetSingleWordInOperand(0), &negVal); |
| 56 | if (condIsConst) *condVal = !negVal; |
| 57 | } break; |
| 58 | default: { condIsConst = false; } break; |
| 59 | } |
| 60 | return condIsConst; |
| 61 | } |
| 62 | |
| 63 | bool DeadBranchElimPass::GetConstInteger(uint32_t selId, uint32_t* selVal) { |
| 64 | Instruction* sInst = get_def_use_mgr()->GetDef(selId); |
| 65 | uint32_t typeId = sInst->type_id(); |
| 66 | Instruction* typeInst = get_def_use_mgr()->GetDef(typeId); |
| 67 | if (!typeInst || (typeInst->opcode() != SpvOpTypeInt)) return false; |
| 68 | // TODO(greg-lunarg): Support non-32 bit ints |
| 69 | if (typeInst->GetSingleWordInOperand(0) != 32) return false; |
| 70 | if (sInst->opcode() == SpvOpConstant) { |
| 71 | *selVal = sInst->GetSingleWordInOperand(0); |
| 72 | return true; |
| 73 | } else if (sInst->opcode() == SpvOpConstantNull) { |
| 74 | *selVal = 0; |
| 75 | return true; |
| 76 | } |
| 77 | return false; |
| 78 | } |
| 79 | |
| 80 | void DeadBranchElimPass::AddBranch(uint32_t labelId, BasicBlock* bp) { |
| 81 | assert(get_def_use_mgr()->GetDef(labelId) != nullptr); |
| 82 | std::unique_ptr<Instruction> newBranch( |
| 83 | new Instruction(context(), SpvOpBranch, 0, 0, |
| 84 | {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {labelId}}})); |
| 85 | context()->AnalyzeDefUse(&*newBranch); |
| 86 | context()->set_instr_block(&*newBranch, bp); |
| 87 | bp->AddInstruction(std::move(newBranch)); |
| 88 | } |
| 89 | |
| 90 | BasicBlock* DeadBranchElimPass::GetParentBlock(uint32_t id) { |
| 91 | return context()->get_instr_block(get_def_use_mgr()->GetDef(id)); |
| 92 | } |
| 93 | |
| 94 | bool DeadBranchElimPass::MarkLiveBlocks( |
| 95 | Function* func, std::unordered_set<BasicBlock*>* live_blocks) { |
| 96 | std::vector<std::pair<BasicBlock*, uint32_t>> conditions_to_simplify; |
| 97 | std::unordered_set<BasicBlock*> blocks_with_backedge; |
| 98 | std::vector<BasicBlock*> stack; |
| 99 | stack.push_back(&*func->begin()); |
| 100 | bool modified = false; |
| 101 | while (!stack.empty()) { |
| 102 | BasicBlock* block = stack.back(); |
| 103 | stack.pop_back(); |
| 104 | |
| 105 | // Live blocks doubles as visited set. |
| 106 | if (!live_blocks->insert(block).second) continue; |
| 107 | |
| 108 | uint32_t cont_id = block->ContinueBlockIdIfAny(); |
| 109 | if (cont_id != 0) { |
| 110 | AddBlocksWithBackEdge(cont_id, block->id(), block->MergeBlockIdIfAny(), |
| 111 | &blocks_with_backedge); |
| 112 | } |
| 113 | |
| 114 | Instruction* terminator = block->terminator(); |
| 115 | uint32_t live_lab_id = 0; |
| 116 | // Check if the terminator has a single valid successor. |
| 117 | if (terminator->opcode() == SpvOpBranchConditional) { |
| 118 | bool condVal; |
| 119 | if (GetConstCondition(terminator->GetSingleWordInOperand(0u), &condVal)) { |
| 120 | live_lab_id = terminator->GetSingleWordInOperand( |
| 121 | condVal ? kBranchCondTrueLabIdInIdx : kBranchCondFalseLabIdInIdx); |
| 122 | } |
| 123 | } else if (terminator->opcode() == SpvOpSwitch) { |
| 124 | uint32_t sel_val; |
| 125 | if (GetConstInteger(terminator->GetSingleWordInOperand(0u), &sel_val)) { |
| 126 | // Search switch operands for selector value, set live_lab_id to |
| 127 | // corresponding label, use default if not found. |
| 128 | uint32_t icnt = 0; |
| 129 | uint32_t case_val; |
| 130 | terminator->WhileEachInOperand( |
| 131 | [&icnt, &case_val, &sel_val, &live_lab_id](const uint32_t* idp) { |
| 132 | if (icnt == 1) { |
| 133 | // Start with default label. |
| 134 | live_lab_id = *idp; |
| 135 | } else if (icnt > 1) { |
| 136 | if (icnt % 2 == 0) { |
| 137 | case_val = *idp; |
| 138 | } else { |
| 139 | if (case_val == sel_val) { |
| 140 | live_lab_id = *idp; |
| 141 | return false; |
| 142 | } |
| 143 | } |
| 144 | } |
| 145 | ++icnt; |
| 146 | return true; |
| 147 | }); |
| 148 | } |
| 149 | } |
| 150 | |
| 151 | // Don't simplify back edges unless it becomes a branch to the header. Every |
| 152 | // loop must have exactly one back edge to the loop header, so we cannot |
| 153 | // remove it. |
| 154 | bool simplify = false; |
| 155 | if (live_lab_id != 0) { |
| 156 | if (!blocks_with_backedge.count(block)) { |
| 157 | // This is not a back edge. |
| 158 | simplify = true; |
| 159 | } else { |
| 160 | const auto& struct_cfg_analysis = context()->GetStructuredCFGAnalysis(); |
| 161 | uint32_t = struct_cfg_analysis->ContainingLoop(block->id()); |
| 162 | if (live_lab_id == header_id) { |
| 163 | // The new branch will be a branch to the header. |
| 164 | simplify = true; |
| 165 | } |
| 166 | } |
| 167 | } |
| 168 | |
| 169 | if (simplify) { |
| 170 | conditions_to_simplify.push_back({block, live_lab_id}); |
| 171 | stack.push_back(GetParentBlock(live_lab_id)); |
| 172 | } else { |
| 173 | // All successors are live. |
| 174 | const auto* const_block = block; |
| 175 | const_block->ForEachSuccessorLabel([&stack, this](const uint32_t label) { |
| 176 | stack.push_back(GetParentBlock(label)); |
| 177 | }); |
| 178 | } |
| 179 | } |
| 180 | |
| 181 | // Traverse |conditions_to_simplify| in reverse order. This is done so that |
| 182 | // we simplify nested constructs before simplifying the constructs that |
| 183 | // contain them. |
| 184 | for (auto b = conditions_to_simplify.rbegin(); |
| 185 | b != conditions_to_simplify.rend(); ++b) { |
| 186 | modified |= SimplifyBranch(b->first, b->second); |
| 187 | } |
| 188 | |
| 189 | return modified; |
| 190 | } |
| 191 | |
| 192 | bool DeadBranchElimPass::SimplifyBranch(BasicBlock* block, |
| 193 | uint32_t live_lab_id) { |
| 194 | Instruction* merge_inst = block->GetMergeInst(); |
| 195 | Instruction* terminator = block->terminator(); |
| 196 | if (merge_inst && merge_inst->opcode() == SpvOpSelectionMerge) { |
| 197 | if (merge_inst->NextNode()->opcode() == SpvOpSwitch && |
| 198 | SwitchHasNestedBreak(block->id())) { |
| 199 | if (terminator->NumInOperands() == 2) { |
| 200 | // We cannot remove the branch, and it already has a single case, so no |
| 201 | // work to do. |
| 202 | return false; |
| 203 | } |
| 204 | // We have to keep the switch because it has a nest break, so we |
| 205 | // remove all cases except for the live one. |
| 206 | Instruction::OperandList new_operands; |
| 207 | new_operands.push_back(terminator->GetInOperand(0)); |
| 208 | new_operands.push_back({SPV_OPERAND_TYPE_ID, {live_lab_id}}); |
| 209 | terminator->SetInOperands(move(new_operands)); |
| 210 | context()->UpdateDefUse(terminator); |
| 211 | } else { |
| 212 | // Check if the merge instruction is still needed because of a |
| 213 | // non-nested break from the construct. Move the merge instruction if |
| 214 | // it is still needed. |
| 215 | StructuredCFGAnalysis* cfg_analysis = |
| 216 | context()->GetStructuredCFGAnalysis(); |
| 217 | Instruction* first_break = FindFirstExitFromSelectionMerge( |
| 218 | live_lab_id, merge_inst->GetSingleWordInOperand(0), |
| 219 | cfg_analysis->LoopMergeBlock(live_lab_id), |
| 220 | cfg_analysis->LoopContinueBlock(live_lab_id), |
| 221 | cfg_analysis->SwitchMergeBlock(live_lab_id)); |
| 222 | |
| 223 | AddBranch(live_lab_id, block); |
| 224 | context()->KillInst(terminator); |
| 225 | if (first_break == nullptr) { |
| 226 | context()->KillInst(merge_inst); |
| 227 | } else { |
| 228 | merge_inst->RemoveFromList(); |
| 229 | first_break->InsertBefore(std::unique_ptr<Instruction>(merge_inst)); |
| 230 | context()->set_instr_block(merge_inst, |
| 231 | context()->get_instr_block(first_break)); |
| 232 | } |
| 233 | } |
| 234 | } else { |
| 235 | AddBranch(live_lab_id, block); |
| 236 | context()->KillInst(terminator); |
| 237 | } |
| 238 | return true; |
| 239 | } |
| 240 | |
| 241 | void DeadBranchElimPass::MarkUnreachableStructuredTargets( |
| 242 | const std::unordered_set<BasicBlock*>& live_blocks, |
| 243 | std::unordered_set<BasicBlock*>* unreachable_merges, |
| 244 | std::unordered_map<BasicBlock*, BasicBlock*>* unreachable_continues) { |
| 245 | for (auto block : live_blocks) { |
| 246 | if (auto merge_id = block->MergeBlockIdIfAny()) { |
| 247 | BasicBlock* merge_block = GetParentBlock(merge_id); |
| 248 | if (!live_blocks.count(merge_block)) { |
| 249 | unreachable_merges->insert(merge_block); |
| 250 | } |
| 251 | if (auto cont_id = block->ContinueBlockIdIfAny()) { |
| 252 | BasicBlock* cont_block = GetParentBlock(cont_id); |
| 253 | if (!live_blocks.count(cont_block)) { |
| 254 | (*unreachable_continues)[cont_block] = block; |
| 255 | } |
| 256 | } |
| 257 | } |
| 258 | } |
| 259 | } |
| 260 | |
| 261 | bool DeadBranchElimPass::FixPhiNodesInLiveBlocks( |
| 262 | Function* func, const std::unordered_set<BasicBlock*>& live_blocks, |
| 263 | const std::unordered_map<BasicBlock*, BasicBlock*>& unreachable_continues) { |
| 264 | bool modified = false; |
| 265 | for (auto& block : *func) { |
| 266 | if (live_blocks.count(&block)) { |
| 267 | for (auto iter = block.begin(); iter != block.end();) { |
| 268 | if (iter->opcode() != SpvOpPhi) { |
| 269 | break; |
| 270 | } |
| 271 | |
| 272 | bool changed = false; |
| 273 | bool backedge_added = false; |
| 274 | Instruction* inst = &*iter; |
| 275 | std::vector<Operand> operands; |
| 276 | // Build a complete set of operands (not just input operands). Start |
| 277 | // with type and result id operands. |
| 278 | operands.push_back(inst->GetOperand(0u)); |
| 279 | operands.push_back(inst->GetOperand(1u)); |
| 280 | // Iterate through the incoming labels and determine which to keep |
| 281 | // and/or modify. If there in an unreachable continue block, there will |
| 282 | // be an edge from that block to the header. We need to keep it to |
| 283 | // maintain the structured control flow. If the header has more that 2 |
| 284 | // incoming edges, then the OpPhi must have an entry for that edge. |
| 285 | // However, if there is only one other incoming edge, the OpPhi can be |
| 286 | // eliminated. |
| 287 | for (uint32_t i = 1; i < inst->NumInOperands(); i += 2) { |
| 288 | BasicBlock* inc = GetParentBlock(inst->GetSingleWordInOperand(i)); |
| 289 | auto cont_iter = unreachable_continues.find(inc); |
| 290 | if (cont_iter != unreachable_continues.end() && |
| 291 | cont_iter->second == &block && inst->NumInOperands() > 4) { |
| 292 | if (get_def_use_mgr() |
| 293 | ->GetDef(inst->GetSingleWordInOperand(i - 1)) |
| 294 | ->opcode() == SpvOpUndef) { |
| 295 | // Already undef incoming value, no change necessary. |
| 296 | operands.push_back(inst->GetInOperand(i - 1)); |
| 297 | operands.push_back(inst->GetInOperand(i)); |
| 298 | backedge_added = true; |
| 299 | } else { |
| 300 | // Replace incoming value with undef if this phi exists in the |
| 301 | // loop header. Otherwise, this edge is not live since the |
| 302 | // unreachable continue block will be replaced with an |
| 303 | // unconditional branch to the header only. |
| 304 | operands.emplace_back( |
| 305 | SPV_OPERAND_TYPE_ID, |
| 306 | std::initializer_list<uint32_t>{Type2Undef(inst->type_id())}); |
| 307 | operands.push_back(inst->GetInOperand(i)); |
| 308 | changed = true; |
| 309 | backedge_added = true; |
| 310 | } |
| 311 | } else if (live_blocks.count(inc) && inc->IsSuccessor(&block)) { |
| 312 | // Keep live incoming edge. |
| 313 | operands.push_back(inst->GetInOperand(i - 1)); |
| 314 | operands.push_back(inst->GetInOperand(i)); |
| 315 | } else { |
| 316 | // Remove incoming edge. |
| 317 | changed = true; |
| 318 | } |
| 319 | } |
| 320 | |
| 321 | if (changed) { |
| 322 | modified = true; |
| 323 | uint32_t continue_id = block.ContinueBlockIdIfAny(); |
| 324 | if (!backedge_added && continue_id != 0 && |
| 325 | unreachable_continues.count(GetParentBlock(continue_id)) && |
| 326 | operands.size() > 4) { |
| 327 | // Changed the backedge to branch from the continue block instead |
| 328 | // of a successor of the continue block. Add an entry to the phi to |
| 329 | // provide an undef for the continue block. Since the successor of |
| 330 | // the continue must also be unreachable (dominated by the continue |
| 331 | // block), any entry for the original backedge has been removed |
| 332 | // from the phi operands. |
| 333 | operands.emplace_back( |
| 334 | SPV_OPERAND_TYPE_ID, |
| 335 | std::initializer_list<uint32_t>{Type2Undef(inst->type_id())}); |
| 336 | operands.emplace_back(SPV_OPERAND_TYPE_ID, |
| 337 | std::initializer_list<uint32_t>{continue_id}); |
| 338 | } |
| 339 | |
| 340 | // Either replace the phi with a single value or rebuild the phi out |
| 341 | // of |operands|. |
| 342 | // |
| 343 | // We always have type and result id operands. So this phi has a |
| 344 | // single source if there are two more operands beyond those. |
| 345 | if (operands.size() == 4) { |
| 346 | // First input data operands is at index 2. |
| 347 | uint32_t replId = operands[2u].words[0]; |
| 348 | context()->ReplaceAllUsesWith(inst->result_id(), replId); |
| 349 | iter = context()->KillInst(&*inst); |
| 350 | } else { |
| 351 | // We've rewritten the operands, so first instruct the def/use |
| 352 | // manager to forget uses in the phi before we replace them. After |
| 353 | // replacing operands update the def/use manager by re-analyzing |
| 354 | // the used ids in this phi. |
| 355 | get_def_use_mgr()->EraseUseRecordsOfOperandIds(inst); |
| 356 | inst->ReplaceOperands(operands); |
| 357 | get_def_use_mgr()->AnalyzeInstUse(inst); |
| 358 | ++iter; |
| 359 | } |
| 360 | } else { |
| 361 | ++iter; |
| 362 | } |
| 363 | } |
| 364 | } |
| 365 | } |
| 366 | |
| 367 | return modified; |
| 368 | } |
| 369 | |
| 370 | bool DeadBranchElimPass::EraseDeadBlocks( |
| 371 | Function* func, const std::unordered_set<BasicBlock*>& live_blocks, |
| 372 | const std::unordered_set<BasicBlock*>& unreachable_merges, |
| 373 | const std::unordered_map<BasicBlock*, BasicBlock*>& unreachable_continues) { |
| 374 | bool modified = false; |
| 375 | for (auto ebi = func->begin(); ebi != func->end();) { |
| 376 | if (unreachable_continues.count(&*ebi)) { |
| 377 | uint32_t cont_id = unreachable_continues.find(&*ebi)->second->id(); |
| 378 | if (ebi->begin() != ebi->tail() || |
| 379 | ebi->terminator()->opcode() != SpvOpBranch || |
| 380 | ebi->terminator()->GetSingleWordInOperand(0u) != cont_id) { |
| 381 | // Make unreachable, but leave the label. |
| 382 | KillAllInsts(&*ebi, false); |
| 383 | // Add unconditional branch to header. |
| 384 | assert(unreachable_continues.count(&*ebi)); |
| 385 | ebi->AddInstruction(MakeUnique<Instruction>( |
| 386 | context(), SpvOpBranch, 0, 0, |
| 387 | std::initializer_list<Operand>{{SPV_OPERAND_TYPE_ID, {cont_id}}})); |
| 388 | get_def_use_mgr()->AnalyzeInstUse(&*ebi->tail()); |
| 389 | context()->set_instr_block(&*ebi->tail(), &*ebi); |
| 390 | modified = true; |
| 391 | } |
| 392 | ++ebi; |
| 393 | } else if (unreachable_merges.count(&*ebi)) { |
| 394 | if (ebi->begin() != ebi->tail() || |
| 395 | ebi->terminator()->opcode() != SpvOpUnreachable) { |
| 396 | // Make unreachable, but leave the label. |
| 397 | KillAllInsts(&*ebi, false); |
| 398 | // Add unreachable terminator. |
| 399 | ebi->AddInstruction( |
| 400 | MakeUnique<Instruction>(context(), SpvOpUnreachable, 0, 0, |
| 401 | std::initializer_list<Operand>{})); |
| 402 | context()->AnalyzeUses(ebi->terminator()); |
| 403 | context()->set_instr_block(ebi->terminator(), &*ebi); |
| 404 | modified = true; |
| 405 | } |
| 406 | ++ebi; |
| 407 | } else if (!live_blocks.count(&*ebi)) { |
| 408 | // Kill this block. |
| 409 | KillAllInsts(&*ebi); |
| 410 | ebi = ebi.Erase(); |
| 411 | modified = true; |
| 412 | } else { |
| 413 | ++ebi; |
| 414 | } |
| 415 | } |
| 416 | |
| 417 | return modified; |
| 418 | } |
| 419 | |
| 420 | bool DeadBranchElimPass::EliminateDeadBranches(Function* func) { |
| 421 | bool modified = false; |
| 422 | std::unordered_set<BasicBlock*> live_blocks; |
| 423 | modified |= MarkLiveBlocks(func, &live_blocks); |
| 424 | |
| 425 | std::unordered_set<BasicBlock*> unreachable_merges; |
| 426 | std::unordered_map<BasicBlock*, BasicBlock*> unreachable_continues; |
| 427 | MarkUnreachableStructuredTargets(live_blocks, &unreachable_merges, |
| 428 | &unreachable_continues); |
| 429 | modified |= FixPhiNodesInLiveBlocks(func, live_blocks, unreachable_continues); |
| 430 | modified |= EraseDeadBlocks(func, live_blocks, unreachable_merges, |
| 431 | unreachable_continues); |
| 432 | |
| 433 | return modified; |
| 434 | } |
| 435 | |
| 436 | void DeadBranchElimPass::FixBlockOrder() { |
| 437 | context()->BuildInvalidAnalyses(IRContext::kAnalysisCFG | |
| 438 | IRContext::kAnalysisDominatorAnalysis); |
| 439 | // Reorders blocks according to DFS of dominator tree. |
| 440 | ProcessFunction reorder_dominators = [this](Function* function) { |
| 441 | DominatorAnalysis* dominators = context()->GetDominatorAnalysis(function); |
| 442 | std::vector<BasicBlock*> blocks; |
| 443 | for (auto iter = dominators->GetDomTree().begin(); |
| 444 | iter != dominators->GetDomTree().end(); ++iter) { |
| 445 | if (iter->id() != 0) { |
| 446 | blocks.push_back(iter->bb_); |
| 447 | } |
| 448 | } |
| 449 | for (uint32_t i = 1; i < blocks.size(); ++i) { |
| 450 | function->MoveBasicBlockToAfter(blocks[i]->id(), blocks[i - 1]); |
| 451 | } |
| 452 | return true; |
| 453 | }; |
| 454 | |
| 455 | // Reorders blocks according to structured order. |
| 456 | ProcessFunction reorder_structured = [this](Function* function) { |
| 457 | std::list<BasicBlock*> order; |
| 458 | context()->cfg()->ComputeStructuredOrder(function, &*function->begin(), |
| 459 | &order); |
| 460 | std::vector<BasicBlock*> blocks; |
| 461 | for (auto block : order) { |
| 462 | blocks.push_back(block); |
| 463 | } |
| 464 | for (uint32_t i = 1; i < blocks.size(); ++i) { |
| 465 | function->MoveBasicBlockToAfter(blocks[i]->id(), blocks[i - 1]); |
| 466 | } |
| 467 | return true; |
| 468 | }; |
| 469 | |
| 470 | // Structured order is more intuitive so use it where possible. |
| 471 | if (context()->get_feature_mgr()->HasCapability(SpvCapabilityShader)) { |
| 472 | context()->ProcessReachableCallTree(reorder_structured); |
| 473 | } else { |
| 474 | context()->ProcessReachableCallTree(reorder_dominators); |
| 475 | } |
| 476 | } |
| 477 | |
| 478 | Pass::Status DeadBranchElimPass::Process() { |
| 479 | // Do not process if module contains OpGroupDecorate. Additional |
| 480 | // support required in KillNamesAndDecorates(). |
| 481 | // TODO(greg-lunarg): Add support for OpGroupDecorate |
| 482 | for (auto& ai : get_module()->annotations()) |
| 483 | if (ai.opcode() == SpvOpGroupDecorate) return Status::SuccessWithoutChange; |
| 484 | // Process all entry point functions |
| 485 | ProcessFunction pfn = [this](Function* fp) { |
| 486 | return EliminateDeadBranches(fp); |
| 487 | }; |
| 488 | bool modified = context()->ProcessReachableCallTree(pfn); |
| 489 | if (modified) FixBlockOrder(); |
| 490 | return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange; |
| 491 | } |
| 492 | |
| 493 | Instruction* DeadBranchElimPass::FindFirstExitFromSelectionMerge( |
| 494 | uint32_t start_block_id, uint32_t merge_block_id, uint32_t loop_merge_id, |
| 495 | uint32_t loop_continue_id, uint32_t switch_merge_id) { |
| 496 | // To find the "first" exit, we follow branches looking for a conditional |
| 497 | // branch that is not in a nested construct and is not the header of a new |
| 498 | // construct. We follow the control flow from |start_block_id| to find the |
| 499 | // first one. |
| 500 | |
| 501 | while (start_block_id != merge_block_id && start_block_id != loop_merge_id && |
| 502 | start_block_id != loop_continue_id) { |
| 503 | BasicBlock* start_block = context()->get_instr_block(start_block_id); |
| 504 | Instruction* branch = start_block->terminator(); |
| 505 | uint32_t next_block_id = 0; |
| 506 | switch (branch->opcode()) { |
| 507 | case SpvOpBranchConditional: |
| 508 | next_block_id = start_block->MergeBlockIdIfAny(); |
| 509 | if (next_block_id == 0) { |
| 510 | // If a possible target is the |loop_merge_id| or |loop_continue_id|, |
| 511 | // which are not the current merge node, then we continue the search |
| 512 | // with the other target. |
| 513 | for (uint32_t i = 1; i < 3; i++) { |
| 514 | if (branch->GetSingleWordInOperand(i) == loop_merge_id && |
| 515 | loop_merge_id != merge_block_id) { |
| 516 | next_block_id = branch->GetSingleWordInOperand(3 - i); |
| 517 | break; |
| 518 | } |
| 519 | if (branch->GetSingleWordInOperand(i) == loop_continue_id && |
| 520 | loop_continue_id != merge_block_id) { |
| 521 | next_block_id = branch->GetSingleWordInOperand(3 - i); |
| 522 | break; |
| 523 | } |
| 524 | if (branch->GetSingleWordInOperand(i) == switch_merge_id && |
| 525 | switch_merge_id != merge_block_id) { |
| 526 | next_block_id = branch->GetSingleWordInOperand(3 - i); |
| 527 | break; |
| 528 | } |
| 529 | } |
| 530 | |
| 531 | if (next_block_id == 0) { |
| 532 | return branch; |
| 533 | } |
| 534 | } |
| 535 | break; |
| 536 | case SpvOpSwitch: |
| 537 | next_block_id = start_block->MergeBlockIdIfAny(); |
| 538 | if (next_block_id == 0) { |
| 539 | // A switch with no merge instructions can have at most 5 targets: |
| 540 | // a. |merge_block_id| |
| 541 | // b. |loop_merge_id| |
| 542 | // c. |loop_continue_id| |
| 543 | // d. |switch_merge_id| |
| 544 | // e. 1 block inside the current region. |
| 545 | // |
| 546 | // Note that because this is a switch, |merge_block_id| must equal |
| 547 | // |switch_merge_id|. |
| 548 | // |
| 549 | // This leads to a number of cases of what to do. |
| 550 | // |
| 551 | // 1. Does not jump to a block inside of the current construct. In |
| 552 | // this case, there is not conditional break, so we should return |
| 553 | // |nullptr|. |
| 554 | // |
| 555 | // 2. Jumps to |merge_block_id| and a block inside the current |
| 556 | // construct. In this case, this branch conditionally break to the |
| 557 | // end of the current construct, so return the current branch. |
| 558 | // |
| 559 | // 3. Otherwise, this branch may break, but not to the current merge |
| 560 | // block. So we continue with the block that is inside the loop. |
| 561 | bool found_break = false; |
| 562 | for (uint32_t i = 1; i < branch->NumInOperands(); i += 2) { |
| 563 | uint32_t target = branch->GetSingleWordInOperand(i); |
| 564 | if (target == merge_block_id) { |
| 565 | found_break = true; |
| 566 | } else if (target != loop_merge_id && target != loop_continue_id) { |
| 567 | next_block_id = branch->GetSingleWordInOperand(i); |
| 568 | } |
| 569 | } |
| 570 | |
| 571 | if (next_block_id == 0) { |
| 572 | // Case 1. |
| 573 | return nullptr; |
| 574 | } |
| 575 | |
| 576 | if (found_break) { |
| 577 | // Case 2. |
| 578 | return branch; |
| 579 | } |
| 580 | |
| 581 | // The fall through is case 3. |
| 582 | } |
| 583 | break; |
| 584 | case SpvOpBranch: |
| 585 | // Need to check if this is the header of a loop nested in the |
| 586 | // selection construct. |
| 587 | next_block_id = start_block->MergeBlockIdIfAny(); |
| 588 | if (next_block_id == 0) { |
| 589 | next_block_id = branch->GetSingleWordInOperand(0); |
| 590 | } |
| 591 | break; |
| 592 | default: |
| 593 | return nullptr; |
| 594 | } |
| 595 | start_block_id = next_block_id; |
| 596 | } |
| 597 | return nullptr; |
| 598 | } |
| 599 | |
| 600 | void DeadBranchElimPass::AddBlocksWithBackEdge( |
| 601 | uint32_t cont_id, uint32_t , uint32_t merge_id, |
| 602 | std::unordered_set<BasicBlock*>* blocks_with_back_edges) { |
| 603 | std::unordered_set<uint32_t> visited; |
| 604 | visited.insert(cont_id); |
| 605 | visited.insert(header_id); |
| 606 | visited.insert(merge_id); |
| 607 | |
| 608 | std::vector<uint32_t> work_list; |
| 609 | work_list.push_back(cont_id); |
| 610 | |
| 611 | while (!work_list.empty()) { |
| 612 | uint32_t bb_id = work_list.back(); |
| 613 | work_list.pop_back(); |
| 614 | |
| 615 | BasicBlock* bb = context()->get_instr_block(bb_id); |
| 616 | |
| 617 | bool has_back_edge = false; |
| 618 | bb->ForEachSuccessorLabel([header_id, &visited, &work_list, |
| 619 | &has_back_edge](uint32_t* succ_label_id) { |
| 620 | if (visited.insert(*succ_label_id).second) { |
| 621 | work_list.push_back(*succ_label_id); |
| 622 | } |
| 623 | if (*succ_label_id == header_id) { |
| 624 | has_back_edge = true; |
| 625 | } |
| 626 | }); |
| 627 | |
| 628 | if (has_back_edge) { |
| 629 | blocks_with_back_edges->insert(bb); |
| 630 | } |
| 631 | } |
| 632 | } |
| 633 | |
| 634 | bool DeadBranchElimPass::SwitchHasNestedBreak(uint32_t ) { |
| 635 | std::vector<BasicBlock*> block_in_construct; |
| 636 | BasicBlock* start_block = context()->get_instr_block(switch_header_id); |
| 637 | uint32_t merge_block_id = start_block->MergeBlockIdIfAny(); |
| 638 | |
| 639 | StructuredCFGAnalysis* cfg_analysis = context()->GetStructuredCFGAnalysis(); |
| 640 | return !get_def_use_mgr()->WhileEachUser( |
| 641 | merge_block_id, |
| 642 | [this, cfg_analysis, switch_header_id](Instruction* inst) { |
| 643 | if (!inst->IsBranch()) { |
| 644 | return true; |
| 645 | } |
| 646 | |
| 647 | BasicBlock* bb = context()->get_instr_block(inst); |
| 648 | if (bb->id() == switch_header_id) { |
| 649 | return true; |
| 650 | } |
| 651 | return (cfg_analysis->ContainingConstruct(inst) == switch_header_id && |
| 652 | bb->GetMergeInst() == nullptr); |
| 653 | }); |
| 654 | } |
| 655 | |
| 656 | } // namespace opt |
| 657 | } // namespace spvtools |
| 658 | |