| /* |
| * 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 "base/scoped_arena_containers.h" |
| #include "reference_type_propagation.h" |
| |
| namespace art { |
| |
| 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 |
| // with different inputs at `index1` and `index2`. Otherwise returns that phi. |
| static HPhi* GetSingleChangedPhi(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 (phi->InputAt(index1) != phi->InputAt(index2)) { |
| if (select_phi == nullptr) { |
| // First phi with different inputs for the two indices found. |
| select_phi = phi; |
| } else { |
| // More than one phis has different inputs for the two indices. |
| return nullptr; |
| } |
| } |
| } |
| return select_phi; |
| } |
| |
| bool HSelectGenerator::Run() { |
| bool didSelect = false; |
| // Select cache with local allocator. |
| ScopedArenaAllocator allocator(graph_->GetArenaStack()); |
| ScopedArenaSafeMap<HInstruction*, HSelect*> cache( |
| std::less<HInstruction*>(), 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; |
| |
| // Find elements of the diamond pattern. |
| 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)) { |
| continue; |
| } |
| 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(); |
| HPhi* phi = GetSingleChangedPhi(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 { |
| continue; |
| } |
| 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->SetReferenceTypeInfo(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 = it->second; |
| DCHECK_EQ(cached->GetCondition(), select->GetCondition()); |
| if (cached->GetTrueValue() == select->GetTrueValue() && |
| cached->GetFalseValue() == select->GetFalseValue() && |
| select->StrictlyDominates(cached)) { |
| cached->ReplaceWith(select); |
| cached->GetBlock()->RemoveInstruction(cached); |
| } |
| 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. |
| didSelect = true; |
| } |
| return didSelect; |
| } |
| |
| } // namespace art |