| /* |
| * 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 "base/array_ref.h" |
| #include "base/bit_vector-inl.h" |
| #include "base/scoped_arena_allocator.h" |
| #include "base/scoped_arena_containers.h" |
| #include "base/stl_util.h" |
| #include "ssa_phi_elimination.h" |
| |
| namespace art { |
| |
| static void MarkReachableBlocks(HGraph* graph, ArenaBitVector* 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; |
| } |
| // 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) { |
| ReferenceTypeInfo ti = obj->GetReferenceTypeInfo(); |
| bound = new (obj->GetBlock()->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 |
| // / \ |
| // | foo() // always throws |
| // \ goto B2 |
| // \ / |
| // B2 |
| // |
| // Into: |
| // |
| // B1 |
| // / \ |
| // | foo() |
| // | goto Exit |
| // | | |
| // B2 Exit |
| // |
| // Rationale: |
| // Removal of the never taken edge to B2 may expose |
| // other optimization opportunities, such as code sinking. |
| bool HDeadCodeElimination::SimplifyAlwaysThrows() { |
| // Make sure exceptions go to exit. |
| if (graph_->HasTryCatch()) { |
| return false; |
| } |
| HBasicBlock* exit = graph_->GetExitBlock(); |
| if (exit == nullptr) { |
| return false; |
| } |
| |
| bool rerun_dominance_and_loop_analysis = false; |
| |
| // Order does not matter, just pick one. |
| for (HBasicBlock* block : graph_->GetReversePostOrder()) { |
| HInstruction* first = block->GetFirstInstruction(); |
| HInstruction* last = block->GetLastInstruction(); |
| // Ensure only one throwing instruction appears before goto. |
| if (first->AlwaysThrows() && |
| first->GetNext() == last && |
| last->IsGoto() && |
| block->GetPhis().IsEmpty() && |
| block->GetPredecessors().size() == 1u) { |
| DCHECK_EQ(block->GetSuccessors().size(), 1u); |
| HBasicBlock* pred = block->GetSinglePredecessor(); |
| HBasicBlock* succ = block->GetSingleSuccessor(); |
| // Ensure no computations are merged through throwing block. |
| // This does not prevent the optimization per se, but would |
| // require an elaborate clean up of the SSA graph. |
| if (succ != exit && |
| !block->Dominates(pred) && |
| pred->Dominates(succ) && |
| succ->GetPredecessors().size() > 1u && |
| succ->GetPhis().IsEmpty()) { |
| block->ReplaceSuccessor(succ, 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 (RemoveNonNullControlDependences(pred, 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_->ClearLoopInformation(); |
| graph_->ClearDominanceInformation(); |
| graph_->BuildDominatorTree(); |
| return true; |
| } |
| return false; |
| } |
| |
| // Simplify the pattern: |
| // |
| // B1 B2 ... |
| // goto goto goto |
| // \ | / |
| // \ | / |
| // B3 |
| // i1 = phi(input, input) |
| // (i2 = condition on i1) |
| // if i1 (or i2) |
| // / \ |
| // / \ |
| // B4 B5 |
| // |
| // Into: |
| // |
| // B1 B2 ... |
| // | | | |
| // B4 B5 B? |
| // |
| // Note that individual edges can be redirected (for example B2->B3 |
| // can be redirected as B2->B5) without applying this optimization |
| // to other incoming edges. |
| // |
| // 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. |
| // |
| // We do not apply this optimization to loop headers as this could |
| // create irreducible loops. We rely on the suspend check in the |
| // loop header to prevent the pattern match. |
| // |
| // Note that we rely on the dead code elimination to get rid of B3. |
| bool HDeadCodeElimination::SimplifyIfs() { |
| bool simplified_one_or_more_ifs = false; |
| bool rerun_dominance_and_loop_analysis = false; |
| |
| for (HBasicBlock* block : graph_->GetReversePostOrder()) { |
| HInstruction* last = block->GetLastInstruction(); |
| HInstruction* first = block->GetFirstInstruction(); |
| if (!block->IsCatchBlock() && |
| last->IsIf() && |
| block->HasSinglePhi() && |
| block->GetFirstPhi()->HasOnlyOneNonEnvironmentUse()) { |
| 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) { |
| DCHECK(!block->IsLoopHeader()); |
| 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_->ClearLoopInformation(); |
| graph_->ClearDominanceInformation(); |
| graph_->BuildDominatorTree(); |
| } else { |
| graph_->ClearDominanceInformation(); |
| // We have introduced critical edges, remove them. |
| graph_->SimplifyCFG(); |
| graph_->ComputeDominanceInformation(); |
| graph_->ComputeTryBlockInformation(); |
| } |
| } |
| |
| return simplified_one_or_more_ifs; |
| } |
| |
| 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. |
| } |
| } |
| } |
| |
| bool HDeadCodeElimination::RemoveDeadBlocks() { |
| // Use local allocator for allocating memory. |
| ScopedArenaAllocator allocator(graph_->GetArenaStack()); |
| |
| // Classify blocks as reachable/unreachable. |
| ArenaBitVector live_blocks(&allocator, graph_->GetBlocks().size(), false, kArenaAllocDCE); |
| live_blocks.ClearAllBits(); |
| |
| 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) { |
| if (rerun_dominance_and_loop_analysis) { |
| graph_->ClearLoopInformation(); |
| graph_->ClearDominanceInformation(); |
| graph_->BuildDominatorTree(); |
| } 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); |
| } |
| } |
| } |
| } |
| |
| void 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 |= RemoveDeadBlocks(); |
| if (did_any_simplification) { |
| // Connect successive blocks created by dead branches. |
| ConnectSuccessiveBlocks(); |
| } |
| } |
| SsaRedundantPhiElimination(graph_).Run(); |
| RemoveDeadInstructions(); |
| } |
| |
| } // namespace art |