diff options
Diffstat (limited to 'compiler/optimizing')
-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 |
8 files changed, 185 insertions, 30 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, |