Compiler implementation of try catch inlining
Notable changes:
1) Wiring of the graph now allows for inlinees graph ending in
TryBoundary, or Goto in some special cases.
2) Building a graph with try catch for inlining may add an extra
Goto block.
3) Oat version bump.
4) Reduced kMaximumNumberOfCumulatedDexRegisters from 32 to 20.
Bug: 227283224
Test: art/test/testrunner/testrunner.py --host --64 --optimizing -b
Change-Id: Ic2fd956de24b72d1de29b4cd3d0b2a1ddab231d8
diff --git a/compiler/optimizing/builder.cc b/compiler/optimizing/builder.cc
index e7826bb..b28c2d9 100644
--- a/compiler/optimizing/builder.cc
+++ b/compiler/optimizing/builder.cc
@@ -96,7 +96,50 @@
return false;
}
-GraphAnalysisResult HGraphBuilder::BuildGraph() {
+static bool NeedsExtraGotoBlock(HBasicBlock* block) {
+ if (!block->IsSingleTryBoundary()) {
+ return false;
+ }
+
+ const HTryBoundary* boundary = block->GetLastInstruction()->AsTryBoundary();
+ DCHECK(boundary->GetNormalFlowSuccessor()->IsExitBlock());
+ DCHECK(!boundary->IsEntry());
+
+ const HInstruction* last_instruction = block->GetSinglePredecessor()->GetLastInstruction();
+ DCHECK(last_instruction->IsReturn() ||
+ last_instruction->IsReturnVoid() ||
+ last_instruction->IsThrow());
+
+ return !last_instruction->IsThrow();
+}
+
+void HGraphBuilder::MaybeAddExtraGotoBlocks() {
+ if (graph_->GetExitBlock() == nullptr) return;
+
+ bool added_block = false;
+ for (size_t pred = 0, size = graph_->GetExitBlock()->GetPredecessors().size(); pred < size;
+ ++pred) {
+ HBasicBlock* predecessor = graph_->GetExitBlock()->GetPredecessors()[pred];
+ if (NeedsExtraGotoBlock(predecessor)) {
+ added_block = true;
+ graph_->SplitEdge(predecessor, graph_->GetExitBlock())
+ ->AddInstruction(new (graph_->GetAllocator()) HGoto(predecessor->GetDexPc()));
+ }
+ }
+
+ // TODO(solanes): Avoid recomputing the full dominator tree by manually updating the relevant
+ // information (loop information, dominance, try catch information).
+ if (added_block) {
+ DCHECK(!graph_->HasIrreducibleLoops())
+ << "Recomputing loop information in graphs with irreducible loops "
+ << "is unsupported, as it could lead to loop header changes";
+ graph_->ClearLoopInformation();
+ graph_->ClearDominanceInformation();
+ graph_->BuildDominatorTree();
+ }
+}
+
+GraphAnalysisResult HGraphBuilder::BuildGraph(bool build_for_inline) {
DCHECK(code_item_accessor_.HasCodeItem());
DCHECK(graph_->GetBlocks().empty());
@@ -147,7 +190,13 @@
return kAnalysisInvalidBytecode;
}
- // 5) Type the graph and eliminate dead/redundant phis.
+ // 5) When inlining, we want to add a Goto block if we have Return/ReturnVoid->TryBoundary->Exit
+ // since we will have Return/ReturnVoid->TryBoundary->`continue to normal execution` once inlined.
+ if (build_for_inline) {
+ MaybeAddExtraGotoBlocks();
+ }
+
+ // 6) Type the graph and eliminate dead/redundant phis.
return ssa_builder.BuildSsa();
}
diff --git a/compiler/optimizing/builder.h b/compiler/optimizing/builder.h
index 580769e..a59e782 100644
--- a/compiler/optimizing/builder.h
+++ b/compiler/optimizing/builder.h
@@ -46,7 +46,7 @@
const CodeItemDebugInfoAccessor& accessor,
DataType::Type return_type = DataType::Type::kInt32);
- GraphAnalysisResult BuildGraph();
+ GraphAnalysisResult BuildGraph(bool build_for_inline = false);
void BuildIntrinsicGraph(ArtMethod* method);
static constexpr const char* kBuilderPassName = "builder";
@@ -54,6 +54,10 @@
private:
bool SkipCompilation(size_t number_of_branches);
+ // When inlining, we sometimes want to add an extra Goto block before the Exit block. This is done
+ // in the building phase as we do not allow the inlining phase to add new instructions.
+ void MaybeAddExtraGotoBlocks();
+
HGraph* const graph_;
const DexFile* const dex_file_;
const CodeItemDebugInfoAccessor code_item_accessor_; // null for intrinsic graph.
diff --git a/compiler/optimizing/inliner.cc b/compiler/optimizing/inliner.cc
index cfde561..b739f3f 100644
--- a/compiler/optimizing/inliner.cc
+++ b/compiler/optimizing/inliner.cc
@@ -58,7 +58,7 @@
// Limit the number of dex registers that we accumulate while inlining
// to avoid creating large amount of nested environments.
-static constexpr size_t kMaximumNumberOfCumulatedDexRegisters = 32;
+static constexpr size_t kMaximumNumberOfCumulatedDexRegisters = 20;
// Limit recursive call inlining, which do not benefit from too
// much inlining compared to code locality.
@@ -72,6 +72,9 @@
// Controls the use of inline caches in AOT mode.
static constexpr bool kUseAOTInlineCaches = true;
+// Controls the use of inlining try catches.
+static constexpr bool kInlineTryCatches = true;
+
// We check for line numbers to make sure the DepthString implementation
// aligns the output nicely.
#define LOG_INTERNAL(msg) \
@@ -504,10 +507,27 @@
DCHECK(!invoke_instruction->IsInvokeStaticOrDirect());
+ // No try catch inlining allowed here, or recursively. For try catch inlining we are banking on
+ // the fact that we have a unique dex pc list. We cannot guarantee that for some TryInline methods
+ // e.g. `TryInlinePolymorphicCall`.
+ // TODO(solanes): Setting `try_catch_inlining_allowed_` to false here covers all cases from
+ // `TryInlineFromCHA` and from `TryInlineFromInlineCache` as well (e.g.
+ // `TryInlinePolymorphicCall`). Reassess to see if we can inline inline catch blocks in
+ // `TryInlineFromCHA`, `TryInlineMonomorphicCall` and `TryInlinePolymorphicCallToSameTarget`.
+
+ // We store the value to restore it since we will use the same HInliner instance for other inlinee
+ // candidates.
+ const bool previous_value = try_catch_inlining_allowed_;
+ try_catch_inlining_allowed_ = false;
+
if (TryInlineFromCHA(invoke_instruction)) {
+ try_catch_inlining_allowed_ = previous_value;
return true;
}
- return TryInlineFromInlineCache(invoke_instruction);
+
+ const bool result = TryInlineFromInlineCache(invoke_instruction);
+ try_catch_inlining_allowed_ = previous_value;
+ return result;
}
bool HInliner::TryInlineFromCHA(HInvoke* invoke_instruction) {
@@ -1407,9 +1427,25 @@
}
if (accessor.TriesSize() != 0) {
- LOG_FAIL(stats_, MethodCompilationStat::kNotInlinedTryCatchCallee)
- << "Method " << method->PrettyMethod() << " is not inlined because of try block";
- return false;
+ if (!kInlineTryCatches) {
+ LOG_FAIL(stats_, MethodCompilationStat::kNotInlinedTryCatchDisabled)
+ << "Method " << method->PrettyMethod()
+ << " is not inlined because inlining try catches is disabled globally";
+ return false;
+ }
+ const bool inlined_into_try_catch =
+ // Direct parent is a try catch.
+ invoke_instruction->GetBlock()->GetTryCatchInformation() != nullptr ||
+ // Indirect parent is a try catch.
+ !try_catch_inlining_allowed_;
+ if (inlined_into_try_catch) {
+ LOG_FAIL(stats_, MethodCompilationStat::kNotInlinedTryCatchCallee)
+ << "Method " << method->PrettyMethod()
+ << " is not inlined because it has a try catch and we are not supporting it for this"
+ << " particular call. This is could be because e.g. it would be inlined inside another"
+ << " try catch, we arrived here from TryInlinePolymorphicCall, etc.";
+ return false;
+ }
}
if (invoke_instruction->IsInvokeStaticOrDirect() &&
@@ -1517,8 +1553,7 @@
return false;
}
- if (!TryBuildAndInlineHelper(
- invoke_instruction, method, receiver_type, return_replacement)) {
+ if (!TryBuildAndInlineHelper(invoke_instruction, method, receiver_type, return_replacement)) {
return false;
}
@@ -1844,7 +1879,15 @@
bool has_one_return = false;
for (HBasicBlock* predecessor : exit_block->GetPredecessors()) {
- if (predecessor->GetLastInstruction()->IsThrow()) {
+ const HInstruction* last_instruction = predecessor->GetLastInstruction();
+ // On inlinees, we can have Throw -> TryBoundary -> Exit. To check for the actual last
+ // instruction, we have to skip it.
+ if (last_instruction->IsTryBoundary()) {
+ predecessor = predecessor->GetSinglePredecessor();
+ last_instruction = predecessor->GetLastInstruction();
+ }
+
+ if (last_instruction->IsThrow()) {
if (target_block->IsTryBlock()) {
// TODO(ngeoffray): Support adding HTryBoundary in Hgraph::InlineInto.
LOG_FAIL(stats_, MethodCompilationStat::kNotInlinedTryCatchCaller)
@@ -2062,7 +2105,7 @@
codegen_,
inline_stats_);
- if (builder.BuildGraph() != kAnalysisSuccess) {
+ if (builder.BuildGraph(/* build_for_inline= */ true) != kAnalysisSuccess) {
LOG_FAIL(stats_, MethodCompilationStat::kNotInlinedCannotBuild)
<< "Method " << callee_dex_file.PrettyMethod(method_index)
<< " could not be built, so cannot be inlined";
@@ -2071,7 +2114,15 @@
SubstituteArguments(callee_graph, invoke_instruction, receiver_type, dex_compilation_unit);
- RunOptimizations(callee_graph, code_item, dex_compilation_unit);
+ const bool try_catch_inlining_allowed_for_recursive_inline =
+ // It was allowed previously.
+ try_catch_inlining_allowed_ &&
+ // The current invoke is not in a try or a catch.
+ invoke_instruction->GetBlock()->GetTryCatchInformation() == nullptr;
+ RunOptimizations(callee_graph,
+ code_item,
+ dex_compilation_unit,
+ try_catch_inlining_allowed_for_recursive_inline);
size_t number_of_instructions = 0;
if (!CanInlineBody(callee_graph, invoke_instruction, &number_of_instructions)) {
@@ -2109,7 +2160,8 @@
void HInliner::RunOptimizations(HGraph* callee_graph,
const dex::CodeItem* code_item,
- const DexCompilationUnit& dex_compilation_unit) {
+ const DexCompilationUnit& dex_compilation_unit,
+ bool try_catch_inlining_allowed_for_recursive_inline) {
// Note: if the outermost_graph_ is being compiled OSR, we should not run any
// optimization that could lead to a HDeoptimize. The following optimizations do not.
HDeadCodeElimination dce(callee_graph, inline_stats_, "dead_code_elimination$inliner");
@@ -2155,7 +2207,8 @@
total_number_of_dex_registers_ + accessor.RegistersSize(),
total_number_of_instructions_ + number_of_instructions,
this,
- depth_ + 1);
+ depth_ + 1,
+ try_catch_inlining_allowed_for_recursive_inline);
inliner.Run();
}
diff --git a/compiler/optimizing/inliner.h b/compiler/optimizing/inliner.h
index e33160e..712d3b5 100644
--- a/compiler/optimizing/inliner.h
+++ b/compiler/optimizing/inliner.h
@@ -42,7 +42,8 @@
size_t total_number_of_dex_registers,
size_t total_number_of_instructions,
HInliner* parent,
- size_t depth = 0,
+ size_t depth,
+ bool try_catch_inlining_allowed,
const char* name = kInlinerPassName)
: HOptimization(outer_graph, name, stats),
outermost_graph_(outermost_graph),
@@ -54,6 +55,7 @@
parent_(parent),
depth_(depth),
inlining_budget_(0),
+ try_catch_inlining_allowed_(try_catch_inlining_allowed),
inline_stats_(nullptr) {}
bool Run() override;
@@ -91,7 +93,7 @@
ArtMethod* resolved_method,
ReferenceTypeInfo receiver_type,
HInstruction** return_replacement)
- REQUIRES_SHARED(Locks::mutator_lock_);
+ REQUIRES_SHARED(Locks::mutator_lock_);
// Substitutes parameters in the callee graph with their values from the caller.
void SubstituteArguments(HGraph* callee_graph,
@@ -103,8 +105,9 @@
// Run simple optimizations on `callee_graph`.
void RunOptimizations(HGraph* callee_graph,
const dex::CodeItem* code_item,
- const DexCompilationUnit& dex_compilation_unit)
- REQUIRES_SHARED(Locks::mutator_lock_);
+ const DexCompilationUnit& dex_compilation_unit,
+ bool try_catch_inlining_allowed_for_recursive_inline)
+ REQUIRES_SHARED(Locks::mutator_lock_);
// Try to recognize known simple patterns and replace invoke call with appropriate instructions.
bool TryPatternSubstitution(HInvoke* invoke_instruction,
@@ -318,6 +321,9 @@
// The budget left for inlining, in number of instructions.
size_t inlining_budget_;
+ // States if we are allowing try catch inlining to occur at this particular instance of inlining.
+ bool try_catch_inlining_allowed_;
+
// Used to record stats about optimizations on the inlined graph.
// If the inlining is successful, these stats are merged to the caller graph's stats.
OptimizingCompilerStats* inline_stats_;
diff --git a/compiler/optimizing/nodes.cc b/compiler/optimizing/nodes.cc
index 19731ae..8c88c50 100644
--- a/compiler/optimizing/nodes.cc
+++ b/compiler/optimizing/nodes.cc
@@ -2703,7 +2703,8 @@
void HGraph::UpdateLoopAndTryInformationOfNewBlock(HBasicBlock* block,
HBasicBlock* reference,
- bool replace_if_back_edge) {
+ bool replace_if_back_edge,
+ bool has_more_specific_try_catch_info) {
if (block->IsLoopHeader()) {
// Clear the information of which blocks are contained in that loop. Since the
// information is stored as a bit vector based on block ids, we have to update
@@ -2730,11 +2731,16 @@
}
}
- // Copy TryCatchInformation if `reference` is a try block, not if it is a catch block.
- TryCatchInformation* try_catch_info = reference->IsTryBlock()
- ? reference->GetTryCatchInformation()
- : nullptr;
- block->SetTryCatchInformation(try_catch_info);
+ DCHECK_IMPLIES(has_more_specific_try_catch_info, reference->GetTryCatchInformation() == nullptr)
+ << "We don't allow to inline try catches inside of other try catches.";
+
+ // Update the TryCatchInformation, if we are not inlining a try catch.
+ if (!has_more_specific_try_catch_info) {
+ // Copy TryCatchInformation if `reference` is a try block, not if it is a catch block.
+ TryCatchInformation* try_catch_info =
+ reference->IsTryBlock() ? reference->GetTryCatchInformation() : nullptr;
+ block->SetTryCatchInformation(try_catch_info);
+ }
}
HInstruction* HGraph::InlineInto(HGraph* outer_graph, HInvoke* invoke) {
@@ -2847,12 +2853,14 @@
// and (4) to the blocks that apply.
for (HBasicBlock* current : GetReversePostOrder()) {
if (current != exit_block_ && current != entry_block_ && current != first) {
- DCHECK(current->GetTryCatchInformation() == nullptr);
DCHECK(current->GetGraph() == this);
current->SetGraph(outer_graph);
outer_graph->AddBlock(current);
outer_graph->reverse_post_order_[++index_of_at] = current;
- UpdateLoopAndTryInformationOfNewBlock(current, at, /* replace_if_back_edge= */ false);
+ UpdateLoopAndTryInformationOfNewBlock(current,
+ at,
+ /* replace_if_back_edge= */ false,
+ current->GetTryCatchInformation() != nullptr);
}
}
@@ -2866,16 +2874,47 @@
// Update all predecessors of the exit block (now the `to` block)
// to not `HReturn` but `HGoto` instead. Special case throwing blocks
- // to now get the outer graph exit block as successor. Note that the inliner
- // currently doesn't support inlining methods with try/catch.
+ // to now get the outer graph exit block as successor.
HPhi* return_value_phi = nullptr;
bool rerun_dominance = false;
bool rerun_loop_analysis = false;
for (size_t pred = 0; pred < to->GetPredecessors().size(); ++pred) {
HBasicBlock* predecessor = to->GetPredecessors()[pred];
HInstruction* last = predecessor->GetLastInstruction();
+
+ // 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
+
+ const bool saw_goto = last->IsGoto();
+ if (saw_goto) {
+ DCHECK(predecessor->IsSingleGoto());
+ predecessor = predecessor->GetSinglePredecessor();
+ last = predecessor->GetLastInstruction();
+ }
+
+ const bool saw_try_boundary = last->IsTryBoundary();
+ if (saw_try_boundary) {
+ DCHECK(predecessor->IsSingleTryBoundary());
+ DCHECK(!last->AsTryBoundary()->IsEntry());
+ predecessor = predecessor->GetSinglePredecessor();
+ last = predecessor->GetLastInstruction();
+ }
+
+ // 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()) {
DCHECK(!at->IsTryBlock());
+ // The chain `Throw->TryBoundary` is allowed but not `Throw->TryBoundary->Goto` since that
+ // would mean a Goto will point to exit after ReplaceSuccessor.
+ DCHECK(!saw_goto);
+
+ // We either have `Throw->TryBoundary` or `Throw`. We want to point the whole chain to the
+ // exit, so we recompute `predecessor`
+ predecessor = to->GetPredecessors()[pred];
predecessor->ReplaceSuccessor(to, outer_graph->GetExitBlock());
--pred;
// We need to re-run dominance information, as the exit block now has
diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h
index 8db8c02..44e342f 100644
--- a/compiler/optimizing/nodes.h
+++ b/compiler/optimizing/nodes.h
@@ -486,9 +486,11 @@
// Update the loop and try membership of `block`, which was spawned from `reference`.
// In case `reference` is a back edge, `replace_if_back_edge` notifies whether `block`
// should be the new back edge.
+ // `has_more_specific_try_catch_info` will be set to true when inlining a try catch.
void UpdateLoopAndTryInformationOfNewBlock(HBasicBlock* block,
HBasicBlock* reference,
- bool replace_if_back_edge);
+ bool replace_if_back_edge,
+ bool has_more_specific_try_catch_info = false);
// Need to add a couple of blocks to test if the loop body is entered and
// put deoptimization instructions, etc.
diff --git a/compiler/optimizing/optimization.cc b/compiler/optimizing/optimization.cc
index 7bf6dbb..2b7bedd 100644
--- a/compiler/optimizing/optimization.cc
+++ b/compiler/optimizing/optimization.cc
@@ -239,6 +239,7 @@
/* total_number_of_instructions= */ 0,
/* parent= */ nullptr,
/* depth= */ 0,
+ /* try_catch_inlining_allowed= */ true,
pass_name);
break;
}
diff --git a/compiler/optimizing/optimizing_compiler_stats.h b/compiler/optimizing/optimizing_compiler_stats.h
index 18b3a60..313f3ef 100644
--- a/compiler/optimizing/optimizing_compiler_stats.h
+++ b/compiler/optimizing/optimizing_compiler_stats.h
@@ -94,6 +94,7 @@
kNotInlinedInfiniteLoop,
kNotInlinedTryCatchCaller,
kNotInlinedTryCatchCallee,
+ kNotInlinedTryCatchDisabled,
kNotInlinedRegisterAllocator,
kNotInlinedCannotBuild,
kNotInlinedNeverInlineAnnotation,
diff --git a/runtime/oat.h b/runtime/oat.h
index 341e70b..32034f6 100644
--- a/runtime/oat.h
+++ b/runtime/oat.h
@@ -32,8 +32,8 @@
class PACKED(4) OatHeader {
public:
static constexpr std::array<uint8_t, 4> kOatMagic { { 'o', 'a', 't', '\n' } };
- // Last oat version changed reason: Don't use instrumentation stubs for native methods.
- static constexpr std::array<uint8_t, 4> kOatVersion { { '2', '2', '7', '\0' } };
+ // Last oat version changed reason: Try catch inlining.
+ static constexpr std::array<uint8_t, 4> kOatVersion { { '2', '2', '8', '\0' } };
static constexpr const char* kDex2OatCmdLineKey = "dex2oat-cmdline";
static constexpr const char* kDebuggableKey = "debuggable";
diff --git a/test/2237-checker-inline-multidex/expected-stdout.txt b/test/2237-checker-inline-multidex/expected-stdout.txt
index 571349a..c2c127f 100644
--- a/test/2237-checker-inline-multidex/expected-stdout.txt
+++ b/test/2237-checker-inline-multidex/expected-stdout.txt
@@ -2,3 +2,4 @@
def
ghi
class Multi$Multi2
+122
diff --git a/test/2237-checker-inline-multidex/info.txt b/test/2237-checker-inline-multidex/info.txt
index fc6dc60..2e639ba 100644
--- a/test/2237-checker-inline-multidex/info.txt
+++ b/test/2237-checker-inline-multidex/info.txt
@@ -1 +1 @@
-Checks that we inline across dex files, even when we need an environment.
+Checks that we inline across dex files.
diff --git a/test/2237-checker-inline-multidex/src-multidex/Multi.java b/test/2237-checker-inline-multidex/src-multidex/Multi.java
index cd234c3..c830c59 100644
--- a/test/2237-checker-inline-multidex/src-multidex/Multi.java
+++ b/test/2237-checker-inline-multidex/src-multidex/Multi.java
@@ -38,4 +38,12 @@
}
private class Multi2 {}
+
+ public static int $inline$TryCatch(String str) {
+ try {
+ return Integer.parseInt(str);
+ } catch (NumberFormatException ex) {
+ return -1;
+ }
+ }
}
diff --git a/test/2237-checker-inline-multidex/src/Main.java b/test/2237-checker-inline-multidex/src/Main.java
index 7ab2e7f..7a2ca82 100644
--- a/test/2237-checker-inline-multidex/src/Main.java
+++ b/test/2237-checker-inline-multidex/src/Main.java
@@ -16,57 +16,73 @@
public class Main {
public static void main(String[] args) {
- System.out.println(testNeedsEnvironment());
- System.out.println(testNeedsBssEntryString());
- System.out.println(testNeedsBssEntryInvoke());
- System.out.println(testClass());
+ // Test that the cross-dex inlining is working for HInstructions that need an environment.
+ System.out.println($noinline$testNeedsEnvironment());
+
+ // Test that the cross-dex inlining is working for HInstructions that need a bss entry.
+ System.out.println($noinline$testNeedsBssEntryString());
+ System.out.println($noinline$testNeedsBssEntryInvoke());
+ System.out.println($noinline$testClass());
+
+ // Test that we are able to inline try catches across dex files.
+ System.out.println($noinline$testTryCatch());
}
- /// CHECK-START: java.lang.String Main.testNeedsEnvironment() inliner (before)
+ /// CHECK-START: java.lang.String Main.$noinline$testNeedsEnvironment() inliner (before)
/// CHECK: InvokeStaticOrDirect method_name:Multi.$inline$NeedsEnvironmentMultiDex
- /// CHECK-START: java.lang.String Main.testNeedsEnvironment() inliner (after)
+ /// CHECK-START: java.lang.String Main.$noinline$testNeedsEnvironment() inliner (after)
/// CHECK-NOT: InvokeStaticOrDirect method_name:Multi.$inline$NeedsEnvironmentMultiDex
- /// CHECK-START: java.lang.String Main.testNeedsEnvironment() inliner (after)
+ /// CHECK-START: java.lang.String Main.$noinline$testNeedsEnvironment() inliner (after)
/// CHECK: StringBuilderAppend
- public static String testNeedsEnvironment() {
+ private static String $noinline$testNeedsEnvironment() {
return Multi.$inline$NeedsEnvironmentMultiDex("abc");
}
- /// CHECK-START: java.lang.String Main.testNeedsBssEntryString() inliner (before)
+ /// CHECK-START: java.lang.String Main.$noinline$testNeedsBssEntryString() inliner (before)
/// CHECK: InvokeStaticOrDirect method_name:Multi.$inline$NeedsBssEntryStringMultiDex
- /// CHECK-START: java.lang.String Main.testNeedsBssEntryString() inliner (after)
+ /// CHECK-START: java.lang.String Main.$noinline$testNeedsBssEntryString() inliner (after)
/// CHECK-NOT: InvokeStaticOrDirect method_name:Multi.$inline$NeedsBssEntryStringMultiDex
- /// CHECK-START: java.lang.String Main.testNeedsBssEntryString() inliner (after)
+ /// CHECK-START: java.lang.String Main.$noinline$testNeedsBssEntryString() inliner (after)
/// CHECK: LoadString load_kind:BssEntry
- public static String testNeedsBssEntryString() {
+ private static String $noinline$testNeedsBssEntryString() {
return Multi.$inline$NeedsBssEntryStringMultiDex();
}
- /// CHECK-START: java.lang.String Main.testNeedsBssEntryInvoke() inliner (before)
+ /// CHECK-START: java.lang.String Main.$noinline$testNeedsBssEntryInvoke() inliner (before)
/// CHECK: InvokeStaticOrDirect method_name:Multi.$inline$NeedsBssEntryInvokeMultiDex
- /// CHECK-START: java.lang.String Main.testNeedsBssEntryInvoke() inliner (after)
+ /// CHECK-START: java.lang.String Main.$noinline$testNeedsBssEntryInvoke() inliner (after)
/// CHECK-NOT: InvokeStaticOrDirect method_name:Multi.$inline$NeedsBssEntryInvokeMultiDex
- /// CHECK-START: java.lang.String Main.testNeedsBssEntryInvoke() inliner (after)
+ /// CHECK-START: java.lang.String Main.$noinline$testNeedsBssEntryInvoke() inliner (after)
/// CHECK: InvokeStaticOrDirect method_name:Multi.$noinline$InnerInvokeMultiDex method_load_kind:BssEntry
- public static String testNeedsBssEntryInvoke() {
+ private static String $noinline$testNeedsBssEntryInvoke() {
return Multi.$inline$NeedsBssEntryInvokeMultiDex();
}
- /// CHECK-START: java.lang.Class Main.testClass() inliner (before)
+ /// CHECK-START: java.lang.Class Main.$noinline$testClass() inliner (before)
/// CHECK: InvokeStaticOrDirect method_name:Multi.NeedsBssEntryClassMultiDex
- /// CHECK-START: java.lang.Class Main.testClass() inliner (after)
+ /// CHECK-START: java.lang.Class Main.$noinline$testClass() inliner (after)
/// CHECK-NOT: InvokeStaticOrDirect method_name:Multi.NeedsBssEntryClassMultiDex
- /// CHECK-START: java.lang.Class Main.testClass() inliner (after)
+ /// CHECK-START: java.lang.Class Main.$noinline$testClass() inliner (after)
/// CHECK: LoadClass load_kind:BssEntry class_name:Multi$Multi2
- public static Class<?> testClass() {
+ private static Class<?> $noinline$testClass() {
return Multi.NeedsBssEntryClassMultiDex();
}
+
+
+ /// CHECK-START: int Main.$noinline$testTryCatch() inliner (before)
+ /// CHECK-NOT: TryBoundary
+
+ /// CHECK-START: int Main.$noinline$testTryCatch() inliner (after)
+ /// CHECK: TryBoundary
+ private static int $noinline$testTryCatch() {
+ return Multi.$inline$TryCatch("123") + Multi.$inline$TryCatch("abc");
+ }
}
diff --git a/test/2241-checker-inline-try-catch/expected-stderr.txt b/test/2241-checker-inline-try-catch/expected-stderr.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/2241-checker-inline-try-catch/expected-stderr.txt
diff --git a/test/2241-checker-inline-try-catch/expected-stdout.txt b/test/2241-checker-inline-try-catch/expected-stdout.txt
new file mode 100644
index 0000000..6d127d1
--- /dev/null
+++ b/test/2241-checker-inline-try-catch/expected-stdout.txt
@@ -0,0 +1,2 @@
+Finally, a worthy opponent!
+Our battle it will be legendary!
diff --git a/test/2241-checker-inline-try-catch/info.txt b/test/2241-checker-inline-try-catch/info.txt
new file mode 100644
index 0000000..3b86006
--- /dev/null
+++ b/test/2241-checker-inline-try-catch/info.txt
@@ -0,0 +1 @@
+Tests that we inline try catches, as long as we don't inline inside of other try catches.
diff --git a/test/2241-checker-inline-try-catch/src/Main.java b/test/2241-checker-inline-try-catch/src/Main.java
new file mode 100644
index 0000000..a76a99b
--- /dev/null
+++ b/test/2241-checker-inline-try-catch/src/Main.java
@@ -0,0 +1,284 @@
+/*
+ * 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) {
+ $noinline$testSingleTryCatch();
+ $noinline$testSingleTryCatchTwice();
+ $noinline$testSingleTryCatchDifferentInputs();
+ $noinline$testDifferentTryCatches();
+ $noinline$testTryCatchFinally();
+ $noinline$testTryCatchFinallyDifferentInputs();
+ $noinline$testRecursiveTryCatch();
+ $noinline$testDoNotInlineInsideTryOrCatch();
+ $noinline$testBeforeAfterTryCatch();
+ $noinline$testDifferentTypes();
+ $noinline$testRawThrow();
+ $noinline$testRawThrowTwice();
+ $noinline$testThrowCaughtInOuterMethod();
+ }
+
+ public static void $noinline$assertEquals(int expected, int result) {
+ if (expected != result) {
+ throw new Error("Expected: " + expected + ", found: " + result);
+ }
+ }
+
+ // Basic try catch inline.
+ private static void $noinline$testSingleTryCatch() {
+ int[] numbers = {};
+ $noinline$assertEquals(1, $inline$OOBTryCatch(numbers));
+ }
+
+ // Two instances of the same method with a try catch.
+ private static void $noinline$testSingleTryCatchTwice() {
+ int[] numbers = {};
+ $noinline$assertEquals(1, $inline$OOBTryCatch(numbers));
+ $noinline$assertEquals(1, $inline$OOBTryCatch(numbers));
+ }
+
+ // Triggering both normal and the exceptional flow.
+ private static void $noinline$testSingleTryCatchDifferentInputs() {
+ $noinline$assertEquals(1, $inline$OOBTryCatch(null));
+ int[] numbers = {};
+ $noinline$assertEquals(1, $inline$OOBTryCatch(numbers));
+ int[] filled_numbers = {42};
+ $noinline$assertEquals(42, $inline$OOBTryCatch(filled_numbers));
+ }
+
+
+ // Two different try catches, with the same catch's dex_pc.
+ private static void $noinline$testDifferentTryCatches() {
+ int[] numbers = {};
+ $noinline$assertEquals(1, $inline$OOBTryCatch(numbers));
+ $noinline$assertEquals(2, $inline$OtherOOBTryCatch(numbers));
+ }
+
+ // Basic try/catch/finally.
+ private static void $noinline$testTryCatchFinally() {
+ int[] numbers = {};
+ $noinline$assertEquals(3, $inline$OOBTryCatchFinally(numbers));
+ }
+
+ // Triggering both normal and the exceptional flow.
+ private static void $noinline$testTryCatchFinallyDifferentInputs() {
+ $noinline$assertEquals(3, $inline$OOBTryCatchFinally(null));
+ int[] numbers = {};
+ $noinline$assertEquals(3, $inline$OOBTryCatchFinally(numbers));
+ int[] filled_numbers = {42};
+ $noinline$assertEquals(42, $inline$OOBTryCatchFinally(filled_numbers));
+ }
+
+ // Test that we can inline even when the try catch is several levels deep.
+ private static void $noinline$testRecursiveTryCatch() {
+ int[] numbers = {};
+ $noinline$assertEquals(1, $inline$OOBTryCatchLevel4(numbers));
+ }
+
+ // Tests that we don't inline inside outer tries or catches.
+ /// CHECK-START: void Main.$noinline$testDoNotInlineInsideTryOrCatch() inliner (before)
+ /// CHECK: InvokeStaticOrDirect method_name:Main.DoNotInlineOOBTryCatch
+ /// CHECK: InvokeStaticOrDirect method_name:Main.DoNotInlineOOBTryCatch
+
+ /// CHECK-START: void Main.$noinline$testDoNotInlineInsideTryOrCatch() inliner (after)
+ /// CHECK: InvokeStaticOrDirect method_name:Main.DoNotInlineOOBTryCatch
+ /// CHECK: InvokeStaticOrDirect method_name:Main.DoNotInlineOOBTryCatch
+ private static void $noinline$testDoNotInlineInsideTryOrCatch() {
+ int val = 0;
+ try {
+ int[] numbers = {};
+ val = DoNotInlineOOBTryCatch(numbers);
+ } catch (Exception ex) {
+ unreachable();
+ // This is unreachable but we will still compile it so it works for checker purposes
+ int[] numbers = {};
+ DoNotInlineOOBTryCatch(numbers);
+ }
+ $noinline$assertEquals(1, val);
+ }
+
+ // Tests that outer tries or catches don't affect as long as we are not inlining the inner
+ // try/catch inside of them.
+ private static void $noinline$testBeforeAfterTryCatch() {
+ int[] numbers = {};
+ $noinline$assertEquals(1, $inline$OOBTryCatch(numbers));
+
+ // Unrelated try catch does not block inlining outside of it. We fill it in to make sure it is
+ // still there by the time the inliner runs.
+ int val = 0;
+ try {
+ int[] other_array = {};
+ val = other_array[0];
+ } catch (Exception ex) {
+ $noinline$assertEquals(0, val);
+ val = 1;
+ }
+ $noinline$assertEquals(1, val);
+
+ $noinline$assertEquals(1, $inline$OOBTryCatch(numbers));
+ }
+
+ // Tests different try catch types in the same outer method.
+ private static void $noinline$testDifferentTypes() {
+ int[] numbers = {};
+ $noinline$assertEquals(1, $inline$OOBTryCatch(numbers));
+ $noinline$assertEquals(2, $inline$OtherOOBTryCatch(numbers));
+ $noinline$assertEquals(123, $inline$ParseIntTryCatch("123"));
+ $noinline$assertEquals(-1, $inline$ParseIntTryCatch("abc"));
+ }
+
+ // Tests a raw throw (rather than an instruction that happens to throw).
+ private static void $noinline$testRawThrow() {
+ $noinline$assertEquals(1, $inline$rawThrowCaught());
+ }
+
+ // Tests a raw throw twice.
+ private static void $noinline$testRawThrowTwice() {
+ $noinline$assertEquals(1, $inline$rawThrowCaught());
+ $noinline$assertEquals(1, $inline$rawThrowCaught());
+ }
+
+ // Tests that the outer method can successfully catch the throw in the inner method.
+ private static void $noinline$testThrowCaughtInOuterMethod() {
+ int[] numbers = {};
+ $noinline$assertEquals(1, $inline$testThrowCaughtInOuterMethod_simpleTryCatch(numbers));
+ $noinline$assertEquals(1, $inline$testThrowCaughtInOuterMethod_simpleTryCatch_inliningInner(numbers));
+ $noinline$assertEquals(1, $inline$testThrowCaughtInOuterMethod_withFinally(numbers));
+ }
+
+ // Building blocks for the test functions.
+ private static int $inline$OOBTryCatch(int[] array) {
+ try {
+ return array[0];
+ } catch (Exception e) {
+ return 1;
+ }
+ }
+
+ private static int $inline$OtherOOBTryCatch(int[] array) {
+ try {
+ return array[0];
+ } catch (Exception e) {
+ return 2;
+ }
+ }
+
+ private static int $inline$OOBTryCatchFinally(int[] array) {
+ int val = 0;
+ try {
+ val = 1;
+ return array[0];
+ } catch (Exception e) {
+ val = 2;
+ } finally {
+ val = 3;
+ }
+ return val;
+ }
+
+ // If we make the depthness a parameter, we wouldn't be able to mark as $inline$ and we would
+ // need extra CHECKer statements.
+ private static int $inline$OOBTryCatchLevel4(int[] array) {
+ return $inline$OOBTryCatchLevel3(array);
+ }
+
+ private static int $inline$OOBTryCatchLevel3(int[] array) {
+ return $inline$OOBTryCatchLevel2(array);
+ }
+
+ private static int $inline$OOBTryCatchLevel2(int[] array) {
+ return $inline$OOBTryCatchLevel1(array);
+ }
+
+ private static int $inline$OOBTryCatchLevel1(int[] array) {
+ return $inline$OOBTryCatch(array);
+ }
+
+ private static int DoNotInlineOOBTryCatch(int[] array) {
+ try {
+ return array[0];
+ } catch (Exception e) {
+ return 1;
+ }
+ }
+
+ private static void unreachable() {
+ throw new Error("Unreachable");
+ }
+
+ private static int $inline$ParseIntTryCatch(String str) {
+ try {
+ return Integer.parseInt(str);
+ } catch (NumberFormatException ex) {
+ return -1;
+ }
+ }
+
+ private static int $inline$rawThrowCaught() {
+ try {
+ throw new Error();
+ } catch (Error e) {
+ return 1;
+ }
+ }
+
+ private static int $inline$testThrowCaughtInOuterMethod_simpleTryCatch(int[] array) {
+ int val = 0;
+ try {
+ $noinline$throwingMethod(array);
+ } catch (Exception ex) {
+ val = 1;
+ }
+ return val;
+ }
+
+ private static int $noinline$throwingMethod(int[] array) {
+ return array[0];
+ }
+
+ private static int $inline$testThrowCaughtInOuterMethod_simpleTryCatch_inliningInner(int[] array) {
+ int val = 0;
+ try {
+ $inline$throwingMethod(array);
+ } catch (Exception ex) {
+ val = 1;
+ }
+ return val;
+ }
+
+ private static int $inline$throwingMethod(int[] array) {
+ return array[0];
+ }
+
+ private static int $inline$testThrowCaughtInOuterMethod_withFinally(int[] array) {
+ int val = 0;
+ try {
+ $noinline$throwingMethodWithFinally(array);
+ } catch (Exception ex) {
+ System.out.println("Our battle it will be legendary!");
+ val = 1;
+ }
+ return val;
+ }
+
+ private static int $noinline$throwingMethodWithFinally(int[] array) {
+ try {
+ return array[0];
+ } finally {
+ System.out.println("Finally, a worthy opponent!");
+ }
+ }
+}
diff --git a/test/542-inline-trycatch/src/Main.java b/test/542-inline-trycatch/src/Main.java
index 5a6e06f..5e33ed6 100644
--- a/test/542-inline-trycatch/src/Main.java
+++ b/test/542-inline-trycatch/src/Main.java
@@ -33,17 +33,6 @@
return is_hex ? Integer.parseInt(str, 16) : Integer.parseInt(str);
}
- // We expect methods with try/catch to not be inlined. Inlined try/catch
- // blocks are not supported at the moment.
-
- private static int $noinline$TryCatch(String str) {
- try {
- return Integer.parseInt(str);
- } catch (NumberFormatException ex) {
- return -1;
- }
- }
-
public static void testSingleBlockFromTry() {
int val = 0;
@@ -117,49 +106,11 @@
assertEquals(32, val);
}
- public static void testTryCatchFromTry() {
- int val = 0;
-
- try {
- val = $noinline$TryCatch("42");
- } catch (NumberFormatException ex) {
- unreachable();
- }
- assertEquals(42, val);
-
- try {
- val = $noinline$TryCatch("xyz");
- } catch (NumberFormatException ex) {
- unreachable();
- }
- assertEquals(-1, val);
- }
-
- public static void testTryCatchFromCatch() {
- int val = 0;
-
- try {
- throwException();
- } catch (Exception ex) {
- val = $noinline$TryCatch("42");
- }
- assertEquals(42, val);
-
- try {
- throwException();
- } catch (Exception ex) {
- val = $noinline$TryCatch("xyz");
- }
- assertEquals(-1, val);
- }
-
public static void main(String[] args) {
testSingleBlockFromTry();
testSingleBlockFromCatch();
testMultipleBlocksFromTry();
testMultipleBlocksFromCatch();
- testTryCatchFromTry();
- testTryCatchFromCatch();
}
private static void assertEquals(int expected, int actual) {