/* * 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 "select_generator.h" #include "optimizing/nodes.h" #include "reference_type_propagation.h" namespace art HIDDEN { static constexpr size_t kMaxInstructionsInBranch = 1u; HSelectGenerator::HSelectGenerator(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(); } // Returns nullptr if `block` has either no phis or there is more than one phi. Otherwise returns // that phi. static HPhi* GetSinglePhi(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(); if (select_phi == nullptr) { // First phi found. select_phi = phi; } else { // More than one phi found, return null. return nullptr; } } return select_phi; } bool HSelectGenerator::TryGenerateSelectSimpleDiamondPattern( HBasicBlock* block, ScopedArenaSafeMap* 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 HPhi* phi = GetSinglePhi(merge_block, predecessor_index_true, predecessor_index_false); HInstruction* true_value = nullptr; HInstruction* false_value = nullptr; if (both_successors_return) { true_value = true_block->GetFirstInstruction()->InputAt(0); false_value = false_block->GetFirstInstruction()->InputAt(0); } else if (phi != nullptr) { true_value = phi->InputAt(predecessor_index_true); false_value = phi->InputAt(predecessor_index_false); } else { return false; } DCHECK(both_successors_return || phi != nullptr); // Create the Select instruction and insert it in front of the If. HInstruction* condition = if_instruction->InputAt(0); HSelect* select = new (graph_->GetAllocator()) HSelect(condition, true_value, false_value, if_instruction->GetDexPc()); if (both_successors_return) { if (true_value->GetType() == DataType::Type::kReference) { DCHECK(false_value->GetType() == DataType::Type::kReference); ReferenceTypePropagation::FixUpInstructionType(select, graph_->GetHandleCache()); } } else if (phi->GetType() == DataType::Type::kReference) { select->SetReferenceTypeInfoIfValid(phi->GetReferenceTypeInfo()); } block->InsertInstructionBefore(select, if_instruction); // Remove the true branch which removes the corresponding Phi // input if needed. If left only with the false branch, the Phi is // automatically removed. if (both_successors_return) { false_block->GetFirstInstruction()->ReplaceInput(select, 0); } else { phi->ReplaceInput(select, predecessor_index_false); } bool only_two_predecessors = (merge_block->GetPredecessors().size() == 2u); 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 && only_two_predecessors) { DCHECK_EQ(only_two_predecessors, phi->GetBlock() == nullptr); DCHECK_EQ(block->GetSingleSuccessor(), merge_block); block->MergeWith(merge_block); } MaybeRecordStat(stats_, MethodCompilationStat::kSelectGenerated); // 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. 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* HSelectGenerator::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 HSelectGenerator::Run() { bool did_select = false; // Select cache with local allocator. ScopedArenaAllocator allocator(graph_->GetArenaStack()); ScopedArenaSafeMap cache(std::less(), allocator.Adapter(kArenaAllocSelectGenerator)); // 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