diff options
author | 2022-06-24 11:16:35 +0100 | |
---|---|---|
committer | 2022-10-10 09:55:22 +0000 | |
commit | 8efb1a62b67fd1e6866a4b7e465afc11770bb082 (patch) | |
tree | 1f7a9ee46d064b801ce596aaea010f4aafba3aea | |
parent | 841a69822d8a8cd04159575957367eeca2fbd1b2 (diff) |
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
-rw-r--r-- | compiler/optimizing/builder.cc | 53 | ||||
-rw-r--r-- | compiler/optimizing/builder.h | 6 | ||||
-rw-r--r-- | compiler/optimizing/inliner.cc | 77 | ||||
-rw-r--r-- | compiler/optimizing/inliner.h | 14 | ||||
-rw-r--r-- | compiler/optimizing/nodes.cc | 59 | ||||
-rw-r--r-- | compiler/optimizing/nodes.h | 4 | ||||
-rw-r--r-- | compiler/optimizing/optimization.cc | 1 | ||||
-rw-r--r-- | compiler/optimizing/optimizing_compiler_stats.h | 1 | ||||
-rw-r--r-- | runtime/oat.h | 4 | ||||
-rw-r--r-- | test/2237-checker-inline-multidex/expected-stdout.txt | 1 | ||||
-rw-r--r-- | test/2237-checker-inline-multidex/info.txt | 2 | ||||
-rw-r--r-- | test/2237-checker-inline-multidex/src-multidex/Multi.java | 8 | ||||
-rw-r--r-- | test/2237-checker-inline-multidex/src/Main.java | 56 | ||||
-rw-r--r-- | test/2241-checker-inline-try-catch/expected-stderr.txt | 0 | ||||
-rw-r--r-- | test/2241-checker-inline-try-catch/expected-stdout.txt | 2 | ||||
-rw-r--r-- | test/2241-checker-inline-try-catch/info.txt | 1 | ||||
-rw-r--r-- | test/2241-checker-inline-try-catch/src/Main.java | 284 | ||||
-rw-r--r-- | test/542-inline-trycatch/src/Main.java | 49 |
18 files changed, 520 insertions, 102 deletions
diff --git a/compiler/optimizing/builder.cc b/compiler/optimizing/builder.cc index e7826bbba3..b28c2d9592 100644 --- a/compiler/optimizing/builder.cc +++ b/compiler/optimizing/builder.cc @@ -96,7 +96,50 @@ bool HGraphBuilder::SkipCompilation(size_t number_of_branches) { 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 @@ GraphAnalysisResult HGraphBuilder::BuildGraph() { 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 580769e0f9..a59e78285f 100644 --- a/compiler/optimizing/builder.h +++ b/compiler/optimizing/builder.h @@ -46,7 +46,7 @@ class HGraphBuilder : public ValueObject { 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 @@ class HGraphBuilder : public ValueObject { 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 cfde561194..b739f3f838 100644 --- a/compiler/optimizing/inliner.cc +++ b/compiler/optimizing/inliner.cc @@ -58,7 +58,7 @@ static constexpr size_t kMaximumNumberOfInstructionsForSmallMethod = 3; // 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 @@ static constexpr size_t kMaximumNumberOfPolymorphicRecursiveCalls = 0; // 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 @@ bool HInliner::TryInline(HInvoke* invoke_instruction) { 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 @@ bool HInliner::IsInliningSupported(const HInvoke* invoke_instruction, } 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 @@ bool HInliner::TryBuildAndInline(HInvoke* invoke_instruction, 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 HInliner::CanInlineBody(const HGraph* callee_graph, 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 @@ bool HInliner::TryBuildAndInlineHelper(HInvoke* invoke_instruction, 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 @@ bool HInliner::TryBuildAndInlineHelper(HInvoke* invoke_instruction, 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 @@ bool HInliner::TryBuildAndInlineHelper(HInvoke* invoke_instruction, 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 @@ void HInliner::RunOptimizations(HGraph* callee_graph, 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 e33160efac..712d3b5323 100644 --- a/compiler/optimizing/inliner.h +++ b/compiler/optimizing/inliner.h @@ -42,7 +42,8 @@ class HInliner : public HOptimization { 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 @@ class HInliner : public HOptimization { 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 @@ class HInliner : public HOptimization { 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 @@ class HInliner : public HOptimization { // 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 @@ class HInliner : public HOptimization { // 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 19731ae474..8c88c503a3 100644 --- a/compiler/optimizing/nodes.cc +++ b/compiler/optimizing/nodes.cc @@ -2703,7 +2703,8 @@ void HGraph::DeleteDeadEmptyBlock(HBasicBlock* block) { 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 @@ void HGraph::UpdateLoopAndTryInformationOfNewBlock(HBasicBlock* block, } } - // 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 @@ HInstruction* HGraph::InlineInto(HGraph* outer_graph, HInvoke* invoke) { // 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 @@ HInstruction* HGraph::InlineInto(HGraph* outer_graph, HInvoke* invoke) { // 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 8db8c02064..44e342f018 100644 --- a/compiler/optimizing/nodes.h +++ b/compiler/optimizing/nodes.h @@ -486,9 +486,11 @@ class HGraph : public ArenaObject<kArenaAllocGraph> { // 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 7bf6dbb741..2b7beddfab 100644 --- a/compiler/optimizing/optimization.cc +++ b/compiler/optimizing/optimization.cc @@ -239,6 +239,7 @@ ArenaVector<HOptimization*> ConstructOptimizations( /* 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 18b3a60546..313f3ef9d8 100644 --- a/compiler/optimizing/optimizing_compiler_stats.h +++ b/compiler/optimizing/optimizing_compiler_stats.h @@ -94,6 +94,7 @@ enum class MethodCompilationStat { kNotInlinedInfiniteLoop, kNotInlinedTryCatchCaller, kNotInlinedTryCatchCallee, + kNotInlinedTryCatchDisabled, kNotInlinedRegisterAllocator, kNotInlinedCannotBuild, kNotInlinedNeverInlineAnnotation, diff --git a/runtime/oat.h b/runtime/oat.h index 341e70bbe9..32034f66f1 100644 --- a/runtime/oat.h +++ b/runtime/oat.h @@ -32,8 +32,8 @@ class InstructionSetFeatures; 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 571349a527..c2c127fa34 100644 --- a/test/2237-checker-inline-multidex/expected-stdout.txt +++ b/test/2237-checker-inline-multidex/expected-stdout.txt @@ -2,3 +2,4 @@ abc 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 fc6dc60e66..2e639baf27 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 cd234c3477..c830c59f49 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 @@ public class Multi { } 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 7ab2e7fa64..7a2ca829f3 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 0000000000..e69de29bb2 --- /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 0000000000..6d127d1c3e --- /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 0000000000..3b8600695f --- /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 0000000000..a76a99bd89 --- /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 5a6e06fa0c..5e33ed654e 100644 --- a/test/542-inline-trycatch/src/Main.java +++ b/test/542-inline-trycatch/src/Main.java @@ -33,17 +33,6 @@ public class Main { 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 @@ public class Main { 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) { |