| /* | |
| * Copyright (C) 2014 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 "dead_code_elimination.h" | |
| #include "android-base/logging.h" | |
| #include "base/array_ref.h" | |
| #include "base/bit_vector-inl.h" | |
| #include "base/logging.h" | |
| #include "base/scoped_arena_allocator.h" | |
| #include "base/scoped_arena_containers.h" | |
| #include "base/stl_util.h" | |
| #include "optimizing/nodes.h" | |
| #include "optimizing/nodes_vector.h" | |
| #include "ssa_phi_elimination.h" | |
| namespace art HIDDEN { | |
| static void MarkReachableBlocks(HGraph* graph, BitVectorView<size_t> visited) { | |
| // Use local allocator for allocating memory. | |
| ScopedArenaAllocator allocator(graph->GetArenaStack()); | |
| ScopedArenaVector<HBasicBlock*> worklist(allocator.Adapter(kArenaAllocDCE)); | |
| constexpr size_t kDefaultWorlistSize = 8; | |
| worklist.reserve(kDefaultWorlistSize); | |
| visited.SetBit(graph->GetEntryBlock()->GetBlockId()); | |
| worklist.push_back(graph->GetEntryBlock()); | |
| while (!worklist.empty()) { | |
| HBasicBlock* block = worklist.back(); | |
| worklist.pop_back(); | |
| int block_id = block->GetBlockId(); | |
| DCHECK(visited.IsBitSet(block_id)); | |
| ArrayRef<HBasicBlock* const> live_successors(block->GetSuccessors()); | |
| HInstruction* last_instruction = block->GetLastInstruction(); | |
| if (last_instruction->IsIf()) { | |
| HIf* if_instruction = last_instruction->AsIf(); | |
| HInstruction* condition = if_instruction->InputAt(0); | |
| if (condition->IsIntConstant()) { | |
| if (condition->AsIntConstant()->IsTrue()) { | |
| live_successors = live_successors.SubArray(0u, 1u); | |
| DCHECK_EQ(live_successors[0], if_instruction->IfTrueSuccessor()); | |
| } else { | |
| DCHECK(condition->AsIntConstant()->IsFalse()) << condition->AsIntConstant()->GetValue(); | |
| live_successors = live_successors.SubArray(1u, 1u); | |
| DCHECK_EQ(live_successors[0], if_instruction->IfFalseSuccessor()); | |
| } | |
| } | |
| } else if (last_instruction->IsPackedSwitch()) { | |
| HPackedSwitch* switch_instruction = last_instruction->AsPackedSwitch(); | |
| HInstruction* switch_input = switch_instruction->InputAt(0); | |
| if (switch_input->IsIntConstant()) { | |
| int32_t switch_value = switch_input->AsIntConstant()->GetValue(); | |
| int32_t start_value = switch_instruction->GetStartValue(); | |
| // Note: Though the spec forbids packed-switch values to wrap around, we leave | |
| // that task to the verifier and use unsigned arithmetic with it's "modulo 2^32" | |
| // semantics to check if the value is in range, wrapped or not. | |
| uint32_t switch_index = | |
| static_cast<uint32_t>(switch_value) - static_cast<uint32_t>(start_value); | |
| if (switch_index < switch_instruction->GetNumEntries()) { | |
| live_successors = live_successors.SubArray(switch_index, 1u); | |
| DCHECK_EQ(live_successors[0], block->GetSuccessors()[switch_index]); | |
| } else { | |
| live_successors = live_successors.SubArray(switch_instruction->GetNumEntries(), 1u); | |
| DCHECK_EQ(live_successors[0], switch_instruction->GetDefaultBlock()); | |
| } | |
| } | |
| } | |
| for (HBasicBlock* successor : live_successors) { | |
| // Add only those successors that have not been visited yet. | |
| if (!visited.IsBitSet(successor->GetBlockId())) { | |
| visited.SetBit(successor->GetBlockId()); | |
| worklist.push_back(successor); | |
| } | |
| } | |
| } | |
| } | |
| void HDeadCodeElimination::MaybeRecordDeadBlock(HBasicBlock* block) { | |
| if (stats_ != nullptr) { | |
| stats_->RecordStat(MethodCompilationStat::kRemovedDeadInstruction, | |
| block->GetPhis().CountSize() + block->GetInstructions().CountSize()); | |
| } | |
| } | |
| void HDeadCodeElimination::MaybeRecordSimplifyIf() { | |
| if (stats_ != nullptr) { | |
| stats_->RecordStat(MethodCompilationStat::kSimplifyIf); | |
| } | |
| } | |
| static bool HasInput(HCondition* instruction, HInstruction* input) { | |
| return (instruction->InputAt(0) == input) || | |
| (instruction->InputAt(1) == input); | |
| } | |
| static bool HasEquality(IfCondition condition) { | |
| switch (condition) { | |
| case kCondEQ: | |
| case kCondLE: | |
| case kCondGE: | |
| case kCondBE: | |
| case kCondAE: | |
| return true; | |
| case kCondNE: | |
| case kCondLT: | |
| case kCondGT: | |
| case kCondB: | |
| case kCondA: | |
| return false; | |
| } | |
| } | |
| static HConstant* Evaluate(HCondition* condition, HInstruction* left, HInstruction* right) { | |
| if (left == right && !DataType::IsFloatingPointType(left->GetType())) { | |
| return condition->GetBlock()->GetGraph()->GetIntConstant( | |
| HasEquality(condition->GetCondition()) ? 1 : 0); | |
| } | |
| if (!left->IsConstant() || !right->IsConstant()) { | |
| return nullptr; | |
| } | |
| if (left->IsIntConstant()) { | |
| return condition->Evaluate(left->AsIntConstant(), right->AsIntConstant()); | |
| } else if (left->IsNullConstant()) { | |
| return condition->Evaluate(left->AsNullConstant(), right->AsNullConstant()); | |
| } else if (left->IsLongConstant()) { | |
| return condition->Evaluate(left->AsLongConstant(), right->AsLongConstant()); | |
| } else if (left->IsFloatConstant()) { | |
| return condition->Evaluate(left->AsFloatConstant(), right->AsFloatConstant()); | |
| } else { | |
| DCHECK(left->IsDoubleConstant()); | |
| return condition->Evaluate(left->AsDoubleConstant(), right->AsDoubleConstant()); | |
| } | |
| } | |
| static bool RemoveNonNullControlDependences(HBasicBlock* block, HBasicBlock* throws) { | |
| // Test for an if as last statement. | |
| if (!block->EndsWithIf()) { | |
| return false; | |
| } | |
| HIf* ifs = block->GetLastInstruction()->AsIf(); | |
| // Find either: | |
| // if obj == null | |
| // throws | |
| // else | |
| // not_throws | |
| // or: | |
| // if obj != null | |
| // not_throws | |
| // else | |
| // throws | |
| HInstruction* cond = ifs->InputAt(0); | |
| HBasicBlock* not_throws = nullptr; | |
| if (throws == ifs->IfTrueSuccessor() && cond->IsEqual()) { | |
| not_throws = ifs->IfFalseSuccessor(); | |
| } else if (throws == ifs->IfFalseSuccessor() && cond->IsNotEqual()) { | |
| not_throws = ifs->IfTrueSuccessor(); | |
| } else { | |
| return false; | |
| } | |
| DCHECK(cond->IsEqual() || cond->IsNotEqual()); | |
| HInstruction* obj = cond->InputAt(1); | |
| if (obj->IsNullConstant()) { | |
| obj = cond->InputAt(0); | |
| } else if (!cond->InputAt(0)->IsNullConstant()) { | |
| return false; | |
| } | |
| // We can't create a BoundType for an object with an invalid RTI. | |
| const ReferenceTypeInfo ti = obj->GetReferenceTypeInfo(); | |
| if (!ti.IsValid()) { | |
| return false; | |
| } | |
| // Scan all uses of obj and find null check under control dependence. | |
| HBoundType* bound = nullptr; | |
| const HUseList<HInstruction*>& uses = obj->GetUses(); | |
| for (auto it = uses.begin(), end = uses.end(); it != end;) { | |
| HInstruction* user = it->GetUser(); | |
| ++it; // increment before possibly replacing | |
| if (user->IsNullCheck()) { | |
| HBasicBlock* user_block = user->GetBlock(); | |
| if (user_block != block && | |
| user_block != throws && | |
| block->Dominates(user_block)) { | |
| if (bound == nullptr) { | |
| bound = new (block->GetGraph()->GetAllocator()) HBoundType(obj); | |
| bound->SetUpperBound(ti, /*can_be_null*/ false); | |
| bound->SetReferenceTypeInfo(ti); | |
| bound->SetCanBeNull(false); | |
| not_throws->InsertInstructionBefore(bound, not_throws->GetFirstInstruction()); | |
| } | |
| user->ReplaceWith(bound); | |
| user_block->RemoveInstruction(user); | |
| } | |
| } | |
| } | |
| return bound != nullptr; | |
| } | |
| // Simplify the pattern: | |
| // | |
| // B1 | |
| // / \ | |
| // | instr_1 | |
| // | ... | |
| // | instr_n | |
| // | foo() // always throws | |
| // | instr_n+2 | |
| // | ... | |
| // | instr_n+m | |
| // \ goto B2 | |
| // \ / | |
| // B2 | |
| // | |
| // Into: | |
| // | |
| // B1 | |
| // / \ | |
| // | instr_1 | |
| // | ... | |
| // | instr_n | |
| // | foo() | |
| // | goto Exit | |
| // | | | |
| // B2 Exit | |
| // | |
| // Rationale: | |
| // Removal of the never taken edge to B2 may expose other optimization opportunities, such as code | |
| // sinking. | |
| // | |
| // Note: The example above is a simple one that uses a `goto` but we could end the block with an If, | |
| // for example. | |
| bool HDeadCodeElimination::SimplifyAlwaysThrows() { | |
| HBasicBlock* exit = graph_->GetExitBlock(); | |
| if (!graph_->HasAlwaysThrowingInvokes() || exit == nullptr) { | |
| return false; | |
| } | |
| bool rerun_dominance_and_loop_analysis = false; | |
| // Order does not matter, just pick one. | |
| for (HBasicBlock* block : graph_->GetReversePostOrder()) { | |
| if (block->IsTryBlock()) { | |
| // We don't want to perform the simplify always throws optimizations for throws inside of | |
| // tries since those throws might not go to the exit block. | |
| continue; | |
| } | |
| // We iterate to find the first instruction that always throws. If two instructions always | |
| // throw, the first one will throw and the second one will never be reached. | |
| HInstruction* throwing_invoke = nullptr; | |
| for (HInstructionIterator it(block->GetInstructions()); !it.Done(); it.Advance()) { | |
| if (it.Current()->IsInvoke() && it.Current()->AsInvoke()->AlwaysThrows()) { | |
| throwing_invoke = it.Current(); | |
| break; | |
| } | |
| } | |
| if (throwing_invoke == nullptr) { | |
| // No always-throwing instruction found. Continue with the rest of the blocks. | |
| continue; | |
| } | |
| // If we are already pointing at the exit block we could still remove the instructions | |
| // between the always throwing instruction, and the exit block. If we have no other | |
| // instructions, just continue since there's nothing to do. | |
| if (block->GetSuccessors().size() == 1 && | |
| block->GetSingleSuccessor() == exit && | |
| block->GetLastInstruction()->GetPrevious() == throwing_invoke) { | |
| continue; | |
| } | |
| // We split the block at the throwing instruction, and the instructions after the throwing | |
| // instructions will be disconnected from the graph after `block` points to the exit. | |
| // `RemoveDeadBlocks` will take care of removing this new block and its instructions. | |
| // Even though `SplitBefore` doesn't guarantee the graph to remain in SSA form, it is fine | |
| // since we do not break it. | |
| HBasicBlock* new_block = block->SplitBefore(throwing_invoke->GetNext(), | |
| /* require_graph_not_in_ssa_form= */ false); | |
| DCHECK_EQ(block->GetSingleSuccessor(), new_block); | |
| block->ReplaceSuccessor(new_block, exit); | |
| rerun_dominance_and_loop_analysis = true; | |
| MaybeRecordStat(stats_, MethodCompilationStat::kSimplifyThrowingInvoke); | |
| // Perform a quick follow up optimization on object != null control dependences | |
| // that is much cheaper to perform now than in a later phase. | |
| // If there are multiple predecessors, none may end with a HIf as required in | |
| // RemoveNonNullControlDependences because we split critical edges. | |
| if (block->GetPredecessors().size() == 1u && | |
| RemoveNonNullControlDependences(block->GetSinglePredecessor(), block)) { | |
| MaybeRecordStat(stats_, MethodCompilationStat::kRemovedNullCheck); | |
| } | |
| } | |
| // We need to re-analyze the graph in order to run DCE afterwards. | |
| if (rerun_dominance_and_loop_analysis) { | |
| graph_->RecomputeDominatorTree(); | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool HDeadCodeElimination::SimplifyIfs() { | |
| bool simplified_one_or_more_ifs = false; | |
| bool rerun_dominance_and_loop_analysis = false; | |
| // Iterating in PostOrder it's better for MaybeAddPhi as it can add a Phi for multiple If | |
| // instructions in a chain without updating the dominator chain. The branch redirection itself can | |
| // work in PostOrder or ReversePostOrder without issues. | |
| for (HBasicBlock* block : graph_->GetPostOrder()) { | |
| if (block->IsCatchBlock()) { | |
| // This simplification cannot be applied to catch blocks, because exception handler edges do | |
| // not represent normal control flow. Though in theory this could still apply to normal | |
| // control flow going directly to a catch block, we cannot support it at the moment because | |
| // the catch Phi's inputs do not correspond to the catch block's predecessors, so we cannot | |
| // identify which predecessor corresponds to a given statically evaluated input. | |
| continue; | |
| } | |
| HInstruction* last = block->GetLastInstruction(); | |
| if (!last->IsIf()) { | |
| continue; | |
| } | |
| if (block->IsLoopHeader()) { | |
| // We do not apply this optimization to loop headers as this could create irreducible loops. | |
| continue; | |
| } | |
| // We will add a Phi which allows the simplification to take place in cases where it wouldn't. | |
| MaybeAddPhi(block); | |
| // TODO(solanes): Investigate support for multiple phis in `block`. We can potentially "push | |
| // downwards" existing Phis into the true/false branches. For example, let's say we have another | |
| // Phi: Phi(x1,x2,x3,x4,x5,x6). This could turn into Phi(x1,x2) in the true branch, Phi(x3,x4) | |
| // in the false branch, and remain as Phi(x5,x6) in `block` (for edges that we couldn't | |
| // redirect). We might even be able to remove some phis altogether as they will have only one | |
| // value. | |
| if (block->HasSinglePhi() && | |
| block->GetFirstPhi()->HasOnlyOneNonEnvironmentUse()) { | |
| HInstruction* first = block->GetFirstInstruction(); | |
| bool has_only_phi_and_if = (last == first) && (last->InputAt(0) == block->GetFirstPhi()); | |
| bool has_only_phi_condition_and_if = | |
| !has_only_phi_and_if && | |
| first->IsCondition() && | |
| HasInput(first->AsCondition(), block->GetFirstPhi()) && | |
| (first->GetNext() == last) && | |
| (last->InputAt(0) == first) && | |
| first->HasOnlyOneNonEnvironmentUse(); | |
| if (has_only_phi_and_if || has_only_phi_condition_and_if) { | |
| HPhi* phi = block->GetFirstPhi()->AsPhi(); | |
| bool phi_input_is_left = (first->InputAt(0) == phi); | |
| // Walk over all inputs of the phis and update the control flow of | |
| // predecessors feeding constants to the phi. | |
| // Note that phi->InputCount() may change inside the loop. | |
| for (size_t i = 0; i < phi->InputCount();) { | |
| HInstruction* input = phi->InputAt(i); | |
| HInstruction* value_to_check = nullptr; | |
| if (has_only_phi_and_if) { | |
| if (input->IsIntConstant()) { | |
| value_to_check = input; | |
| } | |
| } else { | |
| DCHECK(has_only_phi_condition_and_if); | |
| if (phi_input_is_left) { | |
| value_to_check = Evaluate(first->AsCondition(), input, first->InputAt(1)); | |
| } else { | |
| value_to_check = Evaluate(first->AsCondition(), first->InputAt(0), input); | |
| } | |
| } | |
| if (value_to_check == nullptr) { | |
| // Could not evaluate to a constant, continue iterating over the inputs. | |
| ++i; | |
| } else { | |
| HBasicBlock* predecessor_to_update = block->GetPredecessors()[i]; | |
| HBasicBlock* successor_to_update = nullptr; | |
| if (value_to_check->AsIntConstant()->IsTrue()) { | |
| successor_to_update = last->AsIf()->IfTrueSuccessor(); | |
| } else { | |
| DCHECK(value_to_check->AsIntConstant()->IsFalse()) | |
| << value_to_check->AsIntConstant()->GetValue(); | |
| successor_to_update = last->AsIf()->IfFalseSuccessor(); | |
| } | |
| predecessor_to_update->ReplaceSuccessor(block, successor_to_update); | |
| phi->RemoveInputAt(i); | |
| simplified_one_or_more_ifs = true; | |
| if (block->IsInLoop()) { | |
| rerun_dominance_and_loop_analysis = true; | |
| } | |
| // For simplicity, don't create a dead block, let the dead code elimination | |
| // pass deal with it. | |
| if (phi->InputCount() == 1) { | |
| break; | |
| } | |
| } | |
| } | |
| if (block->GetPredecessors().size() == 1) { | |
| phi->ReplaceWith(phi->InputAt(0)); | |
| block->RemovePhi(phi); | |
| if (has_only_phi_condition_and_if) { | |
| // Evaluate here (and not wait for a constant folding pass) to open | |
| // more opportunities for DCE. | |
| HInstruction* result = first->AsCondition()->TryStaticEvaluation(); | |
| if (result != nullptr) { | |
| first->ReplaceWith(result); | |
| block->RemoveInstruction(first); | |
| } | |
| } | |
| } | |
| if (simplified_one_or_more_ifs) { | |
| MaybeRecordSimplifyIf(); | |
| } | |
| } | |
| } | |
| } | |
| // We need to re-analyze the graph in order to run DCE afterwards. | |
| if (simplified_one_or_more_ifs) { | |
| if (rerun_dominance_and_loop_analysis) { | |
| graph_->RecomputeDominatorTree(); | |
| } else { | |
| graph_->ClearDominanceInformation(); | |
| // We have introduced critical edges, remove them. | |
| graph_->SimplifyCFG(); | |
| graph_->ComputeDominanceInformation(); | |
| graph_->ComputeTryBlockInformation(); | |
| } | |
| } | |
| return simplified_one_or_more_ifs; | |
| } | |
| void HDeadCodeElimination::MaybeAddPhi(HBasicBlock* block) { | |
| DCHECK(block->GetLastInstruction()->IsIf()); | |
| HIf* if_instruction = block->GetLastInstruction()->AsIf(); | |
| if (if_instruction->InputAt(0)->IsConstant()) { | |
| // Constant values are handled in RemoveDeadBlocks. | |
| return; | |
| } | |
| if (block->GetNumberOfPredecessors() < 2u) { | |
| // Nothing to redirect. | |
| return; | |
| } | |
| if (!block->GetPhis().IsEmpty()) { | |
| // SimplifyIf doesn't currently work with multiple phis. Adding a phi here won't help that | |
| // optimization. | |
| return; | |
| } | |
| HBasicBlock* dominator = block->GetDominator(); | |
| if (!dominator->EndsWithIf()) { | |
| return; | |
| } | |
| HInstruction* input = if_instruction->InputAt(0); | |
| HInstruction* dominator_input = dominator->GetLastInstruction()->AsIf()->InputAt(0); | |
| const bool same_input = dominator_input == input; | |
| if (!same_input) { | |
| // Try to see if the dominator has the opposite input (e.g. if(cond) and if(!cond)). If that's | |
| // the case, we can perform the optimization with the false and true branches reversed. | |
| if (!dominator_input->IsCondition() || !input->IsCondition()) { | |
| return; | |
| } | |
| HCondition* block_cond = input->AsCondition(); | |
| HCondition* dominator_cond = dominator_input->AsCondition(); | |
| if (block_cond->GetLeft() != dominator_cond->GetLeft() || | |
| block_cond->GetRight() != dominator_cond->GetRight() || | |
| block_cond->GetOppositeCondition() != dominator_cond->GetCondition() || | |
| block_cond->GetBias() != dominator_cond->GetBias()) { | |
| return; | |
| } | |
| } | |
| if (kIsDebugBuild) { | |
| // `block`'s successors should have only one predecessor. Otherwise, we have a critical edge in | |
| // the graph. | |
| for (HBasicBlock* succ : block->GetSuccessors()) { | |
| DCHECK_EQ(succ->GetNumberOfPredecessors(), 1u); | |
| } | |
| } | |
| const size_t pred_size = block->GetNumberOfPredecessors(); | |
| HPhi* new_phi = new (graph_->GetAllocator()) | |
| HPhi(graph_->GetAllocator(), kNoRegNumber, pred_size, DataType::Type::kInt32); | |
| for (size_t index = 0; index < pred_size; index++) { | |
| HBasicBlock* pred = block->GetPredecessors()[index]; | |
| const bool dominated_by_true = | |
| dominator->GetLastInstruction()->AsIf()->IfTrueSuccessor()->Dominates(pred); | |
| const bool dominated_by_false = | |
| dominator->GetLastInstruction()->AsIf()->IfFalseSuccessor()->Dominates(pred); | |
| if (dominated_by_true == dominated_by_false) { | |
| // In this case, we can't know if we are coming from the true branch, or the false branch. It | |
| // happens in cases like: | |
| // 1 (outer if) | |
| // / \ | |
| // 2 3 (inner if) | |
| // | / \ | |
| // | 4 5 | |
| // \/ | | |
| // 6 | | |
| // \ | | |
| // 7 (has the same if(cond) as 1) | |
| // | | |
| // 8 | |
| // `7` (which would be `block` in this example), and `6` will come from both the true path and | |
| // the false path of `1`. We bumped into something similar in `HControlFlowSimplifier`. See | |
| // `HControlFlowSimplifier::TryFixupDoubleDiamondPattern()`. | |
| // TODO(solanes): Figure out if we can fix up the graph into a double diamond in a generic way | |
| // so that `HDeadCodeElimination` and `HControlFlowSimplifier` can take advantage of it. | |
| if (!same_input) { | |
| // `1` and `7` having the opposite condition is a case we are missing. We could potentially | |
| // add a BooleanNot instruction to be able to add the Phi, but it seems like overkill since | |
| // this case is not that common. | |
| return; | |
| } | |
| // The Phi will have `0`, `1`, and `cond` as inputs. If SimplifyIf redirects 0s and 1s, we | |
| // will end up with Phi(cond,...,cond) which will be replaced by `cond`. Effectively, we will | |
| // redirect edges that we are able to redirect and the rest will remain as before (i.e. we | |
| // won't have an extra Phi). | |
| new_phi->SetRawInputAt(index, input); | |
| } else { | |
| // Redirect to either the true branch (1), or the false branch (0). | |
| // Given that `dominated_by_true` is the exact opposite of `dominated_by_false`, | |
| // `(same_input && dominated_by_true) || (!same_input && dominated_by_false)` is equivalent to | |
| // `same_input == dominated_by_true`. | |
| new_phi->SetRawInputAt( | |
| index, | |
| same_input == dominated_by_true ? graph_->GetIntConstant(1) : graph_->GetIntConstant(0)); | |
| } | |
| } | |
| block->AddPhi(new_phi); | |
| if_instruction->ReplaceInput(new_phi, 0); | |
| // Remove the old input now, if possible. This allows the branch redirection in SimplifyIf to | |
| // work without waiting for another pass of DCE. | |
| if (input->IsDeadAndRemovable()) { | |
| DCHECK(!same_input) | |
| << " if both blocks have the same condition, it shouldn't be dead and removable since the " | |
| << "dominator block's If instruction would be using that condition."; | |
| input->GetBlock()->RemoveInstruction(input); | |
| } | |
| MaybeRecordStat(stats_, MethodCompilationStat::kSimplifyIfAddedPhi); | |
| } | |
| void HDeadCodeElimination::ConnectSuccessiveBlocks() { | |
| // Order does not matter. Skip the entry block by starting at index 1 in reverse post order. | |
| for (size_t i = 1u, size = graph_->GetReversePostOrder().size(); i != size; ++i) { | |
| HBasicBlock* block = graph_->GetReversePostOrder()[i]; | |
| DCHECK(!block->IsEntryBlock()); | |
| while (block->GetLastInstruction()->IsGoto()) { | |
| HBasicBlock* successor = block->GetSingleSuccessor(); | |
| if (successor->IsExitBlock() || successor->GetPredecessors().size() != 1u) { | |
| break; | |
| } | |
| DCHECK_LT(i, IndexOfElement(graph_->GetReversePostOrder(), successor)); | |
| block->MergeWith(successor); | |
| --size; | |
| DCHECK_EQ(size, graph_->GetReversePostOrder().size()); | |
| DCHECK_EQ(block, graph_->GetReversePostOrder()[i]); | |
| // Reiterate on this block in case it can be merged with its new successor. | |
| } | |
| } | |
| } | |
| struct HDeadCodeElimination::TryBelongingInformation { | |
| TryBelongingInformation(HGraph* graph, ScopedArenaAllocator* allocator) | |
| : blocks_in_try(ArenaBitVector::CreateFixedSize( | |
| allocator, graph->GetBlocks().size(), kArenaAllocDCE)), | |
| coalesced_try_entries(ArenaBitVector::CreateFixedSize( | |
| allocator, graph->GetBlocks().size(), kArenaAllocDCE)) {} | |
| // Which blocks belong in the try. | |
| BitVectorView<size_t> blocks_in_try; | |
| // Which other try entries are referencing this same try. | |
| BitVectorView<size_t> coalesced_try_entries; | |
| }; | |
| bool HDeadCodeElimination::CanPerformTryRemoval(const TryBelongingInformation& try_belonging_info) { | |
| const ArenaVector<HBasicBlock*>& blocks = graph_->GetBlocks(); | |
| for (uint32_t i : try_belonging_info.blocks_in_try.Indexes()) { | |
| for (HInstructionIterator it(blocks[i]->GetInstructions()); !it.Done(); it.Advance()) { | |
| if (it.Current()->CanThrow()) { | |
| return false; | |
| } | |
| } | |
| } | |
| return true; | |
| } | |
| void HDeadCodeElimination::DisconnectHandlersAndUpdateTryBoundary( | |
| HBasicBlock* block, | |
| /* out */ bool* any_block_in_loop) { | |
| if (block->IsInLoop()) { | |
| *any_block_in_loop = true; | |
| } | |
| // Disconnect the handlers. | |
| while (block->GetSuccessors().size() > 1) { | |
| HBasicBlock* handler = block->GetSuccessors()[1]; | |
| DCHECK(handler->IsCatchBlock()); | |
| block->RemoveSuccessor(handler); | |
| handler->RemovePredecessor(block); | |
| if (handler->IsInLoop()) { | |
| *any_block_in_loop = true; | |
| } | |
| } | |
| // Change TryBoundary to Goto. | |
| DCHECK(block->EndsWithTryBoundary()); | |
| HInstruction* last = block->GetLastInstruction(); | |
| block->RemoveInstruction(last); | |
| block->AddInstruction(new (graph_->GetAllocator()) HGoto(last->GetDexPc())); | |
| DCHECK_EQ(block->GetSuccessors().size(), 1u); | |
| } | |
| void HDeadCodeElimination::RemoveTry(HBasicBlock* try_entry, | |
| const TryBelongingInformation& try_belonging_info, | |
| /* out */ bool* any_block_in_loop) { | |
| // Update all try entries. | |
| DCHECK(try_entry->EndsWithTryBoundary()); | |
| DCHECK(try_entry->GetLastInstruction()->AsTryBoundary()->IsEntry()); | |
| DisconnectHandlersAndUpdateTryBoundary(try_entry, any_block_in_loop); | |
| const ArenaVector<HBasicBlock*>& blocks = graph_->GetBlocks(); | |
| for (uint32_t i : try_belonging_info.coalesced_try_entries.Indexes()) { | |
| HBasicBlock* other_try_entry = blocks[i]; | |
| DCHECK(other_try_entry->EndsWithTryBoundary()); | |
| DCHECK(other_try_entry->GetLastInstruction()->AsTryBoundary()->IsEntry()); | |
| DisconnectHandlersAndUpdateTryBoundary(other_try_entry, any_block_in_loop); | |
| } | |
| // Update the blocks in the try. | |
| for (uint32_t i : try_belonging_info.blocks_in_try.Indexes()) { | |
| HBasicBlock* block = blocks[i]; | |
| // Update the try catch information since now the try doesn't exist. | |
| block->SetTryCatchInformation(nullptr); | |
| if (block->IsInLoop()) { | |
| *any_block_in_loop = true; | |
| } | |
| if (block->EndsWithTryBoundary()) { | |
| // Try exits. | |
| DCHECK(!block->GetLastInstruction()->AsTryBoundary()->IsEntry()); | |
| DisconnectHandlersAndUpdateTryBoundary(block, any_block_in_loop); | |
| if (block->GetSingleSuccessor()->IsExitBlock()) { | |
| // `block` used to be a single exit TryBoundary that got turned into a Goto. It | |
| // is now pointing to the exit which we don't allow. To fix it, we disconnect | |
| // `block` from its predecessor and RemoveDeadBlocks will remove it from the | |
| // graph. | |
| DCHECK(block->IsSingleGoto()); | |
| HBasicBlock* predecessor = block->GetSinglePredecessor(); | |
| predecessor->ReplaceSuccessor(block, graph_->GetExitBlock()); | |
| if (!block->GetDominatedBlocks().empty()) { | |
| // Update domination tree if `block` dominates a block to keep the graph consistent. | |
| DCHECK_EQ(block->GetDominatedBlocks().size(), 1u); | |
| DCHECK_EQ(graph_->GetExitBlock()->GetDominator(), block); | |
| predecessor->AddDominatedBlock(graph_->GetExitBlock()); | |
| graph_->GetExitBlock()->SetDominator(predecessor); | |
| block->RemoveDominatedBlock(graph_->GetExitBlock()); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| bool HDeadCodeElimination::RemoveUnneededTries() { | |
| if (!graph_->HasTryCatch()) { | |
| return false; | |
| } | |
| // Use local allocator for allocating memory. | |
| ScopedArenaAllocator allocator(graph_->GetArenaStack()); | |
| // Collect which blocks are part of which try. | |
| ScopedArenaUnorderedMap<HBasicBlock*, TryBelongingInformation> tries( | |
| allocator.Adapter(kArenaAllocDCE)); | |
| for (HBasicBlock* block : graph_->GetReversePostOrderSkipEntryBlock()) { | |
| if (block->IsTryBlock()) { | |
| HBasicBlock* key = block->GetTryCatchInformation()->GetTryEntry().GetBlock(); | |
| auto it = tries.find(key); | |
| if (it == tries.end()) { | |
| it = tries.insert({key, TryBelongingInformation(graph_, &allocator)}).first; | |
| } | |
| it->second.blocks_in_try.SetBit(block->GetBlockId()); | |
| } | |
| } | |
| // Deduplicate the tries which have different try entries but they are really the same try. | |
| for (auto it = tries.begin(); it != tries.end(); it++) { | |
| HBasicBlock* block = it->first; | |
| DCHECK(block->EndsWithTryBoundary()); | |
| HTryBoundary* try_boundary = block->GetLastInstruction()->AsTryBoundary(); | |
| for (auto other_it = next(it); other_it != tries.end(); /*other_it++ in the loop*/) { | |
| HBasicBlock* other_block = other_it->first; | |
| DCHECK(other_block->EndsWithTryBoundary()); | |
| HTryBoundary* other_try_boundary = other_block->GetLastInstruction()->AsTryBoundary(); | |
| if (try_boundary->HasSameExceptionHandlersAs(*other_try_boundary)) { | |
| // Merge the entries as they are really the same one. | |
| // Block merging. | |
| it->second.blocks_in_try.Union(other_it->second.blocks_in_try); | |
| // Add the coalesced try entry to update it too. | |
| it->second.coalesced_try_entries.SetBit(other_block->GetBlockId()); | |
| // Erase the other entry. | |
| other_it = tries.erase(other_it); | |
| } else { | |
| other_it++; | |
| } | |
| } | |
| } | |
| size_t removed_tries = 0; | |
| bool any_block_in_loop = false; | |
| // Check which tries contain throwing instructions. | |
| for (const auto& entry : tries) { | |
| if (CanPerformTryRemoval(entry.second)) { | |
| ++removed_tries; | |
| RemoveTry(entry.first, entry.second, &any_block_in_loop); | |
| } | |
| } | |
| if (removed_tries != 0) { | |
| // We want to: | |
| // 1) Update the dominance information | |
| // 2) Remove catch block subtrees, if they are now unreachable. | |
| // If we run the dominance recomputation without removing the code, those catch blocks will | |
| // not be part of the post order and won't be removed. If we don't run the dominance | |
| // recomputation, we risk RemoveDeadBlocks not running it and leaving the graph in an | |
| // inconsistent state. So, what we can do is run RemoveDeadBlocks and force a recomputation. | |
| // Note that we are not guaranteed to remove a catch block if we have nested try blocks: | |
| // | |
| // try { | |
| // ... nothing can throw. TryBoundary A ... | |
| // try { | |
| // ... can throw. TryBoundary B... | |
| // } catch (Error e) {} | |
| // } catch (Exception e) {} | |
| // | |
| // In the example above, we can remove the TryBoundary A but the Exception catch cannot be | |
| // removed as the TryBoundary B might still throw into that catch. TryBoundary A and B don't get | |
| // coalesced since they have different catch handlers. | |
| RemoveDeadBlocks(/* force_recomputation= */ true, any_block_in_loop); | |
| MaybeRecordStat(stats_, MethodCompilationStat::kRemovedTry, removed_tries); | |
| return true; | |
| } else { | |
| return false; | |
| } | |
| } | |
| bool HDeadCodeElimination::RemoveEmptyIfs() { | |
| bool did_opt = false; | |
| for (HBasicBlock* block : graph_->GetPostOrder()) { | |
| if (!block->EndsWithIf()) { | |
| continue; | |
| } | |
| HIf* if_instr = block->GetLastInstruction()->AsIf(); | |
| HBasicBlock* true_block = if_instr->IfTrueSuccessor(); | |
| HBasicBlock* false_block = if_instr->IfFalseSuccessor(); | |
| // We can use `visited_blocks` to detect cases like | |
| // 1 | |
| // / \ | |
| // 2 3 | |
| // \ / | |
| // 4 ... | |
| // | / | |
| // 5 | |
| // where 2, 3, and 4 are single HGoto blocks, and block 5 has Phis. | |
| ScopedArenaAllocator allocator(graph_->GetArenaStack()); | |
| BitVectorView<size_t> visited_blocks = | |
| ArenaBitVector::CreateFixedSize(&allocator, graph_->GetBlocks().size(), kArenaAllocDCE); | |
| HBasicBlock* merge_true = true_block; | |
| visited_blocks.SetBit(merge_true->GetBlockId()); | |
| while (merge_true->IsSingleGoto()) { | |
| merge_true = merge_true->GetSuccessors()[0]; | |
| visited_blocks.SetBit(merge_true->GetBlockId()); | |
| } | |
| HBasicBlock* merge_false = false_block; | |
| while (!visited_blocks.IsBitSet(merge_false->GetBlockId()) && merge_false->IsSingleGoto()) { | |
| merge_false = merge_false->GetSuccessors()[0]; | |
| } | |
| if (!visited_blocks.IsBitSet(merge_false->GetBlockId()) || !merge_false->GetPhis().IsEmpty()) { | |
| // TODO(solanes): We could allow Phis iff both branches have the same value for all Phis. This | |
| // may not be covered by SsaRedundantPhiElimination in cases like `HPhi[A,A,B]` where the Phi | |
| // itself is not redundant for the general case but it is for a pair of branches. | |
| continue; | |
| } | |
| // Data structures to help remove now-dead instructions. | |
| ScopedArenaQueue<HInstruction*> maybe_remove(allocator.Adapter(kArenaAllocDCE)); | |
| BitVectorView<size_t> visited = ArenaBitVector::CreateFixedSize( | |
| &allocator, graph_->GetCurrentInstructionId(), kArenaAllocDCE); | |
| maybe_remove.push(if_instr->InputAt(0)); | |
| visited.SetBit(if_instr->GetId()); | |
| // Swap HIf with HGoto | |
| block->ReplaceAndRemoveInstructionWith( | |
| if_instr, new (graph_->GetAllocator()) HGoto(if_instr->GetDexPc())); | |
| // Reconnect blocks | |
| block->RemoveSuccessor(true_block); | |
| block->RemoveSuccessor(false_block); | |
| true_block->RemovePredecessor(block); | |
| false_block->RemovePredecessor(block); | |
| block->AddSuccessor(merge_false); | |
| // Remove now dead instructions e.g. comparisons that are only used as input to the if | |
| // instruction. This can allow for further removal of other empty ifs. | |
| while (!maybe_remove.empty()) { | |
| HInstruction* instr = maybe_remove.front(); | |
| maybe_remove.pop(); | |
| if (instr->IsDeadAndRemovable()) { | |
| for (HInstruction* input : instr->GetInputs()) { | |
| if (visited.IsBitSet(input->GetId())) { | |
| continue; | |
| } | |
| visited.SetBit(input->GetId()); | |
| maybe_remove.push(input); | |
| } | |
| instr->GetBlock()->RemoveInstructionOrPhi(instr); | |
| MaybeRecordStat(stats_, MethodCompilationStat::kRemovedDeadInstruction); | |
| } | |
| } | |
| did_opt = true; | |
| } | |
| if (did_opt) { | |
| graph_->RecomputeDominatorTree(); | |
| } | |
| return did_opt; | |
| } | |
| bool HDeadCodeElimination::RemoveDeadBlocks(bool force_recomputation, | |
| bool force_loop_recomputation) { | |
| DCHECK_IMPLIES(force_loop_recomputation, force_recomputation); | |
| // Use local allocator for allocating memory. | |
| ScopedArenaAllocator allocator(graph_->GetArenaStack()); | |
| // Classify blocks as reachable/unreachable. | |
| BitVectorView<size_t> live_blocks = | |
| ArenaBitVector::CreateFixedSize(&allocator, graph_->GetBlocks().size(), kArenaAllocDCE); | |
| MarkReachableBlocks(graph_, live_blocks); | |
| bool removed_one_or_more_blocks = false; | |
| bool rerun_dominance_and_loop_analysis = false; | |
| // Remove all dead blocks. Iterate in post order because removal needs the | |
| // block's chain of dominators and nested loops need to be updated from the | |
| // inside out. | |
| for (HBasicBlock* block : graph_->GetPostOrder()) { | |
| int id = block->GetBlockId(); | |
| if (!live_blocks.IsBitSet(id)) { | |
| MaybeRecordDeadBlock(block); | |
| block->DisconnectAndDelete(); | |
| removed_one_or_more_blocks = true; | |
| if (block->IsInLoop()) { | |
| rerun_dominance_and_loop_analysis = true; | |
| } | |
| } | |
| } | |
| // If we removed at least one block, we need to recompute the full | |
| // dominator tree and try block membership. | |
| if (removed_one_or_more_blocks || force_recomputation) { | |
| if (rerun_dominance_and_loop_analysis || force_loop_recomputation) { | |
| graph_->RecomputeDominatorTree(); | |
| } else { | |
| graph_->ClearDominanceInformation(); | |
| graph_->ComputeDominanceInformation(); | |
| graph_->ComputeTryBlockInformation(); | |
| } | |
| } | |
| return removed_one_or_more_blocks; | |
| } | |
| void HDeadCodeElimination::RemoveDeadInstructions() { | |
| // Process basic blocks in post-order in the dominator tree, so that | |
| // a dead instruction depending on another dead instruction is removed. | |
| for (HBasicBlock* block : graph_->GetPostOrder()) { | |
| // Traverse this block's instructions in backward order and remove | |
| // the unused ones. | |
| HBackwardInstructionIterator i(block->GetInstructions()); | |
| // Skip the first iteration, as the last instruction of a block is | |
| // a branching instruction. | |
| DCHECK(i.Current()->IsControlFlow()); | |
| for (i.Advance(); !i.Done(); i.Advance()) { | |
| HInstruction* inst = i.Current(); | |
| DCHECK(!inst->IsControlFlow()); | |
| if (inst->IsDeadAndRemovable()) { | |
| block->RemoveInstruction(inst); | |
| MaybeRecordStat(stats_, MethodCompilationStat::kRemovedDeadInstruction); | |
| } | |
| } | |
| // Same for Phis. | |
| for (HBackwardInstructionIterator phi_it(block->GetPhis()); !phi_it.Done(); phi_it.Advance()) { | |
| DCHECK(phi_it.Current()->IsPhi()); | |
| HPhi* phi = phi_it.Current()->AsPhi(); | |
| if (phi->IsPhiDeadAndRemovable()) { | |
| block->RemovePhi(phi); | |
| MaybeRecordStat(stats_, MethodCompilationStat::kRemovedDeadPhi); | |
| } | |
| } | |
| } | |
| } | |
| void HDeadCodeElimination::UpdateGraphFlags() { | |
| bool has_monitor_operations = false; | |
| bool has_traditional_simd = false; | |
| bool has_predicated_simd = false; | |
| bool has_bounds_checks = false; | |
| bool has_always_throwing_invokes = false; | |
| for (HBasicBlock* block : graph_->GetReversePostOrder()) { | |
| for (HInstructionIterator it(block->GetInstructions()); !it.Done(); it.Advance()) { | |
| HInstruction* instruction = it.Current(); | |
| if (instruction->IsMonitorOperation()) { | |
| has_monitor_operations = true; | |
| } else if (instruction->IsVecOperation()) { | |
| HVecOperation* vec_instruction = instruction->AsVecOperation(); | |
| if (vec_instruction->IsPredicated()) { | |
| has_predicated_simd = true; | |
| } else { | |
| has_traditional_simd = true; | |
| } | |
| } else if (instruction->IsBoundsCheck()) { | |
| has_bounds_checks = true; | |
| } else if (instruction->IsInvoke() && instruction->AsInvoke()->AlwaysThrows()) { | |
| has_always_throwing_invokes = true; | |
| } | |
| } | |
| } | |
| graph_->SetHasMonitorOperations(has_monitor_operations); | |
| graph_->SetHasTraditionalSIMD(has_traditional_simd); | |
| graph_->SetHasPredicatedSIMD(has_predicated_simd); | |
| graph_->SetHasBoundsChecks(has_bounds_checks); | |
| graph_->SetHasAlwaysThrowingInvokes(has_always_throwing_invokes); | |
| } | |
| bool HDeadCodeElimination::Run() { | |
| // Do not eliminate dead blocks if the graph has irreducible loops. We could | |
| // support it, but that would require changes in our loop representation to handle | |
| // multiple entry points. We decided it was not worth the complexity. | |
| if (!graph_->HasIrreducibleLoops()) { | |
| // Simplify graph to generate more dead block patterns. | |
| ConnectSuccessiveBlocks(); | |
| bool did_any_simplification = false; | |
| did_any_simplification |= SimplifyAlwaysThrows(); | |
| did_any_simplification |= SimplifyIfs(); | |
| did_any_simplification |= RemoveEmptyIfs(); | |
| did_any_simplification |= RemoveDeadBlocks(); | |
| // We call RemoveDeadBlocks before RemoveUnneededTries to remove the dead blocks from the | |
| // previous optimizations. Otherwise, we might detect that a try has throwing instructions but | |
| // they are actually dead code. RemoveUnneededTryBoundary will call RemoveDeadBlocks again if | |
| // needed. | |
| did_any_simplification |= RemoveUnneededTries(); | |
| if (did_any_simplification) { | |
| // Connect successive blocks created by dead branches. | |
| ConnectSuccessiveBlocks(); | |
| } | |
| } | |
| SsaRedundantPhiElimination(graph_).Run(); | |
| RemoveDeadInstructions(); | |
| UpdateGraphFlags(); | |
| return true; | |
| } | |
| } // namespace art |