summaryrefslogtreecommitdiff
path: root/compiler
diff options
context:
space:
mode:
Diffstat (limited to 'compiler')
-rw-r--r--compiler/optimizing/dead_code_elimination.cc188
-rw-r--r--compiler/optimizing/dead_code_elimination.h20
-rw-r--r--compiler/optimizing/graph_visualizer.cc4
-rw-r--r--compiler/optimizing/nodes.cc6
-rw-r--r--compiler/optimizing/optimizing_compiler_stats.h1
5 files changed, 217 insertions, 2 deletions
diff --git a/compiler/optimizing/dead_code_elimination.cc b/compiler/optimizing/dead_code_elimination.cc
index 909ff838f2..ea914622df 100644
--- a/compiler/optimizing/dead_code_elimination.cc
+++ b/compiler/optimizing/dead_code_elimination.cc
@@ -486,6 +486,189 @@ void HDeadCodeElimination::ConnectSuccessiveBlocks() {
}
}
+struct HDeadCodeElimination::TryBelongingInformation {
+ TryBelongingInformation(ScopedArenaAllocator* allocator)
+ : blocks_in_try(allocator->Adapter(kArenaAllocDCE)),
+ coalesced_try_entries(allocator->Adapter(kArenaAllocDCE)) {}
+
+ // Which blocks belong in the try.
+ ScopedArenaSet<HBasicBlock*> blocks_in_try;
+ // Which other try entries are referencing this same try.
+ ScopedArenaSet<HBasicBlock*> coalesced_try_entries;
+};
+
+bool HDeadCodeElimination::CanPerformTryRemoval(const TryBelongingInformation& try_belonging_info) {
+ for (HBasicBlock* block : try_belonging_info.blocks_in_try) {
+ for (HInstructionIterator it(block->GetInstructions()); !it.Done(); it.Advance()) {
+ if (it.Current()->CanThrow()) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+void HDeadCodeElimination::DisconnectHandlersAndUpdateTryBoundary(
+ HBasicBlock* block,
+ /* out */ bool* any_handler_in_loop) {
+ // 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_handler_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_handler_in_loop) {
+ // Update all try entries.
+ DCHECK(try_entry->EndsWithTryBoundary());
+ DCHECK(try_entry->GetLastInstruction()->AsTryBoundary()->IsEntry());
+ DisconnectHandlersAndUpdateTryBoundary(try_entry, any_handler_in_loop);
+
+ for (HBasicBlock* other_try_entry : try_belonging_info.coalesced_try_entries) {
+ DCHECK(other_try_entry->EndsWithTryBoundary());
+ DCHECK(other_try_entry->GetLastInstruction()->AsTryBoundary()->IsEntry());
+ DisconnectHandlersAndUpdateTryBoundary(other_try_entry, any_handler_in_loop);
+ }
+
+ // Update the blocks in the try.
+ for (HBasicBlock* block : try_belonging_info.blocks_in_try) {
+ // Update the try catch information since now the try doesn't exist.
+ block->SetTryCatchInformation(nullptr);
+
+ if (block->EndsWithTryBoundary()) {
+ // Try exits.
+ DCHECK(!block->GetLastInstruction()->AsTryBoundary()->IsEntry());
+ DisconnectHandlersAndUpdateTryBoundary(block, any_handler_in_loop);
+
+ if (block->GetSingleSuccessor()->IsExitBlock()) {
+ // `predecessor` 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
+ // `predecessor` from its predecessor and RemoveDeadBlocks will remove it from the
+ // graph.
+ DCHECK(block->IsSingleGoto());
+ HBasicBlock* predecessor = block->GetSinglePredecessor();
+ predecessor->ReplaceSuccessor(block, 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.
+ std::unordered_map<HBasicBlock*, TryBelongingInformation> tries;
+ 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(&allocator)}).first;
+ }
+ it->second.blocks_in_try.insert(block);
+ }
+ }
+
+ // Deduplicate the tries which have different try entries but they are really the same try.
+ for (auto it = tries.begin(); it != tries.end(); it++) {
+ DCHECK(it->first->EndsWithTryBoundary());
+ HTryBoundary* try_boundary = it->first->GetLastInstruction()->AsTryBoundary();
+ for (auto other_it = next(it); other_it != tries.end(); /*other_it++ in the loop*/) {
+ DCHECK(other_it->first->EndsWithTryBoundary());
+ HTryBoundary* other_try_boundary = other_it->first->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.insert(other_it->second.blocks_in_try.begin(),
+ other_it->second.blocks_in_try.end());
+
+ // Add the coalesced try entry to update it too.
+ it->second.coalesced_try_entries.insert(other_it->first);
+
+ // Erase the other entry.
+ other_it = tries.erase(other_it);
+ } else {
+ other_it++;
+ }
+ }
+ }
+
+ const size_t total_tries = tries.size();
+ size_t removed_tries = 0;
+ bool any_handler_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_handler_in_loop);
+ }
+ }
+
+ if (removed_tries == total_tries) {
+ graph_->SetHasTryCatch(false);
+ }
+
+ 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 if it didn't remove any
+ // block we trigger 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.
+
+ if (!RemoveDeadBlocks()) {
+ // If the catches that we modified were in a loop, we have to update the loop information.
+ if (any_handler_in_loop) {
+ graph_->ClearLoopInformation();
+ graph_->ClearDominanceInformation();
+ graph_->BuildDominatorTree();
+ } else {
+ graph_->ClearDominanceInformation();
+ graph_->ComputeDominanceInformation();
+ graph_->ComputeTryBlockInformation();
+ }
+ }
+ MaybeRecordStat(stats_, MethodCompilationStat::kRemovedTry, removed_tries);
+ return true;
+ } else {
+ return false;
+ }
+}
+
bool HDeadCodeElimination::RemoveDeadBlocks() {
// Use local allocator for allocating memory.
ScopedArenaAllocator allocator(graph_->GetArenaStack());
@@ -561,6 +744,11 @@ bool HDeadCodeElimination::Run() {
did_any_simplification |= SimplifyAlwaysThrows();
did_any_simplification |= SimplifyIfs();
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();
diff --git a/compiler/optimizing/dead_code_elimination.h b/compiler/optimizing/dead_code_elimination.h
index a3e993c1d7..873e13f244 100644
--- a/compiler/optimizing/dead_code_elimination.h
+++ b/compiler/optimizing/dead_code_elimination.h
@@ -46,6 +46,26 @@ class HDeadCodeElimination : public HOptimization {
bool SimplifyIfs();
void ConnectSuccessiveBlocks();
+ // Helper struct to eliminate tries.
+ struct TryBelongingInformation;
+ // Disconnects `block`'s handlers and update its `TryBoundary` instruction to a `Goto`.
+ // Sets `any_handler_in_loop` to true if any handler is currently a loop to later update the loop
+ // information if needed.
+ void DisconnectHandlersAndUpdateTryBoundary(HBasicBlock* block,
+ /* out */ bool* any_handler_in_loop);
+ // Returns true iff the try doesn't contain throwing instructions.
+ bool CanPerformTryRemoval(const TryBelongingInformation& try_belonging_info);
+ // Removes the try by disconnecting all try entries and exits from their handlers. Also updates
+ // the graph in the case that a `TryBoundary` instruction of kind `exit` has the Exit block as
+ // its successor.
+ void RemoveTry(HBasicBlock* try_entry,
+ const TryBelongingInformation& try_belonging_info,
+ bool* any_catch_in_loop);
+ // Checks which tries (if any) are currently in the graph, coalesces the different try entries
+ // that are referencing the same try, and removes the tries which don't contain any throwing
+ // instructions.
+ bool RemoveUnneededTries();
+
DISALLOW_COPY_AND_ASSIGN(HDeadCodeElimination);
};
diff --git a/compiler/optimizing/graph_visualizer.cc b/compiler/optimizing/graph_visualizer.cc
index e1199dfb72..2de9446c78 100644
--- a/compiler/optimizing/graph_visualizer.cc
+++ b/compiler/optimizing/graph_visualizer.cc
@@ -909,6 +909,10 @@ class HGraphVisualizerPrinter : public HGraphDelegateVisitor {
PrintEmptyProperty("flags");
}
+ if (block->IsTryBlock()) {
+ PrintProperty("try_start", block->GetTryCatchInformation()->GetTryEntry().GetBlock());
+ }
+
if (block->GetDominator() != nullptr) {
PrintProperty("dominator", block->GetDominator());
}
diff --git a/compiler/optimizing/nodes.cc b/compiler/optimizing/nodes.cc
index d40ce7a717..841576a5e5 100644
--- a/compiler/optimizing/nodes.cc
+++ b/compiler/optimizing/nodes.cc
@@ -2885,7 +2885,10 @@ HInstruction* HGraph::InlineInto(HGraph* outer_graph, HInvoke* invoke) {
// At this point we might either have:
// A) Return/ReturnVoid/Throw as the last instruction
// B) `Return/ReturnVoid->TryBoundary->Goto` as the last instruction chain
- // C) `Throw->TryBoundary` as the last instruction chain
+ // C) `Return/ReturnVoid->Goto` as the last instruction chain. This exists when we added the
+ // extra Goto because we had a TryBoundary which we could eliminate in DCE after
+ // substituting arguments.
+ // D) `Throw->TryBoundary` as the last instruction chain
const bool saw_goto = last->IsGoto();
if (saw_goto) {
@@ -2903,7 +2906,6 @@ HInstruction* HGraph::InlineInto(HGraph* outer_graph, HInvoke* invoke) {
}
// Check that if we have an instruction chain, it is one of the allowed ones.
- DCHECK_IMPLIES(saw_goto, saw_try_boundary);
DCHECK_IMPLIES(saw_goto, last->IsReturnVoid() || last->IsReturn());
if (last->IsThrow()) {
diff --git a/compiler/optimizing/optimizing_compiler_stats.h b/compiler/optimizing/optimizing_compiler_stats.h
index 192519bdd4..02f1fe9d0d 100644
--- a/compiler/optimizing/optimizing_compiler_stats.h
+++ b/compiler/optimizing/optimizing_compiler_stats.h
@@ -47,6 +47,7 @@ enum class MethodCompilationStat {
kUnresolvedFieldNotAFastAccess,
kRemovedCheckedCast,
kRemovedDeadInstruction,
+ kRemovedTry,
kRemovedNullCheck,
kNotCompiledSkipped,
kNotCompiledInvalidBytecode,