/* * 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 "ssa_phi_elimination.h" #include "base/arena_bit_vector.h" #include "base/scoped_arena_allocator.h" #include "base/scoped_arena_containers.h" #include "base/bit_vector-inl.h" namespace art { bool SsaDeadPhiElimination::Run() { MarkDeadPhis(); EliminateDeadPhis(); return true; } void SsaDeadPhiElimination::MarkDeadPhis() { // Use local allocator for allocating memory used by this optimization. ScopedArenaAllocator allocator(graph_->GetArenaStack()); static constexpr size_t kDefaultWorklistSize = 8; ScopedArenaVector worklist(allocator.Adapter(kArenaAllocSsaPhiElimination)); worklist.reserve(kDefaultWorklistSize); // Phis are constructed live and should not be revived if previously marked // dead. This algorithm temporarily breaks that invariant but we DCHECK that // only phis which were initially live are revived. ScopedArenaSet initially_live(allocator.Adapter(kArenaAllocSsaPhiElimination)); // Add to the worklist phis referenced by non-phi instructions. for (HBasicBlock* block : graph_->GetReversePostOrder()) { for (HInstructionIterator inst_it(block->GetPhis()); !inst_it.Done(); inst_it.Advance()) { HPhi* phi = inst_it.Current()->AsPhi(); if (phi->IsDead()) { continue; } bool keep_alive = (graph_->IsDebuggable() && phi->HasEnvironmentUses()); if (!keep_alive) { for (const HUseListNode& use : phi->GetUses()) { if (!use.GetUser()->IsPhi()) { keep_alive = true; break; } } } if (keep_alive) { worklist.push_back(phi); } else { phi->SetDead(); if (kIsDebugBuild) { initially_live.insert(phi); } } } } // Process the worklist by propagating liveness to phi inputs. while (!worklist.empty()) { HPhi* phi = worklist.back(); worklist.pop_back(); for (HInstruction* raw_input : phi->GetInputs()) { HPhi* input = raw_input->AsPhi(); if (input != nullptr && input->IsDead()) { // Input is a dead phi. Revive it and add to the worklist. We make sure // that the phi was not dead initially (see definition of `initially_live`). DCHECK(ContainsElement(initially_live, input)); input->SetLive(); worklist.push_back(input); } } } } void SsaDeadPhiElimination::EliminateDeadPhis() { // Remove phis that are not live. Visit in post order so that phis // that are not inputs of loop phis can be removed when they have // no users left (dead phis might use dead phis). for (HBasicBlock* block : graph_->GetPostOrder()) { HInstruction* current = block->GetFirstPhi(); HInstruction* next = nullptr; HPhi* phi; while (current != nullptr) { phi = current->AsPhi(); next = current->GetNext(); if (phi->IsDead()) { // Make sure the phi is only used by other dead phis. if (kIsDebugBuild) { for (const HUseListNode& use : phi->GetUses()) { HInstruction* user = use.GetUser(); DCHECK(user->IsLoopHeaderPhi()); DCHECK(user->AsPhi()->IsDead()); } } // Remove the phi from use lists of its inputs. phi->RemoveAsUserOfAllInputs(); // Remove the phi from environments that use it. for (const HUseListNode& use : phi->GetEnvUses()) { HEnvironment* user = use.GetUser(); user->SetRawEnvAt(use.GetIndex(), nullptr); } // Delete it from the instruction list. block->RemovePhi(phi, /*ensure_safety=*/ false); } current = next; } } } bool SsaRedundantPhiElimination::Run() { // Use local allocator for allocating memory used by this optimization. ScopedArenaAllocator allocator(graph_->GetArenaStack()); static constexpr size_t kDefaultWorklistSize = 8; ScopedArenaVector worklist(allocator.Adapter(kArenaAllocSsaPhiElimination)); worklist.reserve(kDefaultWorklistSize); // Add all phis in the worklist. Order does not matter for correctness, and // neither will necessarily converge faster. for (HBasicBlock* block : graph_->GetReversePostOrder()) { for (HInstructionIterator inst_it(block->GetPhis()); !inst_it.Done(); inst_it.Advance()) { worklist.push_back(inst_it.Current()->AsPhi()); } } ArenaBitVector visited_phis_in_cycle(&allocator, graph_->GetCurrentInstructionId(), /* expandable= */ false, kArenaAllocSsaPhiElimination); visited_phis_in_cycle.ClearAllBits(); ScopedArenaVector cycle_worklist(allocator.Adapter(kArenaAllocSsaPhiElimination)); while (!worklist.empty()) { HPhi* phi = worklist.back(); worklist.pop_back(); // If the phi has already been processed, continue. if (!phi->IsInBlock()) { continue; } // If the phi is dead, we know we won't revive it and it will be removed, // so don't process it. if (phi->IsDead()) { continue; } HInstruction* candidate = nullptr; visited_phis_in_cycle.ClearAllBits(); cycle_worklist.clear(); cycle_worklist.push_back(phi); visited_phis_in_cycle.SetBit(phi->GetId()); bool catch_phi_in_cycle = phi->IsCatchPhi(); bool irreducible_loop_phi_in_cycle = phi->IsIrreducibleLoopHeaderPhi(); // First do a simple loop over inputs and check if they are all the same. for (HInstruction* input : phi->GetInputs()) { if (input == phi) { continue; } else if (candidate == nullptr) { candidate = input; } else if (candidate != input) { candidate = nullptr; break; } } // If we haven't found a candidate, check for a phi cycle. Note that we need to detect // such cycles to avoid having reference and non-reference equivalents. We check this // invariant in the graph checker. if (candidate == nullptr) { // We iterate over the array as long as it grows. for (size_t i = 0; i < cycle_worklist.size(); ++i) { HPhi* current = cycle_worklist[i]; DCHECK(!current->IsLoopHeaderPhi() || current->GetBlock()->IsLoopPreHeaderFirstPredecessor()); for (HInstruction* input : current->GetInputs()) { if (input == current) { continue; } else if (input->IsPhi()) { if (!visited_phis_in_cycle.IsBitSet(input->GetId())) { cycle_worklist.push_back(input->AsPhi()); visited_phis_in_cycle.SetBit(input->GetId()); catch_phi_in_cycle |= input->AsPhi()->IsCatchPhi(); irreducible_loop_phi_in_cycle |= input->IsIrreducibleLoopHeaderPhi(); } else { // Already visited, nothing to do. } } else if (candidate == nullptr) { candidate = input; } else if (candidate != input) { candidate = nullptr; // Clear the cycle worklist to break out of the outer loop. cycle_worklist.clear(); break; } } } } if (candidate == nullptr) { continue; } if (irreducible_loop_phi_in_cycle && !candidate->IsConstant()) { // For irreducible loops, we need to keep the phis to satisfy our linear scan // algorithm. // There is one exception for constants, as the type propagation requires redundant // cyclic phis of a constant to be removed. This is ok for the linear scan as it // has to deal with constants anyway, and they can trivially be rematerialized. continue; } for (HPhi* current : cycle_worklist) { // The candidate may not dominate a phi in a catch block: there may be non-throwing // instructions at the beginning of a try range, that may be the first input of // catch phis. // TODO(dbrazdil): Remove this situation by moving those non-throwing instructions // before the try entry. if (catch_phi_in_cycle) { if (!candidate->StrictlyDominates(current)) { continue; } } else { DCHECK(candidate->StrictlyDominates(current)); } // Because we're updating the users of this phi, we may have new candidates // for elimination. Add phis that use this phi to the worklist. for (const HUseListNode& use : current->GetUses()) { HInstruction* user = use.GetUser(); if (user->IsPhi() && !visited_phis_in_cycle.IsBitSet(user->GetId())) { worklist.push_back(user->AsPhi()); } } DCHECK(candidate->StrictlyDominates(current)); current->ReplaceWith(candidate); current->GetBlock()->RemovePhi(current); } } return true; } } // namespace art