diff options
author | 2015-05-18 22:31:29 +0100 | |
---|---|---|
committer | 2015-05-29 14:15:04 +0100 | |
commit | d23eeef3492b53102eb8093524cf37e2b4c296db (patch) | |
tree | 57d3e9ab2853d5b8092568bb3d29bc850c113315 | |
parent | a15c78d3cc28f514a482ffd792a767e97fe53c95 (diff) |
Support for inlining methods that call/throw.
Mostly fixes here and there to make it working.
Change-Id: I1b535e895105d78b65634636d675b818551f783e
24 files changed, 377 insertions, 62 deletions
diff --git a/compiler/optimizing/builder.cc b/compiler/optimizing/builder.cc index f858f82a40..365599f904 100644 --- a/compiler/optimizing/builder.cc +++ b/compiler/optimizing/builder.cc @@ -652,8 +652,8 @@ bool HGraphBuilder::BuildInvoke(const Instruction& instruction, DCHECK((optimized_invoke_type == invoke_type) || (optimized_invoke_type != kDirect) || compiler_driver_->GetCompilerOptions().GetCompilePic()); bool is_recursive = - (target_method.dex_method_index == dex_compilation_unit_->GetDexMethodIndex()); - DCHECK(!is_recursive || (target_method.dex_file == dex_compilation_unit_->GetDexFile())); + (target_method.dex_method_index == outer_compilation_unit_->GetDexMethodIndex()) + && (target_method.dex_file == outer_compilation_unit_->GetDexFile()); if (optimized_invoke_type == kStatic) { ScopedObjectAccess soa(Thread::Current()); diff --git a/compiler/optimizing/graph_visualizer.cc b/compiler/optimizing/graph_visualizer.cc index 7da4f2d1fd..fd2e4e81df 100644 --- a/compiler/optimizing/graph_visualizer.cc +++ b/compiler/optimizing/graph_visualizer.cc @@ -280,6 +280,13 @@ class HGraphVisualizerPrinter : public HGraphVisitor { << instance_of->MustDoNullCheck() << std::noboolalpha; } + void VisitInvokeStaticOrDirect(HInvokeStaticOrDirect* invoke) OVERRIDE { + StartAttributeStream("dex_file_index") << invoke->GetDexMethodIndex(); + StartAttributeStream("recursive") << std::boolalpha + << invoke->IsRecursive() + << std::noboolalpha; + } + bool IsPass(const char* name) { return strcmp(pass_name_, name) == 0; } diff --git a/compiler/optimizing/inliner.cc b/compiler/optimizing/inliner.cc index 997f980f45..15f3deb174 100644 --- a/compiler/optimizing/inliner.cc +++ b/compiler/optimizing/inliner.cc @@ -36,8 +36,8 @@ namespace art { -static constexpr int kMaxInlineCodeUnits = 100; -static constexpr int kDepthLimit = 5; +static constexpr int kMaxInlineCodeUnits = 18; +static constexpr int kDepthLimit = 3; void HInliner::Run() { if (graph_->IsDebuggable()) { @@ -46,8 +46,15 @@ void HInliner::Run() { return; } const GrowableArray<HBasicBlock*>& blocks = graph_->GetReversePostOrder(); + HBasicBlock* next_block = blocks.Get(0); for (size_t i = 0; i < blocks.Size(); ++i) { - HBasicBlock* block = blocks.Get(i); + // Because we are changing the graph when inlining, we need to remember the next block. + // This avoids doing the inlining work again on the inlined blocks. + if (blocks.Get(i) != next_block) { + continue; + } + HBasicBlock* block = next_block; + next_block = (i == blocks.Size() - 1) ? nullptr : blocks.Get(i + 1); for (HInstruction* instruction = block->GetFirstInstruction(); instruction != nullptr;) { HInstruction* next = instruction->GetNext(); HInvokeStaticOrDirect* call = instruction->AsInvokeStaticOrDirect(); @@ -90,10 +97,10 @@ bool HInliner::TryInline(HInvoke* invoke_instruction, return false; } - bool can_use_dex_cache = true; + bool same_dex_file = true; const DexFile& outer_dex_file = *outer_compilation_unit_.GetDexFile(); if (resolved_method->GetDexFile()->GetLocation().compare(outer_dex_file.GetLocation()) != 0) { - can_use_dex_cache = false; + same_dex_file = false; } const DexFile::CodeItem* code_item = resolved_method->GetCodeItem(); @@ -140,7 +147,7 @@ bool HInliner::TryInline(HInvoke* invoke_instruction, return false; } - if (!TryBuildAndInline(resolved_method, invoke_instruction, method_index, can_use_dex_cache)) { + if (!TryBuildAndInline(resolved_method, invoke_instruction, method_index, same_dex_file)) { return false; } @@ -152,7 +159,7 @@ bool HInliner::TryInline(HInvoke* invoke_instruction, bool HInliner::TryBuildAndInline(Handle<mirror::ArtMethod> resolved_method, HInvoke* invoke_instruction, uint32_t method_index, - bool can_use_dex_cache) const { + bool same_dex_file) const { ScopedObjectAccess soa(Thread::Current()); const DexFile::CodeItem* code_item = resolved_method->GetCodeItem(); const DexFile& caller_dex_file = *caller_compilation_unit_.GetDexFile(); @@ -254,6 +261,31 @@ bool HInliner::TryBuildAndInline(Handle<mirror::ArtMethod> resolved_method, inliner.Run(); } + // TODO: We should abort only if all predecessors throw. However, + // HGraph::InlineInto currently does not handle an exit block with + // a throw predecessor. + HBasicBlock* exit_block = callee_graph->GetExitBlock(); + if (exit_block == nullptr) { + VLOG(compiler) << "Method " << PrettyMethod(method_index, caller_dex_file) + << " could not be inlined because it has an infinite loop"; + resolved_method->SetShouldNotInline(); + return false; + } + + bool has_throw_predecessor = false; + for (size_t i = 0, e = exit_block->GetPredecessors().Size(); i < e; ++i) { + if (exit_block->GetPredecessors().Get(i)->GetLastInstruction()->IsThrow()) { + has_throw_predecessor = true; + break; + } + } + if (has_throw_predecessor) { + VLOG(compiler) << "Method " << PrettyMethod(method_index, caller_dex_file) + << " could not be inlined because one branch always throws"; + resolved_method->SetShouldNotInline(); + return false; + } + HReversePostOrderIterator it(*callee_graph); it.Advance(); // Past the entry block, it does not contain instructions that prevent inlining. for (; !it.Done(); it.Advance()) { @@ -269,27 +301,24 @@ bool HInliner::TryBuildAndInline(Handle<mirror::ArtMethod> resolved_method, !instr_it.Done(); instr_it.Advance()) { HInstruction* current = instr_it.Current(); - if (current->IsSuspendCheck()) { - continue; - } - if (current->CanThrow()) { + if (current->IsInvokeInterface()) { + // Disable inlining of interface calls. The cost in case of entering the + // resolution conflict is currently too high. VLOG(compiler) << "Method " << PrettyMethod(method_index, caller_dex_file) - << " could not be inlined because " << current->DebugName() - << " can throw"; + << " could not be inlined because it has an interface call."; resolved_method->SetShouldNotInline(); return false; } - if (current->NeedsEnvironment()) { + if (!same_dex_file && current->NeedsEnvironment()) { VLOG(compiler) << "Method " << PrettyMethod(method_index, caller_dex_file) << " could not be inlined because " << current->DebugName() - << " needs an environment"; - resolved_method->SetShouldNotInline(); + << " needs an environment and is in a different dex file"; return false; } - if (!can_use_dex_cache && current->NeedsDexCache()) { + if (!same_dex_file && current->NeedsDexCache()) { VLOG(compiler) << "Method " << PrettyMethod(method_index, caller_dex_file) << " could not be inlined because " << current->DebugName() << " it is in a different dex file and requires access to the dex cache"; @@ -302,10 +331,6 @@ bool HInliner::TryBuildAndInline(Handle<mirror::ArtMethod> resolved_method, callee_graph->InlineInto(graph_, invoke_instruction); - if (callee_graph->HasBoundsChecks()) { - graph_->SetHasBoundsChecks(true); - } - return true; } diff --git a/compiler/optimizing/inliner.h b/compiler/optimizing/inliner.h index 1dbc7d392b..09a36c6659 100644 --- a/compiler/optimizing/inliner.h +++ b/compiler/optimizing/inliner.h @@ -51,7 +51,7 @@ class HInliner : public HOptimization { bool TryBuildAndInline(Handle<mirror::ArtMethod> resolved_method, HInvoke* invoke_instruction, uint32_t method_index, - bool can_use_dex_cache) const; + bool same_dex_file) const; const DexCompilationUnit& outer_compilation_unit_; const DexCompilationUnit& caller_compilation_unit_; diff --git a/compiler/optimizing/nodes.cc b/compiler/optimizing/nodes.cc index 80d4b4a863..06f6a7fd88 100644 --- a/compiler/optimizing/nodes.cc +++ b/compiler/optimizing/nodes.cc @@ -1314,6 +1314,29 @@ void HGraph::DeleteDeadBlock(HBasicBlock* block) { void HGraph::InlineInto(HGraph* outer_graph, HInvoke* invoke) { DCHECK(HasExitBlock()) << "Unimplemented scenario"; + // Update the environments in this graph to have the invoke's environment + // as parent. + { + HReversePostOrderIterator it(*this); + it.Advance(); // Skip the entry block, we do not need to update the entry's suspend check. + for (; !it.Done(); it.Advance()) { + HBasicBlock* block = it.Current(); + for (HInstructionIterator instr_it(block->GetInstructions()); + !instr_it.Done(); + instr_it.Advance()) { + HInstruction* current = instr_it.Current(); + if (current->NeedsEnvironment()) { + current->GetEnvironment()->SetAndCopyParentChain( + outer_graph->GetArena(), invoke->GetEnvironment()); + } + } + } + } + outer_graph->UpdateMaximumNumberOfOutVRegs(GetMaximumNumberOfOutVRegs()); + if (HasBoundsChecks()) { + outer_graph->SetHasBoundsChecks(true); + } + if (GetBlocks().Size() == 3) { // Simple case of an entry block, a body block, and an exit block. // Put the body block's instruction into `invoke`'s block. diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h index 3144c5c193..005d50eea0 100644 --- a/compiler/optimizing/nodes.h +++ b/compiler/optimizing/nodes.h @@ -143,6 +143,7 @@ class HGraph : public ArenaObject<kArenaAllocMisc> { dex_file_(dex_file), method_idx_(method_idx), invoke_type_(invoke_type), + in_ssa_form_(false), should_generate_constructor_barrier_(should_generate_constructor_barrier), cached_null_constant_(nullptr), cached_int_constants_(std::less<int32_t>(), arena->Adapter()), @@ -174,6 +175,7 @@ class HGraph : public ArenaObject<kArenaAllocMisc> { // users remaining when being visited. if (!AnalyzeNaturalLoops()) return false; TransformToSsa(); + in_ssa_form_ = true; return true; } @@ -216,11 +218,16 @@ class HGraph : public ArenaObject<kArenaAllocMisc> { maximum_number_of_out_vregs_ = new_value; } + void UpdateMaximumNumberOfOutVRegs(uint16_t other_value) { + maximum_number_of_out_vregs_ = std::max(maximum_number_of_out_vregs_, other_value); + } + void UpdateTemporariesVRegSlots(size_t slots) { temporaries_vreg_slots_ = std::max(slots, temporaries_vreg_slots_); } size_t GetTemporariesVRegSlots() const { + DCHECK(!in_ssa_form_); return temporaries_vreg_slots_; } @@ -229,6 +236,7 @@ class HGraph : public ArenaObject<kArenaAllocMisc> { } uint16_t GetNumberOfVRegs() const { + DCHECK(!in_ssa_form_); return number_of_vregs_; } @@ -237,6 +245,7 @@ class HGraph : public ArenaObject<kArenaAllocMisc> { } uint16_t GetNumberOfLocalVRegs() const { + DCHECK(!in_ssa_form_); return number_of_vregs_ - number_of_in_vregs_; } @@ -381,6 +390,11 @@ class HGraph : public ArenaObject<kArenaAllocMisc> { // If inlined, this encodes how the callee is being invoked. const InvokeType invoke_type_; + // Whether the graph has been transformed to SSA form. Only used + // in debug mode to ensure we are not using properties only valid + // for non-SSA form (like the number of temporaries). + bool in_ssa_form_; + const bool should_generate_constructor_barrier_; // Cached constants. @@ -1121,14 +1135,16 @@ class HEnvironment : public ArenaObject<kArenaAllocMisc> { const DexFile& dex_file, uint32_t method_idx, uint32_t dex_pc, - InvokeType invoke_type) + InvokeType invoke_type, + HInstruction* holder) : vregs_(arena, number_of_vregs), locations_(arena, number_of_vregs), parent_(nullptr), dex_file_(dex_file), method_idx_(method_idx), dex_pc_(dex_pc), - invoke_type_(invoke_type) { + invoke_type_(invoke_type), + holder_(holder) { vregs_.SetSize(number_of_vregs); for (size_t i = 0; i < number_of_vregs; i++) { vregs_.Put(i, HUserRecord<HEnvironment*>()); @@ -1140,19 +1156,24 @@ class HEnvironment : public ArenaObject<kArenaAllocMisc> { } } - HEnvironment(ArenaAllocator* arena, const HEnvironment& to_copy) + HEnvironment(ArenaAllocator* arena, const HEnvironment& to_copy, HInstruction* holder) : HEnvironment(arena, to_copy.Size(), to_copy.GetDexFile(), to_copy.GetMethodIdx(), to_copy.GetDexPc(), - to_copy.GetInvokeType()) {} + to_copy.GetInvokeType(), + holder) {} void SetAndCopyParentChain(ArenaAllocator* allocator, HEnvironment* parent) { - parent_ = new (allocator) HEnvironment(allocator, *parent); - parent_->CopyFrom(parent); - if (parent->GetParent() != nullptr) { - parent_->SetAndCopyParentChain(allocator, parent->GetParent()); + if (parent_ != nullptr) { + parent_->SetAndCopyParentChain(allocator, parent); + } else { + parent_ = new (allocator) HEnvironment(allocator, *parent, holder_); + parent_->CopyFrom(parent); + if (parent->GetParent() != nullptr) { + parent_->SetAndCopyParentChain(allocator, parent->GetParent()); + } } } @@ -1202,6 +1223,10 @@ class HEnvironment : public ArenaObject<kArenaAllocMisc> { return dex_file_; } + HInstruction* GetHolder() const { + return holder_; + } + private: // Record instructions' use entries of this environment for constant-time removal. // It should only be called by HInstruction when a new environment use is added. @@ -1219,6 +1244,10 @@ class HEnvironment : public ArenaObject<kArenaAllocMisc> { const uint32_t dex_pc_; const InvokeType invoke_type_; + // The instruction that holds this environment. Only used in debug mode + // to ensure the graph is consistent. + HInstruction* const holder_; + friend class HInstruction; DISALLOW_COPY_AND_ASSIGN(HEnvironment); @@ -1425,13 +1454,18 @@ class HInstruction : public ArenaObject<kArenaAllocMisc> { HEnvironment* GetEnvironment() const { return environment_; } // Set the `environment_` field. Raw because this method does not // update the uses lists. - void SetRawEnvironment(HEnvironment* environment) { environment_ = environment; } + void SetRawEnvironment(HEnvironment* environment) { + DCHECK(environment_ == nullptr); + DCHECK_EQ(environment->GetHolder(), this); + environment_ = environment; + } // Set the environment of this instruction, copying it from `environment`. While // copying, the uses lists are being updated. void CopyEnvironmentFrom(HEnvironment* environment) { + DCHECK(environment_ == nullptr); ArenaAllocator* allocator = GetBlock()->GetGraph()->GetArena(); - environment_ = new (allocator) HEnvironment(allocator, *environment); + environment_ = new (allocator) HEnvironment(allocator, *environment, this); environment_->CopyFrom(environment); if (environment->GetParent() != nullptr) { environment_->SetAndCopyParentChain(allocator, environment->GetParent()); @@ -1440,8 +1474,9 @@ class HInstruction : public ArenaObject<kArenaAllocMisc> { void CopyEnvironmentFromWithLoopPhiAdjustment(HEnvironment* environment, HBasicBlock* block) { + DCHECK(environment_ == nullptr); ArenaAllocator* allocator = GetBlock()->GetGraph()->GetArena(); - environment_ = new (allocator) HEnvironment(allocator, *environment); + environment_ = new (allocator) HEnvironment(allocator, *environment, this); environment_->CopyFromWithLoopPhiAdjustment(environment, block); if (environment->GetParent() != nullptr) { environment_->SetAndCopyParentChain(allocator, environment->GetParent()); @@ -2420,6 +2455,12 @@ class HInvoke : public HInstruction { intrinsic_ = intrinsic; } + bool IsInlined() const { + return GetEnvironment()->GetParent() != nullptr; + } + + bool CanThrow() const OVERRIDE { return true; } + DECLARE_INSTRUCTION(Invoke); protected: diff --git a/compiler/optimizing/nodes_test.cc b/compiler/optimizing/nodes_test.cc index 782cde486a..fef77aa0ed 100644 --- a/compiler/optimizing/nodes_test.cc +++ b/compiler/optimizing/nodes_test.cc @@ -51,7 +51,7 @@ TEST(Node, RemoveInstruction) { exit_block->AddInstruction(new (&allocator) HExit()); HEnvironment* environment = new (&allocator) HEnvironment( - &allocator, 1, graph->GetDexFile(), graph->GetMethodIdx(), 0, kStatic); + &allocator, 1, graph->GetDexFile(), graph->GetMethodIdx(), 0, kStatic, null_check); null_check->SetRawEnvironment(environment); environment->SetRawEnvAt(0, parameter); parameter->AddEnvUseAt(null_check->GetEnvironment(), 0); @@ -132,7 +132,7 @@ TEST(Node, ParentEnvironment) { ASSERT_TRUE(parameter1->GetUses().HasOnlyOneUse()); HEnvironment* environment = new (&allocator) HEnvironment( - &allocator, 1, graph->GetDexFile(), graph->GetMethodIdx(), 0, kStatic); + &allocator, 1, graph->GetDexFile(), graph->GetMethodIdx(), 0, kStatic, with_environment); GrowableArray<HInstruction*> array(&allocator, 1); array.Add(parameter1); @@ -143,13 +143,13 @@ TEST(Node, ParentEnvironment) { ASSERT_TRUE(parameter1->GetEnvUses().HasOnlyOneUse()); HEnvironment* parent1 = new (&allocator) HEnvironment( - &allocator, 1, graph->GetDexFile(), graph->GetMethodIdx(), 0, kStatic); + &allocator, 1, graph->GetDexFile(), graph->GetMethodIdx(), 0, kStatic, nullptr); parent1->CopyFrom(array); ASSERT_EQ(parameter1->GetEnvUses().SizeSlow(), 2u); HEnvironment* parent2 = new (&allocator) HEnvironment( - &allocator, 1, graph->GetDexFile(), graph->GetMethodIdx(), 0, kStatic); + &allocator, 1, graph->GetDexFile(), graph->GetMethodIdx(), 0, kStatic, nullptr); parent2->CopyFrom(array); parent1->SetAndCopyParentChain(&allocator, parent2); diff --git a/compiler/optimizing/prepare_for_register_allocation.cc b/compiler/optimizing/prepare_for_register_allocation.cc index 538736be37..a249aa9711 100644 --- a/compiler/optimizing/prepare_for_register_allocation.cc +++ b/compiler/optimizing/prepare_for_register_allocation.cc @@ -88,7 +88,11 @@ void PrepareForRegisterAllocation::VisitInvokeStaticOrDirect(HInvokeStaticOrDire // The static call will initialize the class so there's no need for a clinit check if // it's the first user. - if (last_input == invoke->GetPrevious()) { + // There is one special case where we still need the clinit check, when inlining. Because + // currently the callee is responsible for reporting parameters to the GC, the code + // that walks the stack during `artQuickResolutionTrampoline` cannot be interrupted for GC. + // Therefore we cannot allocate any object in that code, including loading a new class. + if (last_input == invoke->GetPrevious() && !invoke->IsInlined()) { last_input->SetMustGenerateClinitCheck(false); } @@ -102,7 +106,7 @@ void PrepareForRegisterAllocation::VisitInvokeStaticOrDirect(HInvokeStaticOrDire // If the load class instruction is no longer used, remove it from // the graph. - if (!last_input->HasUses()) { + if (!last_input->HasUses() && !(last_input->MustGenerateClinitCheck() && invoke->IsInlined())) { last_input->GetBlock()->RemoveInstruction(last_input); } } diff --git a/compiler/optimizing/register_allocator.cc b/compiler/optimizing/register_allocator.cc index d4ff4d8dee..9a859bf35f 100644 --- a/compiler/optimizing/register_allocator.cc +++ b/compiler/optimizing/register_allocator.cc @@ -1583,7 +1583,7 @@ void RegisterAllocator::ConnectSiblings(LiveInterval* interval) { while (env_use != nullptr && env_use->GetPosition() <= range->GetEnd()) { DCHECK(current->CoversSlow(env_use->GetPosition()) || (env_use->GetPosition() == range->GetEnd())); - HEnvironment* environment = env_use->GetUser()->GetEnvironment(); + HEnvironment* environment = env_use->GetEnvironment(); environment->SetLocationAt(env_use->GetInputIndex(), source); env_use = env_use->GetNext(); } diff --git a/compiler/optimizing/ssa_builder.cc b/compiler/optimizing/ssa_builder.cc index c51d248765..c4612af393 100644 --- a/compiler/optimizing/ssa_builder.cc +++ b/compiler/optimizing/ssa_builder.cc @@ -548,7 +548,8 @@ void SsaBuilder::VisitInstruction(HInstruction* instruction) { GetGraph()->GetDexFile(), GetGraph()->GetMethodIdx(), instruction->GetDexPc(), - GetGraph()->GetInvokeType()); + GetGraph()->GetInvokeType(), + instruction); environment->CopyFrom(*current_locals_); instruction->SetRawEnvironment(environment); } diff --git a/compiler/optimizing/ssa_liveness_analysis.h b/compiler/optimizing/ssa_liveness_analysis.h index 4b19c5b46a..4cbe29ae42 100644 --- a/compiler/optimizing/ssa_liveness_analysis.h +++ b/compiler/optimizing/ssa_liveness_analysis.h @@ -117,6 +117,7 @@ class UsePosition : public ArenaObject<kArenaAllocMisc> { || user->IsPhi() || (GetPosition() == user->GetLifetimePosition() + 1) || (GetPosition() == user->GetLifetimePosition())); + DCHECK(environment == nullptr || user == nullptr); DCHECK(next_ == nullptr || next->GetPosition() >= GetPosition()); } @@ -128,6 +129,7 @@ class UsePosition : public ArenaObject<kArenaAllocMisc> { void SetNext(UsePosition* next) { next_ = next; } HInstruction* GetUser() const { return user_; } + HEnvironment* GetEnvironment() const { return environment_; } bool GetIsEnvironment() const { return environment_ != nullptr; } bool IsSynthesized() const { return user_ == nullptr; } @@ -280,7 +282,7 @@ class LiveInterval : public ArenaObject<kArenaAllocMisc> { } DCHECK(first_use_->GetPosition() + 1 == position); UsePosition* new_use = new (allocator_) UsePosition( - instruction, environment, input_index, position, cursor->GetNext()); + instruction, nullptr /* environment */, input_index, position, cursor->GetNext()); cursor->SetNext(new_use); if (first_range_->GetEnd() == first_use_->GetPosition()) { first_range_->end_ = position; @@ -290,10 +292,10 @@ class LiveInterval : public ArenaObject<kArenaAllocMisc> { if (is_environment) { first_env_use_ = new (allocator_) UsePosition( - instruction, environment, input_index, position, first_env_use_); + nullptr /* instruction */, environment, input_index, position, first_env_use_); } else { first_use_ = new (allocator_) UsePosition( - instruction, environment, input_index, position, first_use_); + instruction, nullptr /* environment */, input_index, position, first_use_); } if (is_environment && !keep_alive) { diff --git a/runtime/entrypoints/entrypoint_utils-inl.h b/runtime/entrypoints/entrypoint_utils-inl.h index 625e695ce4..526fb8d11f 100644 --- a/runtime/entrypoints/entrypoint_utils-inl.h +++ b/runtime/entrypoints/entrypoint_utils-inl.h @@ -38,6 +38,29 @@ namespace art { +inline mirror::ArtMethod* GetResolvedMethod(mirror::ArtMethod* outer_method, + uint32_t method_index, + InvokeType invoke_type) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + mirror::ArtMethod* caller = outer_method->GetDexCacheResolvedMethod(method_index); + if (!caller->IsRuntimeMethod()) { + return caller; + } + + // The method in the dex cache can be the runtime method responsible for invoking + // the stub that will then update the dex cache. Therefore, we need to do the + // resolution ourselves. + + StackHandleScope<3> hs(Thread::Current()); + ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); + Handle<mirror::ArtMethod> outer(hs.NewHandle(outer_method)); + Handle<mirror::ClassLoader> class_loader(hs.NewHandle(outer->GetClassLoader())); + Handle<mirror::DexCache> dex_cache(hs.NewHandle(outer->GetDexCache())); + Handle<mirror::ArtMethod> referrer; + return class_linker->ResolveMethod( + *outer->GetDexFile(), method_index, dex_cache, class_loader, referrer, invoke_type); +} + inline mirror::ArtMethod* GetCalleeSaveMethodCaller(StackReference<mirror::ArtMethod>* sp, Runtime::CalleeSaveType type, bool do_caller_check = false) @@ -47,7 +70,25 @@ inline mirror::ArtMethod* GetCalleeSaveMethodCaller(StackReference<mirror::ArtMe const size_t callee_frame_size = GetCalleeSaveFrameSize(kRuntimeISA, type); auto* caller_sp = reinterpret_cast<StackReference<mirror::ArtMethod>*>( reinterpret_cast<uintptr_t>(sp) + callee_frame_size); - auto* caller = caller_sp->AsMirrorPtr(); + mirror::ArtMethod* outer_method = caller_sp->AsMirrorPtr(); + mirror::ArtMethod* caller = outer_method; + + if ((outer_method != nullptr) && outer_method->IsOptimized(sizeof(void*))) { + const size_t callee_return_pc_offset = GetCalleeSaveReturnPcOffset(kRuntimeISA, type); + uintptr_t caller_pc = *reinterpret_cast<uintptr_t*>( + (reinterpret_cast<uint8_t*>(sp) + callee_return_pc_offset)); + uintptr_t native_pc_offset = outer_method->NativeQuickPcOffset(caller_pc); + CodeInfo code_info = outer_method->GetOptimizedCodeInfo(); + StackMap stack_map = code_info.GetStackMapForNativePcOffset(native_pc_offset); + DCHECK(stack_map.IsValid()); + if (stack_map.HasInlineInfo(code_info)) { + InlineInfo inline_info = code_info.GetInlineInfoOf(stack_map); + uint32_t method_index = inline_info.GetMethodIndexAtDepth(inline_info.GetDepth() - 1); + InvokeType invoke_type = static_cast<InvokeType>( + inline_info.GetInvokeTypeAtDepth(inline_info.GetDepth() - 1)); + caller = GetResolvedMethod(outer_method, method_index, invoke_type); + } + } if (kIsDebugBuild && do_caller_check) { // Note that do_caller_check is optional, as this method can be called by diff --git a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc index c029eeb74e..33d70657d6 100644 --- a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc +++ b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc @@ -297,10 +297,37 @@ class QuickArgumentVisitor { return GetCalleeSaveMethodCaller(sp, Runtime::kRefsAndArgs); } + static mirror::ArtMethod* GetOuterMethod(StackReference<mirror::ArtMethod>* sp) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + DCHECK(sp->AsMirrorPtr()->IsCalleeSaveMethod()); + uint8_t* previous_sp = + reinterpret_cast<uint8_t*>(sp) + kQuickCalleeSaveFrame_RefAndArgs_FrameSize; + return reinterpret_cast<StackReference<mirror::ArtMethod>*>(previous_sp)->AsMirrorPtr(); + } + static uint32_t GetCallingDexPc(StackReference<mirror::ArtMethod>* sp) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { DCHECK(sp->AsMirrorPtr()->IsCalleeSaveMethod()); - return GetCallingMethod(sp)->ToDexPc(QuickArgumentVisitor::GetCallingPc(sp)); + const size_t callee_frame_size = GetCalleeSaveFrameSize(kRuntimeISA, Runtime::kRefsAndArgs); + auto* caller_sp = reinterpret_cast<StackReference<mirror::ArtMethod>*>( + reinterpret_cast<uintptr_t>(sp) + callee_frame_size); + mirror::ArtMethod* outer_method = caller_sp->AsMirrorPtr(); + uintptr_t outer_pc = QuickArgumentVisitor::GetCallingPc(sp); + uintptr_t outer_pc_offset = outer_method->NativeQuickPcOffset(outer_pc); + + if (outer_method->IsOptimized(sizeof(void*))) { + CodeInfo code_info = outer_method->GetOptimizedCodeInfo(); + StackMap stack_map = code_info.GetStackMapForNativePcOffset(outer_pc_offset); + DCHECK(stack_map.IsValid()); + if (stack_map.HasInlineInfo(code_info)) { + InlineInfo inline_info = code_info.GetInlineInfoOf(stack_map); + return inline_info.GetDexPcAtDepth(inline_info.GetDepth() - 1); + } else { + return stack_map.GetDexPc(code_info); + } + } else { + return outer_method->ToDexPc(outer_pc); + } } // For the given quick ref and args quick frame, return the caller's PC. @@ -2068,7 +2095,11 @@ extern "C" TwoWordReturn artInvokeInterfaceTrampoline(uint32_t dex_method_idx, StackReference<mirror::ArtMethod>* sp) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { ScopedQuickEntrypointChecks sqec(self); - mirror::ArtMethod* caller_method = QuickArgumentVisitor::GetCallingMethod(sp); + // The optimizing compiler currently does not inline methods that have an interface + // invocation. We use the outer method directly to avoid fetching a stack map, which is + // more expensive. + mirror::ArtMethod* caller_method = QuickArgumentVisitor::GetOuterMethod(sp); + DCHECK_EQ(caller_method, QuickArgumentVisitor::GetCallingMethod(sp)); mirror::ArtMethod* interface_method = caller_method->GetDexCacheResolvedMethod(dex_method_idx); mirror::ArtMethod* method; if (LIKELY(interface_method->GetDexMethodIndex() != DexFile::kDexNoIndex)) { diff --git a/runtime/stack.cc b/runtime/stack.cc index f7b96eaf2e..09b56a1181 100644 --- a/runtime/stack.cc +++ b/runtime/stack.cc @@ -18,6 +18,7 @@ #include "arch/context.h" #include "base/hex_dump.h" +#include "entrypoints/entrypoint_utils-inl.h" #include "entrypoints/runtime_asm_entrypoints.h" #include "gc_map.h" #include "mirror/art_method-inl.h" @@ -119,8 +120,11 @@ mirror::ArtMethod* StackVisitor::GetMethod() const SHARED_LOCKS_REQUIRED(Locks:: } else if (cur_quick_frame_ != nullptr) { if (IsInInlinedFrame()) { size_t depth_in_stack_map = current_inlining_depth_ - 1; - return GetCurrentQuickFrame()->AsMirrorPtr()->GetDexCacheResolvedMethod( - GetCurrentInlineInfo().GetMethodIndexAtDepth(depth_in_stack_map)); + InlineInfo inline_info = GetCurrentInlineInfo(); + uint32_t method_index = inline_info.GetMethodIndexAtDepth(depth_in_stack_map); + InvokeType invoke_type = + static_cast<InvokeType>(inline_info.GetInvokeTypeAtDepth(depth_in_stack_map)); + return GetResolvedMethod(GetCurrentQuickFrame()->AsMirrorPtr(), method_index, invoke_type); } else { return cur_quick_frame_->AsMirrorPtr(); } @@ -761,6 +765,7 @@ void StackVisitor::WalkStack(bool include_transitions) { if (UNLIKELY(!should_continue)) { return; } + cur_depth_++; } } } diff --git a/test/004-StackWalk/src/Main.java b/test/004-StackWalk/src/Main.java index 1e2a91b5a1..782f51d2d3 100644 --- a/test/004-StackWalk/src/Main.java +++ b/test/004-StackWalk/src/Main.java @@ -2,9 +2,14 @@ public class Main { public Main() { } - int f() { + int f() throws Exception { g(1); g(2); + + // This loop currently defeats inlining of `f`. + for (int i = 0; i < 10; i++) { + Thread.sleep(0); + } return 0; } @@ -86,7 +91,7 @@ public class Main { System.loadLibrary("arttest"); } - public static void main(String[] args) { + public static void main(String[] args) throws Exception { Main st = new Main(); st.f(); } diff --git a/test/004-StackWalk/stack_walk_jni.cc b/test/004-StackWalk/stack_walk_jni.cc index c40de7e247..97afe1c310 100644 --- a/test/004-StackWalk/stack_walk_jni.cc +++ b/test/004-StackWalk/stack_walk_jni.cc @@ -45,11 +45,11 @@ class TestReferenceMapVisitor : public CheckReferenceMapVisitor { if (m_name == "f") { if (gJava_StackWalk_refmap_calls == 1) { CHECK_EQ(1U, GetDexPc()); - CHECK_REGS(1); + CHECK_REGS(4); } else { CHECK_EQ(gJava_StackWalk_refmap_calls, 2); CHECK_EQ(5U, GetDexPc()); - CHECK_REGS(1); + CHECK_REGS(4); } } else if (m_name == "g") { if (gJava_StackWalk_refmap_calls == 1) { diff --git a/test/466-get-live-vreg/src/Main.java b/test/466-get-live-vreg/src/Main.java index 3118085efd..851506bf8a 100644 --- a/test/466-get-live-vreg/src/Main.java +++ b/test/466-get-live-vreg/src/Main.java @@ -53,18 +53,30 @@ public class Main { } public static void main(String[] args) { - if (testLiveArgument(42) != 42) { - throw new Error("Expected 42"); + if (testLiveArgument(staticField3) != staticField3) { + throw new Error("Expected " + staticField3); } - if (testLiveArgument(42) != 42) { - throw new Error("Expected 42"); + if (testLiveArgument(staticField3) != staticField3) { + throw new Error("Expected " + staticField3); } - testIntervalHole(1, true); - testIntervalHole(1, false); + testWrapperIntervalHole(1, true); + testWrapperIntervalHole(1, false); + } + + // Wrapper method to avoid inlining, which affects liveness + // of dex registers. + static void testWrapperIntervalHole(int arg, boolean test) { + try { + Thread.sleep(0); + testIntervalHole(arg, test); + } catch (Exception e) { + throw new Error(e); + } } static int staticField1; static int staticField2; + static int staticField3 = 42; } diff --git a/test/478-checker-clinit-check-pruning/src/Main.java b/test/478-checker-clinit-check-pruning/src/Main.java index d5592aa49b..51be912b96 100644 --- a/test/478-checker-clinit-check-pruning/src/Main.java +++ b/test/478-checker-clinit-check-pruning/src/Main.java @@ -167,7 +167,7 @@ public class Main { static void invokeStaticNotInlined() { // The invocation of invokeStaticNotInlined triggers the // initialization of ClassWithClinit4, meaning that the - // hereinbelow call to staticMethod does not need a clinit + // call to staticMethod below does not need a clinit // check. staticMethod(); } diff --git a/test/487-checker-inline-calls/expected.txt b/test/487-checker-inline-calls/expected.txt new file mode 100644 index 0000000000..2230482312 --- /dev/null +++ b/test/487-checker-inline-calls/expected.txt @@ -0,0 +1,6 @@ +java.lang.Error + at Main.inline3(Main.java:48) + at Main.inline2(Main.java:44) + at Main.inline1(Main.java:40) + at Main.doTopCall(Main.java:36) + at Main.main(Main.java:21) diff --git a/test/487-checker-inline-calls/info.txt b/test/487-checker-inline-calls/info.txt new file mode 100644 index 0000000000..9f5df8b97d --- /dev/null +++ b/test/487-checker-inline-calls/info.txt @@ -0,0 +1 @@ +Checker test for ensuring inlining preserves stack traces. diff --git a/test/487-checker-inline-calls/src/Main.java b/test/487-checker-inline-calls/src/Main.java new file mode 100644 index 0000000000..70384d5a7f --- /dev/null +++ b/test/487-checker-inline-calls/src/Main.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2015 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) { + try { + doTopCall(); + } catch (Error e) { + e.printStackTrace(); + } + } + + // We check that some inlining happened by checking the + // method index of the called method. + + /// CHECK-START: void Main.doTopCall() inliner (before) + /// CHECK: InvokeStaticOrDirect dex_file_index:2 + + /// CHECK-START: void Main.doTopCall() inliner (after) + /// CHECK: InvokeStaticOrDirect dex_file_index:4 + public static void doTopCall() { + inline1(); + } + + public static void inline1() { + inline2(); + } + + public static void inline2() { + inline3(); + } + + public static void inline3() { + throw new Error(); + } +} diff --git a/test/488-checker-inline-recursive-calls/expected.txt b/test/488-checker-inline-recursive-calls/expected.txt new file mode 100644 index 0000000000..f615d3a9a2 --- /dev/null +++ b/test/488-checker-inline-recursive-calls/expected.txt @@ -0,0 +1,7 @@ +java.lang.Error + at Main.inline3(Main.java:51) + at Main.doTopCall(Main.java:37) + at Main.inline2(Main.java:47) + at Main.inline1(Main.java:43) + at Main.doTopCall(Main.java:34) + at Main.main(Main.java:21) diff --git a/test/488-checker-inline-recursive-calls/info.txt b/test/488-checker-inline-recursive-calls/info.txt new file mode 100644 index 0000000000..9abd93cc07 --- /dev/null +++ b/test/488-checker-inline-recursive-calls/info.txt @@ -0,0 +1 @@ +Checker test for inlining calls that in turn call the outer method. diff --git a/test/488-checker-inline-recursive-calls/src/Main.java b/test/488-checker-inline-recursive-calls/src/Main.java new file mode 100644 index 0000000000..c1f25b3004 --- /dev/null +++ b/test/488-checker-inline-recursive-calls/src/Main.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2015 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) { + try { + doTopCall(true); + } catch (Error e) { + e.printStackTrace(); + } + } + + /// CHECK-START: void Main.doTopCall(boolean) inliner (before) + /// CHECK-NOT: InvokeStaticOrDirect recursive:true + + /// CHECK-START: void Main.doTopCall(boolean) inliner (after) + /// CHECK: InvokeStaticOrDirect recursive:true + public static void doTopCall(boolean first_call) { + if (first_call) { + inline1(); + } else { + while (true) { + inline3(); + } + } + } + + public static void inline1() { + inline2(); + } + + public static void inline2() { + doTopCall(false); + } + + public static void inline3() { + throw new Error(); + } +} |