/* * 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 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 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(switch_value) - static_cast(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& 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); } } } } 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 |= RemoveDeadBlocks(); if (did_any_simplification) { // Connect successive blocks created by dead branches. ConnectSuccessiveBlocks(); } } SsaRedundantPhiElimination(graph_).Run(); RemoveDeadInstructions(); return true; } } // namespace art