| /* | |
| * Copyright (C) 2016 The Android Open Source Project | |
| * | |
| * 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 "control_flow_simplifier.h" | |
| #include "optimizing/nodes.h" | |
| #include "reference_type_propagation.h" | |
| namespace art HIDDEN { | |
| static constexpr size_t kMaxInstructionsInBranch = 1u; | |
| HControlFlowSimplifier::HControlFlowSimplifier(HGraph* graph, | |
| OptimizingCompilerStats* stats, | |
| const char* name) | |
| : HOptimization(graph, name, stats) { | |
| } | |
| // Returns true if `block` has only one predecessor, ends with a Goto | |
| // or a Return and contains at most `kMaxInstructionsInBranch` other | |
| // movable instruction with no side-effects. | |
| static bool IsSimpleBlock(HBasicBlock* block) { | |
| if (block->GetPredecessors().size() != 1u) { | |
| return false; | |
| } | |
| DCHECK(block->GetPhis().IsEmpty()); | |
| size_t num_instructions = 0u; | |
| for (HInstructionIterator it(block->GetInstructions()); !it.Done(); it.Advance()) { | |
| HInstruction* instruction = it.Current(); | |
| if (instruction->IsControlFlow()) { | |
| return instruction->IsGoto() || instruction->IsReturn(); | |
| } else if (instruction->CanBeMoved() && | |
| !instruction->HasSideEffects() && | |
| !instruction->CanThrow()) { | |
| if (instruction->IsSelect() && instruction->AsSelect()->GetCondition()->GetBlock() == block) { | |
| // Count one HCondition and HSelect in the same block as a single instruction. | |
| // This enables finding nested selects. | |
| continue; | |
| } else if (++num_instructions > kMaxInstructionsInBranch) { | |
| return false; // bail as soon as we exceed number of allowed instructions | |
| } | |
| } else { | |
| return false; | |
| } | |
| } | |
| LOG(FATAL) << "Unreachable"; | |
| UNREACHABLE(); | |
| } | |
| // Returns true if 'block1' and 'block2' are empty and merge into the | |
| // same single successor. | |
| static bool BlocksMergeTogether(HBasicBlock* block1, HBasicBlock* block2) { | |
| return block1->GetSingleSuccessor() == block2->GetSingleSuccessor(); | |
| } | |
| // Search `block` for phis that have different inputs at `index1` and `index2`. | |
| // If none is found, returns `{true, nullptr}`. | |
| // If exactly one such `phi` is found, returns `{true, phi}`. | |
| // Otherwise (if more than one such phi is found), returns `{false, nullptr}`. | |
| static std::pair<bool, HPhi*> HasAtMostOnePhiWithDifferentInputs(HBasicBlock* block, | |
| size_t index1, | |
| size_t index2) { | |
| DCHECK_NE(index1, index2); | |
| HPhi* select_phi = nullptr; | |
| for (HInstructionIterator it(block->GetPhis()); !it.Done(); it.Advance()) { | |
| HPhi* phi = it.Current()->AsPhi(); | |
| auto&& inputs = phi->GetInputs(); | |
| if (inputs[index1] == inputs[index2]) { | |
| continue; | |
| } | |
| if (select_phi == nullptr) { | |
| // First phi found. | |
| select_phi = phi; | |
| } else { | |
| // More than one phi found, return null. | |
| return {false, nullptr}; | |
| } | |
| } | |
| return {true, select_phi}; | |
| } | |
| bool HControlFlowSimplifier::TryGenerateSelectSimpleDiamondPattern( | |
| HBasicBlock* block, ScopedArenaSafeMap<HInstruction*, HSelect*>* cache) { | |
| DCHECK(block->GetLastInstruction()->IsIf()); | |
| HIf* if_instruction = block->GetLastInstruction()->AsIf(); | |
| HBasicBlock* true_block = if_instruction->IfTrueSuccessor(); | |
| HBasicBlock* false_block = if_instruction->IfFalseSuccessor(); | |
| DCHECK_NE(true_block, false_block); | |
| if (!IsSimpleBlock(true_block) || | |
| !IsSimpleBlock(false_block) || | |
| !BlocksMergeTogether(true_block, false_block)) { | |
| return false; | |
| } | |
| HBasicBlock* merge_block = true_block->GetSingleSuccessor(); | |
| // If the branches are not empty, move instructions in front of the If. | |
| // TODO(dbrazdil): This puts an instruction between If and its condition. | |
| // Implement moving of conditions to first users if possible. | |
| while (!true_block->IsSingleGoto() && !true_block->IsSingleReturn()) { | |
| HInstruction* instr = true_block->GetFirstInstruction(); | |
| DCHECK(!instr->CanThrow()); | |
| instr->MoveBefore(if_instruction); | |
| } | |
| while (!false_block->IsSingleGoto() && !false_block->IsSingleReturn()) { | |
| HInstruction* instr = false_block->GetFirstInstruction(); | |
| DCHECK(!instr->CanThrow()); | |
| instr->MoveBefore(if_instruction); | |
| } | |
| DCHECK(true_block->IsSingleGoto() || true_block->IsSingleReturn()); | |
| DCHECK(false_block->IsSingleGoto() || false_block->IsSingleReturn()); | |
| // Find the resulting true/false values. | |
| size_t predecessor_index_true = merge_block->GetPredecessorIndexOf(true_block); | |
| size_t predecessor_index_false = merge_block->GetPredecessorIndexOf(false_block); | |
| DCHECK_NE(predecessor_index_true, predecessor_index_false); | |
| bool both_successors_return = true_block->IsSingleReturn() && false_block->IsSingleReturn(); | |
| // TODO(solanes): Extend to support multiple phis? e.g. | |
| // int a, b; | |
| // if (bool) { | |
| // a = 0; b = 1; | |
| // } else { | |
| // a = 1; b = 2; | |
| // } | |
| // // use a and b | |
| bool at_most_one_phi_with_different_inputs = false; | |
| HPhi* phi = nullptr; | |
| HInstruction* true_value = nullptr; | |
| HInstruction* false_value = nullptr; | |
| if (both_successors_return) { | |
| // Note: This can create a select with the same then-value and else-value. | |
| true_value = true_block->GetFirstInstruction()->InputAt(0); | |
| false_value = false_block->GetFirstInstruction()->InputAt(0); | |
| } else { | |
| std::tie(at_most_one_phi_with_different_inputs, phi) = HasAtMostOnePhiWithDifferentInputs( | |
| merge_block, predecessor_index_true, predecessor_index_false); | |
| if (!at_most_one_phi_with_different_inputs) { | |
| return false; | |
| } | |
| if (phi != nullptr) { | |
| true_value = phi->InputAt(predecessor_index_true); | |
| false_value = phi->InputAt(predecessor_index_false); | |
| } // else we don't need to create a `HSelect` at all. | |
| } | |
| DCHECK(both_successors_return || at_most_one_phi_with_different_inputs); | |
| // Create the Select instruction and insert it in front of the If. | |
| HInstruction* condition = if_instruction->InputAt(0); | |
| HSelect* select = nullptr; | |
| if (both_successors_return || phi != nullptr) { | |
| select = new (graph_->GetAllocator()) HSelect(condition, | |
| true_value, | |
| false_value, | |
| if_instruction->GetDexPc()); | |
| block->InsertInstructionBefore(select, if_instruction); | |
| if (both_successors_return) { | |
| if (true_value->GetType() == DataType::Type::kReference) { | |
| DCHECK(false_value->GetType() == DataType::Type::kReference); | |
| ReferenceTypePropagation::FixUpSelectType(select, graph_->GetHandleCache()); | |
| } | |
| false_block->GetFirstInstruction()->ReplaceInput(select, 0); | |
| } else { | |
| if (phi->GetType() == DataType::Type::kReference) { | |
| select->SetReferenceTypeInfoIfValid(phi->GetReferenceTypeInfo()); | |
| } | |
| phi->ReplaceInput(select, predecessor_index_false); // We'll remove the true branch below. | |
| } | |
| } | |
| // Remove the true branch which removes the corresponding Phi input if needed. | |
| // If left only with the false branch, the Phi is automatically removed. | |
| true_block->DisconnectAndDelete(); | |
| // Merge remaining blocks which are now connected with Goto. | |
| DCHECK_EQ(block->GetSingleSuccessor(), false_block); | |
| block->MergeWith(false_block); | |
| if (!both_successors_return && merge_block->GetPredecessors().size() == 1u) { | |
| DCHECK_IMPLIES(phi != nullptr, phi->GetBlock() == nullptr); | |
| DCHECK(merge_block->GetPhis().IsEmpty()); | |
| DCHECK_EQ(block->GetSingleSuccessor(), merge_block); | |
| block->MergeWith(merge_block); | |
| } | |
| MaybeRecordStat(stats_, select != nullptr ? MethodCompilationStat::kControlFlowSelectGenerated | |
| : MethodCompilationStat::kControlFlowDiamondRemoved); | |
| // Very simple way of finding common subexpressions in the generated HSelect statements | |
| // (since this runs after GVN). Lookup by condition, and reuse latest one if possible | |
| // (due to post order, latest select is most likely replacement). If needed, we could | |
| // improve this by e.g. using the operands in the map as well. | |
| if (select != nullptr) { | |
| auto it = cache->find(condition); | |
| if (it == cache->end()) { | |
| cache->Put(condition, select); | |
| } else { | |
| // Found cached value. See if latest can replace cached in the HIR. | |
| HSelect* cached_select = it->second; | |
| DCHECK_EQ(cached_select->GetCondition(), select->GetCondition()); | |
| if (cached_select->GetTrueValue() == select->GetTrueValue() && | |
| cached_select->GetFalseValue() == select->GetFalseValue() && | |
| select->StrictlyDominates(cached_select)) { | |
| cached_select->ReplaceWith(select); | |
| cached_select->GetBlock()->RemoveInstruction(cached_select); | |
| } | |
| it->second = select; // always cache latest | |
| } | |
| } | |
| // No need to update dominance information, as we are simplifying | |
| // a simple diamond shape, where the join block is merged with the | |
| // entry block. Any following blocks would have had the join block | |
| // as a dominator, and `MergeWith` handles changing that to the | |
| // entry block | |
| return true; | |
| } | |
| HBasicBlock* HControlFlowSimplifier::TryFixupDoubleDiamondPattern(HBasicBlock* block) { | |
| DCHECK(block->GetLastInstruction()->IsIf()); | |
| HIf* if_instruction = block->GetLastInstruction()->AsIf(); | |
| HBasicBlock* true_block = if_instruction->IfTrueSuccessor(); | |
| HBasicBlock* false_block = if_instruction->IfFalseSuccessor(); | |
| DCHECK_NE(true_block, false_block); | |
| // One branch must be a single goto, and the other one the inner if. | |
| if (true_block->IsSingleGoto() == false_block->IsSingleGoto()) { | |
| return nullptr; | |
| } | |
| HBasicBlock* single_goto = true_block->IsSingleGoto() ? true_block : false_block; | |
| HBasicBlock* inner_if_block = true_block->IsSingleGoto() ? false_block : true_block; | |
| // The innner if branch has to be a block with just a comparison and an if. | |
| if (!inner_if_block->EndsWithIf() || | |
| inner_if_block->GetLastInstruction()->AsIf()->InputAt(0) != | |
| inner_if_block->GetFirstInstruction() || | |
| inner_if_block->GetLastInstruction()->GetPrevious() != | |
| inner_if_block->GetFirstInstruction() || | |
| !inner_if_block->GetFirstInstruction()->IsCondition()) { | |
| return nullptr; | |
| } | |
| HIf* inner_if_instruction = inner_if_block->GetLastInstruction()->AsIf(); | |
| HBasicBlock* inner_if_true_block = inner_if_instruction->IfTrueSuccessor(); | |
| HBasicBlock* inner_if_false_block = inner_if_instruction->IfFalseSuccessor(); | |
| if (!inner_if_true_block->IsSingleGoto() || !inner_if_false_block->IsSingleGoto()) { | |
| return nullptr; | |
| } | |
| // One must merge into the outer condition and the other must not. | |
| if (BlocksMergeTogether(single_goto, inner_if_true_block) == | |
| BlocksMergeTogether(single_goto, inner_if_false_block)) { | |
| return nullptr; | |
| } | |
| // First merge merges the outer if with one of the inner if branches. The block must be a Phi and | |
| // a Goto. | |
| HBasicBlock* first_merge = single_goto->GetSingleSuccessor(); | |
| if (first_merge->GetNumberOfPredecessors() != 2 || | |
| first_merge->GetPhis().CountSize() != 1 || | |
| !first_merge->GetLastInstruction()->IsGoto() || | |
| first_merge->GetFirstInstruction() != first_merge->GetLastInstruction()) { | |
| return nullptr; | |
| } | |
| HPhi* first_phi = first_merge->GetFirstPhi()->AsPhi(); | |
| // Second merge is first_merge and the remainder branch merging. It must be phi + goto, or phi + | |
| // return. Depending on the first merge, we define the second merge. | |
| HBasicBlock* merges_into_second_merge = | |
| BlocksMergeTogether(single_goto, inner_if_true_block) | |
| ? inner_if_false_block | |
| : inner_if_true_block; | |
| if (!BlocksMergeTogether(first_merge, merges_into_second_merge)) { | |
| return nullptr; | |
| } | |
| HBasicBlock* second_merge = merges_into_second_merge->GetSingleSuccessor(); | |
| if (second_merge->GetNumberOfPredecessors() != 2 || | |
| second_merge->GetPhis().CountSize() != 1 || | |
| !(second_merge->GetLastInstruction()->IsGoto() || | |
| second_merge->GetLastInstruction()->IsReturn()) || | |
| second_merge->GetFirstInstruction() != second_merge->GetLastInstruction()) { | |
| return nullptr; | |
| } | |
| size_t index = second_merge->GetPredecessorIndexOf(merges_into_second_merge); | |
| HPhi* second_phi = second_merge->GetFirstPhi()->AsPhi(); | |
| // Merge the phis. | |
| first_phi->AddInput(second_phi->InputAt(index)); | |
| merges_into_second_merge->ReplaceSuccessor(second_merge, first_merge); | |
| second_phi->ReplaceWith(first_phi); | |
| second_merge->RemovePhi(second_phi); | |
| // Sort out the new domination before merging the blocks | |
| DCHECK_EQ(second_merge->GetSinglePredecessor(), first_merge); | |
| second_merge->GetDominator()->RemoveDominatedBlock(second_merge); | |
| second_merge->SetDominator(first_merge); | |
| first_merge->AddDominatedBlock(second_merge); | |
| first_merge->MergeWith(second_merge); | |
| // No need to update dominance information. There's a chance that `merges_into_second_merge` | |
| // doesn't come before `first_merge` but we don't need to fix it since `merges_into_second_merge` | |
| // will disappear from the graph altogether when doing the follow-up | |
| // TryGenerateSelectSimpleDiamondPattern. | |
| return inner_if_block; | |
| } | |
| bool HControlFlowSimplifier::Run() { | |
| bool did_select = false; | |
| // Select cache with local allocator. | |
| ScopedArenaAllocator allocator(graph_->GetArenaStack()); | |
| ScopedArenaSafeMap<HInstruction*, HSelect*> cache( | |
| std::less<HInstruction*>(), allocator.Adapter(kArenaAllocControlFlowSimplifier)); | |
| // Iterate in post order in the unlikely case that removing one occurrence of | |
| // the selection pattern empties a branch block of another occurrence. | |
| for (HBasicBlock* block : graph_->GetPostOrder()) { | |
| if (!block->EndsWithIf()) { | |
| continue; | |
| } | |
| if (TryGenerateSelectSimpleDiamondPattern(block, &cache)) { | |
| did_select = true; | |
| } else { | |
| // Try to fix up the odd version of the double diamond pattern. If we could do it, it means | |
| // that we can generate two selects. | |
| HBasicBlock* inner_if_block = TryFixupDoubleDiamondPattern(block); | |
| if (inner_if_block != nullptr) { | |
| // Generate the selects now since `inner_if_block` should be after `block` in PostOrder. | |
| bool result = TryGenerateSelectSimpleDiamondPattern(inner_if_block, &cache); | |
| DCHECK(result); | |
| result = TryGenerateSelectSimpleDiamondPattern(block, &cache); | |
| DCHECK(result); | |
| did_select = true; | |
| } | |
| } | |
| } | |
| return did_select; | |
| } | |
| } // namespace art |