|  | // 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/if_conversion.h" | 
|  |  | 
|  | #include <memory> | 
|  | #include <vector> | 
|  |  | 
|  | #include "source/opt/value_number_table.h" | 
|  |  | 
|  | namespace spvtools { | 
|  | namespace opt { | 
|  |  | 
|  | Pass::Status IfConversion::Process() { | 
|  | if (!context()->get_feature_mgr()->HasCapability(SpvCapabilityShader)) { | 
|  | return Status::SuccessWithoutChange; | 
|  | } | 
|  |  | 
|  | const ValueNumberTable& vn_table = *context()->GetValueNumberTable(); | 
|  | bool modified = false; | 
|  | std::vector<Instruction*> to_kill; | 
|  | for (auto& func : *get_module()) { | 
|  | DominatorAnalysis* dominators = context()->GetDominatorAnalysis(&func); | 
|  | for (auto& block : func) { | 
|  | // Check if it is possible for |block| to have phis that can be | 
|  | // transformed. | 
|  | BasicBlock* common = nullptr; | 
|  | if (!CheckBlock(&block, dominators, &common)) continue; | 
|  |  | 
|  | // Get an insertion point. | 
|  | auto iter = block.begin(); | 
|  | while (iter != block.end() && iter->opcode() == SpvOpPhi) { | 
|  | ++iter; | 
|  | } | 
|  |  | 
|  | InstructionBuilder builder( | 
|  | context(), &*iter, | 
|  | IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping); | 
|  | block.ForEachPhiInst([this, &builder, &modified, &common, &to_kill, | 
|  | dominators, &block, &vn_table](Instruction* phi) { | 
|  | // This phi is not compatible, but subsequent phis might be. | 
|  | if (!CheckType(phi->type_id())) return; | 
|  |  | 
|  | // We cannot transform cases where the phi is used by another phi in the | 
|  | // same block due to instruction ordering restrictions. | 
|  | // TODO(alan-baker): If all inappropriate uses could also be | 
|  | // transformed, we could still remove this phi. | 
|  | if (!CheckPhiUsers(phi, &block)) return; | 
|  |  | 
|  | // Identify the incoming values associated with the true and false | 
|  | // branches. If |then_block| dominates |inc0| or if the true edge | 
|  | // branches straight to this block and |common| is |inc0|, then |inc0| | 
|  | // is on the true branch. Otherwise the |inc1| is on the true branch. | 
|  | BasicBlock* inc0 = GetIncomingBlock(phi, 0u); | 
|  | Instruction* branch = common->terminator(); | 
|  | uint32_t condition = branch->GetSingleWordInOperand(0u); | 
|  | BasicBlock* then_block = GetBlock(branch->GetSingleWordInOperand(1u)); | 
|  | Instruction* true_value = nullptr; | 
|  | Instruction* false_value = nullptr; | 
|  | if ((then_block == &block && inc0 == common) || | 
|  | dominators->Dominates(then_block, inc0)) { | 
|  | true_value = GetIncomingValue(phi, 0u); | 
|  | false_value = GetIncomingValue(phi, 1u); | 
|  | } else { | 
|  | true_value = GetIncomingValue(phi, 1u); | 
|  | false_value = GetIncomingValue(phi, 0u); | 
|  | } | 
|  |  | 
|  | BasicBlock* true_def_block = context()->get_instr_block(true_value); | 
|  | BasicBlock* false_def_block = context()->get_instr_block(false_value); | 
|  |  | 
|  | uint32_t true_vn = vn_table.GetValueNumber(true_value); | 
|  | uint32_t false_vn = vn_table.GetValueNumber(false_value); | 
|  | if (true_vn != 0 && true_vn == false_vn) { | 
|  | Instruction* inst_to_use = nullptr; | 
|  |  | 
|  | // Try to pick an instruction that is not in a side node.  If we can't | 
|  | // pick either the true for false branch as long as they can be | 
|  | // legally moved. | 
|  | if (!true_def_block || | 
|  | dominators->Dominates(true_def_block, &block)) { | 
|  | inst_to_use = true_value; | 
|  | } else if (!false_def_block || | 
|  | dominators->Dominates(false_def_block, &block)) { | 
|  | inst_to_use = false_value; | 
|  | } else if (CanHoistInstruction(true_value, common, dominators)) { | 
|  | inst_to_use = true_value; | 
|  | } else if (CanHoistInstruction(false_value, common, dominators)) { | 
|  | inst_to_use = false_value; | 
|  | } | 
|  |  | 
|  | if (inst_to_use != nullptr) { | 
|  | modified = true; | 
|  | HoistInstruction(inst_to_use, common, dominators); | 
|  | context()->KillNamesAndDecorates(phi); | 
|  | context()->ReplaceAllUsesWith(phi->result_id(), | 
|  | inst_to_use->result_id()); | 
|  | } | 
|  | return; | 
|  | } | 
|  |  | 
|  | // If either incoming value is defined in a block that does not dominate | 
|  | // this phi, then we cannot eliminate the phi with a select. | 
|  | // TODO(alan-baker): Perform code motion where it makes sense to enable | 
|  | // the transform in this case. | 
|  | if (true_def_block && !dominators->Dominates(true_def_block, &block)) | 
|  | return; | 
|  |  | 
|  | if (false_def_block && !dominators->Dominates(false_def_block, &block)) | 
|  | return; | 
|  |  | 
|  | analysis::Type* data_ty = | 
|  | context()->get_type_mgr()->GetType(true_value->type_id()); | 
|  | if (analysis::Vector* vec_data_ty = data_ty->AsVector()) { | 
|  | condition = SplatCondition(vec_data_ty, condition, &builder); | 
|  | } | 
|  |  | 
|  | Instruction* select = builder.AddSelect(phi->type_id(), condition, | 
|  | true_value->result_id(), | 
|  | false_value->result_id()); | 
|  | context()->ReplaceAllUsesWith(phi->result_id(), select->result_id()); | 
|  | to_kill.push_back(phi); | 
|  | modified = true; | 
|  |  | 
|  | return; | 
|  | }); | 
|  | } | 
|  | } | 
|  |  | 
|  | for (auto inst : to_kill) { | 
|  | context()->KillInst(inst); | 
|  | } | 
|  |  | 
|  | return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange; | 
|  | } | 
|  |  | 
|  | bool IfConversion::CheckBlock(BasicBlock* block, DominatorAnalysis* dominators, | 
|  | BasicBlock** common) { | 
|  | const std::vector<uint32_t>& preds = cfg()->preds(block->id()); | 
|  |  | 
|  | // TODO(alan-baker): Extend to more than two predecessors | 
|  | if (preds.size() != 2) return false; | 
|  |  | 
|  | BasicBlock* inc0 = context()->get_instr_block(preds[0]); | 
|  | if (dominators->Dominates(block, inc0)) return false; | 
|  |  | 
|  | BasicBlock* inc1 = context()->get_instr_block(preds[1]); | 
|  | if (dominators->Dominates(block, inc1)) return false; | 
|  |  | 
|  | // All phis will have the same common dominator, so cache the result | 
|  | // for this block. If there is no common dominator, then we cannot transform | 
|  | // any phi in this basic block. | 
|  | *common = dominators->CommonDominator(inc0, inc1); | 
|  | if (!*common || cfg()->IsPseudoEntryBlock(*common)) return false; | 
|  | Instruction* branch = (*common)->terminator(); | 
|  | if (branch->opcode() != SpvOpBranchConditional) return false; | 
|  | auto merge = (*common)->GetMergeInst(); | 
|  | if (!merge || merge->opcode() != SpvOpSelectionMerge) return false; | 
|  | if ((*common)->MergeBlockIdIfAny() != block->id()) return false; | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool IfConversion::CheckPhiUsers(Instruction* phi, BasicBlock* block) { | 
|  | return get_def_use_mgr()->WhileEachUser(phi, [block, | 
|  | this](Instruction* user) { | 
|  | if (user->opcode() == SpvOpPhi && context()->get_instr_block(user) == block) | 
|  | return false; | 
|  | return true; | 
|  | }); | 
|  | } | 
|  |  | 
|  | uint32_t IfConversion::SplatCondition(analysis::Vector* vec_data_ty, | 
|  | uint32_t cond, | 
|  | InstructionBuilder* builder) { | 
|  | // If the data inputs to OpSelect are vectors, the condition for | 
|  | // OpSelect must be a boolean vector with the same number of | 
|  | // components. So splat the condition for the branch into a vector | 
|  | // type. | 
|  | analysis::Bool bool_ty; | 
|  | analysis::Vector bool_vec_ty(&bool_ty, vec_data_ty->element_count()); | 
|  | uint32_t bool_vec_id = | 
|  | context()->get_type_mgr()->GetTypeInstruction(&bool_vec_ty); | 
|  | std::vector<uint32_t> ids(vec_data_ty->element_count(), cond); | 
|  | return builder->AddCompositeConstruct(bool_vec_id, ids)->result_id(); | 
|  | } | 
|  |  | 
|  | bool IfConversion::CheckType(uint32_t id) { | 
|  | Instruction* type = get_def_use_mgr()->GetDef(id); | 
|  | SpvOp op = type->opcode(); | 
|  | if (spvOpcodeIsScalarType(op) || op == SpvOpTypePointer || | 
|  | op == SpvOpTypeVector) | 
|  | return true; | 
|  | return false; | 
|  | } | 
|  |  | 
|  | BasicBlock* IfConversion::GetBlock(uint32_t id) { | 
|  | return context()->get_instr_block(get_def_use_mgr()->GetDef(id)); | 
|  | } | 
|  |  | 
|  | BasicBlock* IfConversion::GetIncomingBlock(Instruction* phi, | 
|  | uint32_t predecessor) { | 
|  | uint32_t in_index = 2 * predecessor + 1; | 
|  | return GetBlock(phi->GetSingleWordInOperand(in_index)); | 
|  | } | 
|  |  | 
|  | Instruction* IfConversion::GetIncomingValue(Instruction* phi, | 
|  | uint32_t predecessor) { | 
|  | uint32_t in_index = 2 * predecessor; | 
|  | return get_def_use_mgr()->GetDef(phi->GetSingleWordInOperand(in_index)); | 
|  | } | 
|  |  | 
|  | void IfConversion::HoistInstruction(Instruction* inst, BasicBlock* target_block, | 
|  | DominatorAnalysis* dominators) { | 
|  | BasicBlock* inst_block = context()->get_instr_block(inst); | 
|  | if (!inst_block) { | 
|  | // This is in the header, and dominates everything. | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (dominators->Dominates(inst_block, target_block)) { | 
|  | // Already in position.  No work to do. | 
|  | return; | 
|  | } | 
|  |  | 
|  | assert(inst->IsOpcodeCodeMotionSafe() && | 
|  | "Trying to move an instruction that is not safe to move."); | 
|  |  | 
|  | // First hoist all instructions it depends on. | 
|  | analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr(); | 
|  | inst->ForEachInId( | 
|  | [this, target_block, def_use_mgr, dominators](uint32_t* id) { | 
|  | Instruction* operand_inst = def_use_mgr->GetDef(*id); | 
|  | HoistInstruction(operand_inst, target_block, dominators); | 
|  | }); | 
|  |  | 
|  | Instruction* insertion_pos = target_block->terminator(); | 
|  | if ((insertion_pos)->PreviousNode()->opcode() == SpvOpSelectionMerge) { | 
|  | insertion_pos = insertion_pos->PreviousNode(); | 
|  | } | 
|  | inst->RemoveFromList(); | 
|  | insertion_pos->InsertBefore(std::unique_ptr<Instruction>(inst)); | 
|  | context()->set_instr_block(inst, target_block); | 
|  | } | 
|  |  | 
|  | bool IfConversion::CanHoistInstruction(Instruction* inst, | 
|  | BasicBlock* target_block, | 
|  | DominatorAnalysis* dominators) { | 
|  | BasicBlock* inst_block = context()->get_instr_block(inst); | 
|  | if (!inst_block) { | 
|  | // This is in the header, and dominates everything. | 
|  | return true; | 
|  | } | 
|  |  | 
|  | if (dominators->Dominates(inst_block, target_block)) { | 
|  | // Already in position.  No work to do. | 
|  | return true; | 
|  | } | 
|  |  | 
|  | if (!inst->IsOpcodeCodeMotionSafe()) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // Check all instruction |inst| depends on. | 
|  | analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr(); | 
|  | return inst->WhileEachInId( | 
|  | [this, target_block, def_use_mgr, dominators](uint32_t* id) { | 
|  | Instruction* operand_inst = def_use_mgr->GetDef(*id); | 
|  | return CanHoistInstruction(operand_inst, target_block, dominators); | 
|  | }); | 
|  | } | 
|  |  | 
|  | }  // namespace opt | 
|  | }  // namespace spvtools |