Remove tries which don't contain throwing instructions
If nothing can throw within a TryBoundary, we are safe to eliminate
it. We were already doing this at the builder stage, but this CL
takes care of subsequent passes (e.g. we might remove DivZeroCheck
instructions which means that now we know we can't throw).
Sometimes this means we are able to eliminate catch blocks which
brings some code size improvements.
Locally on a Pixel 5 compiling with `speed`:
* AGSA -684K (0.2%)
* services.jar -100K (0.2%)
* SystemUIGoogle -88K (0.3%)
Bug: 229249867
Test: art/test/testrunner/testrunner.py --host --64 --optimizing -b
Change-Id: I36d5880be99c1f1109c94266b1be583de8d6cf72
diff --git a/compiler/optimizing/dead_code_elimination.cc b/compiler/optimizing/dead_code_elimination.cc
index 909ff83..ea91462 100644
--- a/compiler/optimizing/dead_code_elimination.cc
+++ b/compiler/optimizing/dead_code_elimination.cc
@@ -486,6 +486,189 @@
}
}
+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 @@
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 a3e993c..873e13f 100644
--- a/compiler/optimizing/dead_code_elimination.h
+++ b/compiler/optimizing/dead_code_elimination.h
@@ -46,6 +46,26 @@
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 e1199df..2de9446 100644
--- a/compiler/optimizing/graph_visualizer.cc
+++ b/compiler/optimizing/graph_visualizer.cc
@@ -909,6 +909,10 @@
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 d40ce7a..841576a 100644
--- a/compiler/optimizing/nodes.cc
+++ b/compiler/optimizing/nodes.cc
@@ -2885,7 +2885,10 @@
// 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 @@
}
// 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 192519b..02f1fe9 100644
--- a/compiler/optimizing/optimizing_compiler_stats.h
+++ b/compiler/optimizing/optimizing_compiler_stats.h
@@ -47,6 +47,7 @@
kUnresolvedFieldNotAFastAccess,
kRemovedCheckedCast,
kRemovedDeadInstruction,
+ kRemovedTry,
kRemovedNullCheck,
kNotCompiledSkipped,
kNotCompiledInvalidBytecode,
diff --git a/test/2244-checker-remove-try-boundary/expected-stderr.txt b/test/2244-checker-remove-try-boundary/expected-stderr.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/2244-checker-remove-try-boundary/expected-stderr.txt
diff --git a/test/2244-checker-remove-try-boundary/expected-stdout.txt b/test/2244-checker-remove-try-boundary/expected-stdout.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/2244-checker-remove-try-boundary/expected-stdout.txt
diff --git a/test/2244-checker-remove-try-boundary/info.txt b/test/2244-checker-remove-try-boundary/info.txt
new file mode 100644
index 0000000..4247e14
--- /dev/null
+++ b/test/2244-checker-remove-try-boundary/info.txt
@@ -0,0 +1,2 @@
+Tests that we remove TryBoundary instructions if doesn't contain instructions that can throw.
+Sometimes we can remove the catch blocks too.
diff --git a/test/2244-checker-remove-try-boundary/src/Main.java b/test/2244-checker-remove-try-boundary/src/Main.java
new file mode 100644
index 0000000..efc8ca7
--- /dev/null
+++ b/test/2244-checker-remove-try-boundary/src/Main.java
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+public class Main {
+ public static void main(String[] args) throws Exception {
+ assertEquals(2, $noinline$testDivideOverTen(20));
+ assertEquals(-2, $noinline$testDivideOverTen(-20));
+ assertEquals(0, $noinline$testSimpleDivisionInLoop(0));
+ assertEquals(1, $noinline$testSimpleDivisionInLoop(81));
+ assertEquals(10, $noinline$testOptimizeSeparateBranches(60, true));
+ assertEquals(10, $noinline$testOptimizeSeparateBranches(80, false));
+ assertEquals(1, $noinline$testDoNotOptimizeOneBranchThrows(81, false));
+ assertEquals(-1000, $noinline$testDoNotOptimizeOneBranchThrows(81, true));
+ assertEquals(1, $noinline$testOptimizeAfterOneBranchDisappears(81, false));
+ assertEquals(10, $noinline$testRemoveTryBoundaryNested(60));
+ assertEquals(-2000, $noinline$testRemoveTryBoundaryNestedButNotCatch(60, true));
+ assertEquals(30, $noinline$testRemoveTryBoundaryNestedButNotCatch(60, false));
+ }
+
+ public static void assertEquals(int expected, int result) {
+ if (expected != result) {
+ throw new Error("Expected: " + expected + ", found: " + result);
+ }
+ }
+
+ // Check that this version cannot remove the TryBoundary instructions since we may throw.
+
+ /// CHECK-START: int Main.$inline$division(int, int) register (after)
+ /// CHECK: TryBoundary
+ /// CHECK: TryBoundary
+ /// CHECK-NOT: TryBoundary
+
+ /// CHECK-START: int Main.$inline$division(int, int) register (after)
+ /// CHECK: flags "catch_block"
+ private static int $inline$division(int a, int b) {
+ try {
+ return a / b;
+ } catch (Error unexpected) {
+ return -1000;
+ }
+ }
+
+ // Check that we can remove the TryBoundary afer inlining since we know we can't throw.
+
+ /// CHECK-START: int Main.$noinline$testDivideOverTen(int) inliner (after)
+ /// CHECK-NOT: TryBoundary
+
+ /// CHECK-START: int Main.$noinline$testDivideOverTen(int) inliner (after)
+ /// CHECK-NOT: flags "catch_block"
+ private static int $noinline$testDivideOverTen(int a) {
+ return $inline$division(a, 10);
+ }
+
+ /// CHECK-START: int Main.$noinline$testSimpleDivisionInLoop(int) dead_code_elimination$initial (before)
+ /// CHECK: TryBoundary
+ /// CHECK: TryBoundary
+ /// CHECK-NOT: TryBoundary
+
+ /// CHECK-START: int Main.$noinline$testSimpleDivisionInLoop(int) dead_code_elimination$initial (before)
+ /// CHECK: flags "catch_block"
+
+ /// CHECK-START: int Main.$noinline$testSimpleDivisionInLoop(int) dead_code_elimination$initial (after)
+ /// CHECK-NOT: TryBoundary
+
+ /// CHECK-START: int Main.$noinline$testSimpleDivisionInLoop(int) dead_code_elimination$initial (after)
+ /// CHECK-NOT: flags "catch_block"
+ private static int $noinline$testSimpleDivisionInLoop(int a) {
+ try {
+ for (int i = 0; i < 4; i++) {
+ a /= 3;
+ }
+ } catch (Error unexpected) {
+ return -1000;
+ }
+ return a;
+ }
+
+ // Even though the `TryBoundary`s are split, we can remove them as nothing in the try can throw.
+
+ /// CHECK-START: int Main.$noinline$testOptimizeSeparateBranches(int, boolean) dead_code_elimination$initial (before)
+ /// CHECK: TryBoundary
+ /// CHECK: TryBoundary
+ /// CHECK: TryBoundary
+ /// CHECK-NOT: TryBoundary
+
+ /// CHECK-START: int Main.$noinline$testOptimizeSeparateBranches(int, boolean) dead_code_elimination$initial (before)
+ /// CHECK: flags "catch_block"
+ /// CHECK-NOT: flags "catch_block"
+
+ /// CHECK-START: int Main.$noinline$testOptimizeSeparateBranches(int, boolean) dead_code_elimination$initial (after)
+ /// CHECK-NOT: TryBoundary
+
+ /// CHECK-START: int Main.$noinline$testOptimizeSeparateBranches(int, boolean) dead_code_elimination$initial (after)
+ /// CHECK-NOT: flags "catch_block"
+ private static int $noinline$testOptimizeSeparateBranches(int a, boolean val) {
+ try {
+ if (val) {
+ // TryBoundary kind:entry
+ a /= 3;
+ } else {
+ // TryBoundary kind:entry
+ a /= 4;
+ }
+ a /= 2;
+ // TryBoundary kind:exit
+ } catch (Error unexpected) {
+ return -1000;
+ }
+ return a;
+ }
+
+ // Even though the `a /= 3;` can't throw, we don't eliminate any `TryBoundary` instructions. This
+ // is because we have the `throw new Error();` in the try as well. We could potentially support
+ // removing some `TryBoundary` instructions and not all in the try, but this would complicate the
+ // code and wouldn't bring code size reductions since we would be unable to remove the catch
+ // block.
+
+ /// CHECK-START: int Main.$noinline$testDoNotOptimizeOneBranchThrows(int, boolean) register (after)
+ /// CHECK: TryBoundary
+ /// CHECK: TryBoundary
+ /// CHECK: TryBoundary
+ /// CHECK: TryBoundary
+ /// CHECK-NOT: TryBoundary
+
+ /// CHECK-START: int Main.$noinline$testDoNotOptimizeOneBranchThrows(int, boolean) register (after)
+ /// CHECK: flags "catch_block"
+ public static int $noinline$testDoNotOptimizeOneBranchThrows(int a, boolean val) {
+ try {
+ for (int i = 0; i < 4; i++) {
+ // TryBoundary kind:entry
+ a /= 3;
+ // TryBoundary kind:exit
+ }
+
+ if (val) {
+ // TryBoundary kind:entry
+ throw new Error();
+ // TryBoundary kind:exit
+ }
+ } catch (Error e) {
+ return -1000;
+ }
+ return a;
+ }
+
+ // The throw gets eliminated by `SimplifyIfs` in DCE, so we can detect that nothing can throw in
+ // the graph and eliminate the `TryBoundary` instructions.
+
+ /// CHECK-START: int Main.$noinline$testOptimizeAfterOneBranchDisappears(int, boolean) dead_code_elimination$initial (before)
+ /// CHECK: Throw
+
+ /// CHECK-START: int Main.$noinline$testOptimizeAfterOneBranchDisappears(int, boolean) dead_code_elimination$initial (before)
+ /// CHECK: TryBoundary
+ /// CHECK: TryBoundary
+ /// CHECK: TryBoundary
+ /// CHECK: TryBoundary
+ /// CHECK-NOT: TryBoundary
+
+ /// CHECK-START: int Main.$noinline$testOptimizeAfterOneBranchDisappears(int, boolean) dead_code_elimination$initial (before)
+ /// CHECK: flags "catch_block"
+ /// CHECK-NOT: flags "catch_block"
+
+ /// CHECK-START: int Main.$noinline$testOptimizeAfterOneBranchDisappears(int, boolean) dead_code_elimination$initial (after)
+ /// CHECK-NOT: Throw
+
+ /// CHECK-START: int Main.$noinline$testOptimizeAfterOneBranchDisappears(int, boolean) dead_code_elimination$initial (after)
+ /// CHECK-NOT: TryBoundary
+
+ /// CHECK-START: int Main.$noinline$testOptimizeAfterOneBranchDisappears(int, boolean) dead_code_elimination$initial (after)
+ /// CHECK-NOT: flags "catch_block"
+ public static int $noinline$testOptimizeAfterOneBranchDisappears(int a, boolean val) {
+ try {
+ for (int i = 0; i < 4; i++) {
+ // TryBoundary kind:entry
+ a /= 3;
+ // TryBoundary kind:exit
+ }
+
+ if (val && !val) {
+ // TryBoundary kind:entry
+ throw new Error();
+ // TryBoundary kind:exit
+ }
+ } catch (Error e) {
+ return -1000;
+ }
+ return a;
+ }
+
+ /// CHECK-START: int Main.$noinline$testRemoveTryBoundaryNested(int) dead_code_elimination$initial (before)
+ /// CHECK: TryBoundary
+ /// CHECK: TryBoundary
+ /// CHECK: TryBoundary
+ /// CHECK: TryBoundary
+ /// CHECK-NOT: TryBoundary
+
+ /// CHECK-START: int Main.$noinline$testRemoveTryBoundaryNested(int) dead_code_elimination$initial (before)
+ /// CHECK: flags "catch_block"
+ /// CHECK: flags "catch_block"
+ /// CHECK-NOT: flags "catch_block"
+
+ /// CHECK-START: int Main.$noinline$testRemoveTryBoundaryNested(int) dead_code_elimination$initial (after)
+ /// CHECK-NOT: TryBoundary
+
+ /// CHECK-START: int Main.$noinline$testRemoveTryBoundaryNested(int) dead_code_elimination$initial (after)
+ /// CHECK-NOT: flags "catch_block"
+ public static int $noinline$testRemoveTryBoundaryNested(int a) {
+ try {
+ // TryBoundary kind:entry
+ a /= 2;
+ // TryBoundary kind:exit
+ try {
+ // TryBoundary kind:entry
+ a /= 3;
+ // TryBoundary kind:exit
+ } catch (Error e) {
+ return -2000;
+ }
+ } catch (Exception e) {
+ return -1000;
+ }
+ return a;
+ }
+
+ // We can remove the `TryBoundary` instructions surrounding `a /= 2;` but since the inner try can
+ // throw, we must keep both the inner and outer catches as they are catch handlers of the inner
+ // try.
+
+ /// CHECK-START: int Main.$noinline$testRemoveTryBoundaryNestedButNotCatch(int, boolean) dead_code_elimination$initial (before)
+ /// CHECK: TryBoundary
+ /// CHECK: TryBoundary
+ /// CHECK: TryBoundary
+ /// CHECK: TryBoundary
+ /// CHECK-NOT: TryBoundary
+
+ /// CHECK-START: int Main.$noinline$testRemoveTryBoundaryNestedButNotCatch(int, boolean) dead_code_elimination$initial (before)
+ /// CHECK: flags "catch_block"
+ /// CHECK: flags "catch_block"
+ /// CHECK-NOT: flags "catch_block"
+
+ /// CHECK-START: int Main.$noinline$testRemoveTryBoundaryNestedButNotCatch(int, boolean) dead_code_elimination$initial (after)
+ /// CHECK: TryBoundary
+ /// CHECK: TryBoundary
+ /// CHECK-NOT: TryBoundary
+
+ /// CHECK-START: int Main.$noinline$testRemoveTryBoundaryNestedButNotCatch(int, boolean) dead_code_elimination$initial (after)
+ /// CHECK: flags "catch_block"
+ /// CHECK: flags "catch_block"
+ /// CHECK-NOT: flags "catch_block"
+ public static int $noinline$testRemoveTryBoundaryNestedButNotCatch(int a, boolean val) {
+ try {
+ // TryBoundary kind:entry
+ a /= 2;
+ // TryBoundary kind:exit
+ try {
+ if (val) {
+ // TryBoundary kind:entry
+ throw new Error();
+ // TryBoundary kind:exit
+ }
+ // TryBoundary kind:exit
+ } catch (Error e) {
+ return -2000;
+ }
+ } catch (Exception e) {
+ return -1000;
+ }
+ return a;
+ }
+}
diff --git a/test/639-checker-code-sinking/src/Main.java b/test/639-checker-code-sinking/src/Main.java
index f561718..a42e40d 100644
--- a/test/639-checker-code-sinking/src/Main.java
+++ b/test/639-checker-code-sinking/src/Main.java
@@ -393,45 +393,13 @@
}
private static void testCatchBlock() {
- assertEquals(456, testSinkToCatchBlock());
assertEquals(456, testDoNotSinkToTry());
- assertEquals(456, testDoNotSinkToCatchInsideTry());
assertEquals(456, testSinkWithinTryBlock());
assertEquals(456, testSinkRightBeforeTryBlock());
- assertEquals(456, testSinkToSecondCatch());
assertEquals(456, testDoNotSinkToCatchInsideTryWithMoreThings(false, false));
- assertEquals(456, testSinkToCatchBlockCustomClass());
assertEquals(456, DoNotSinkWithOOMThrow());
}
- /// CHECK-START: int Main.testSinkToCatchBlock() code_sinking (before)
- /// CHECK: <<ObjLoadClass:l\d+>> LoadClass class_name:java.lang.Object
- /// CHECK: NewInstance [<<ObjLoadClass>>]
- /// CHECK: TryBoundary kind:entry
-
- /// CHECK-START: int Main.testSinkToCatchBlock() code_sinking (after)
- /// CHECK: TryBoundary kind:entry
- /// CHECK: <<ObjLoadClass:l\d+>> LoadClass class_name:java.lang.Object
- /// CHECK: NewInstance [<<ObjLoadClass>>]
-
- // Consistency check to make sure there's only one entry TryBoundary.
- /// CHECK-START: int Main.testSinkToCatchBlock() code_sinking (after)
- /// CHECK: TryBoundary kind:entry
- /// CHECK-NOT: TryBoundary kind:entry
-
- // Tests that we can sink the Object creation to the catch block.
- private static int testSinkToCatchBlock() {
- Object o = new Object();
- try {
- if (doEarlyReturn) {
- return 123;
- }
- } catch (Error e) {
- throw new Error(o.toString());
- }
- return 456;
- }
-
/// CHECK-START: int Main.testDoNotSinkToTry() code_sinking (before)
/// CHECK: <<ObjLoadClass:l\d+>> LoadClass class_name:java.lang.Object
/// CHECK: NewInstance [<<ObjLoadClass>>]
@@ -460,41 +428,6 @@
return 456;
}
- /// CHECK-START: int Main.testDoNotSinkToCatchInsideTry() code_sinking (before)
- /// CHECK: <<ObjLoadClass:l\d+>> LoadClass class_name:java.lang.Object
- /// CHECK: NewInstance [<<ObjLoadClass>>]
- /// CHECK: TryBoundary kind:entry
- /// CHECK: TryBoundary kind:entry
-
- /// CHECK-START: int Main.testDoNotSinkToCatchInsideTry() code_sinking (after)
- /// CHECK: <<ObjLoadClass:l\d+>> LoadClass class_name:java.lang.Object
- /// CHECK: NewInstance [<<ObjLoadClass>>]
- /// CHECK: TryBoundary kind:entry
- /// CHECK: TryBoundary kind:entry
-
- // Consistency check to make sure there's exactly two entry TryBoundary.
- /// CHECK-START: int Main.testDoNotSinkToCatchInsideTry() code_sinking (after)
- /// CHECK: TryBoundary kind:entry
- /// CHECK: TryBoundary kind:entry
- /// CHECK-NOT: TryBoundary kind:entry
-
- // Tests that we don't sink the Object creation into a catch handler surrounded by try/catch.
- private static int testDoNotSinkToCatchInsideTry() {
- Object o = new Object();
- try {
- try {
- if (doEarlyReturn) {
- return 123;
- }
- } catch (Error e) {
- throw new Error(o.toString());
- }
- } catch (Error e) {
- throw new Error();
- }
- return 456;
- }
-
/// CHECK-START: int Main.testSinkWithinTryBlock() code_sinking (before)
/// CHECK: <<ObjLoadClass:l\d+>> LoadClass class_name:java.lang.Object
/// CHECK: NewInstance [<<ObjLoadClass>>]
@@ -539,46 +472,6 @@
return 456;
}
- /// CHECK-START: int Main.testSinkToSecondCatch() code_sinking (before)
- /// CHECK: <<ObjLoadClass:l\d+>> LoadClass class_name:java.lang.Object
- /// CHECK: NewInstance [<<ObjLoadClass>>]
- /// CHECK: TryBoundary kind:entry
- /// CHECK: TryBoundary kind:entry
-
- /// CHECK-START: int Main.testSinkToSecondCatch() code_sinking (after)
- /// CHECK: TryBoundary kind:entry
- /// CHECK: TryBoundary kind:entry
- /// CHECK: <<ObjLoadClass:l\d+>> LoadClass class_name:java.lang.Object
- /// CHECK: NewInstance [<<ObjLoadClass>>]
-
- // Consistency check to make sure there's exactly two entry TryBoundary.
- /// CHECK-START: int Main.testSinkToSecondCatch() code_sinking (after)
- /// CHECK: TryBoundary kind:entry
- /// CHECK: TryBoundary kind:entry
- /// CHECK-NOT: TryBoundary kind:entry
- private static int testSinkToSecondCatch() {
- Object o = new Object();
- try {
- if (doEarlyReturn) {
- return 123;
- }
- } catch (Error e) {
- throw new Error();
- }
-
- try {
- // We need a different boolean to the one above, so that the compiler cannot optimize this
- // return away.
- if (doOtherEarlyReturn) {
- return 789;
- }
- } catch (Error e) {
- throw new Error(o.toString());
- }
-
- return 456;
- }
-
/// CHECK-START: int Main.testDoNotSinkToCatchInsideTryWithMoreThings(boolean, boolean) code_sinking (before)
/// CHECK-NOT: TryBoundary kind:entry
/// CHECK: <<ObjLoadClass:l\d+>> LoadClass class_name:java.lang.Object
@@ -617,38 +510,6 @@
int x;
}
- /// CHECK-START: int Main.testSinkToCatchBlockCustomClass() code_sinking (before)
- /// CHECK: <<LoadClass:l\d+>> LoadClass class_name:Main$ObjectWithInt
- /// CHECK: <<Clinit:l\d+>> ClinitCheck [<<LoadClass>>]
- /// CHECK: NewInstance [<<Clinit>>]
- /// CHECK: TryBoundary kind:entry
-
- /// CHECK-START: int Main.testSinkToCatchBlockCustomClass() code_sinking (after)
- /// CHECK: <<LoadClass:l\d+>> LoadClass class_name:Main$ObjectWithInt
- /// CHECK: <<Clinit:l\d+>> ClinitCheck [<<LoadClass>>]
- /// CHECK: TryBoundary kind:entry
- /// CHECK: NewInstance [<<Clinit>>]
-
- // Consistency check to make sure there's only one entry TryBoundary.
- /// CHECK-START: int Main.testSinkToCatchBlockCustomClass() code_sinking (after)
- /// CHECK: TryBoundary kind:entry
- /// CHECK-NOT: TryBoundary kind:entry
-
- // Similar to testSinkToCatchBlock, but using a custom class. CLinit check is not an instruction
- // that we sink since it can throw and it is not in the allow list. We can sink the NewInstance
- // nevertheless.
- private static int testSinkToCatchBlockCustomClass() {
- ObjectWithInt obj = new ObjectWithInt();
- try {
- if (doEarlyReturn) {
- return 123;
- }
- } catch (Error e) {
- throw new Error(Integer.toString(obj.x));
- }
- return 456;
- }
-
/// CHECK-START: int Main.DoNotSinkWithOOMThrow() code_sinking (before)
/// CHECK: <<LoadClass:l\d+>> LoadClass class_name:Main$ObjectWithInt
/// CHECK: <<Clinit:l\d+>> ClinitCheck [<<LoadClass>>]