Support for inlining methods that call/throw.
Mostly fixes here and there to make it working.
Change-Id: I1b535e895105d78b65634636d675b818551f783e
diff --git a/compiler/optimizing/builder.cc b/compiler/optimizing/builder.cc
index f858f82..365599f 100644
--- a/compiler/optimizing/builder.cc
+++ b/compiler/optimizing/builder.cc
@@ -652,8 +652,8 @@
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 7da4f2d..fd2e4e8 100644
--- a/compiler/optimizing/graph_visualizer.cc
+++ b/compiler/optimizing/graph_visualizer.cc
@@ -280,6 +280,13 @@
<< 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 997f980..15f3deb 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 @@
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 @@
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 @@
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::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 @@
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 @@
!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 @@
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 1dbc7d3..09a36c6 100644
--- a/compiler/optimizing/inliner.h
+++ b/compiler/optimizing/inliner.h
@@ -51,7 +51,7 @@
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 80d4b4a..06f6a7f 100644
--- a/compiler/optimizing/nodes.cc
+++ b/compiler/optimizing/nodes.cc
@@ -1314,6 +1314,29 @@
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 3144c5c..005d50e 100644
--- a/compiler/optimizing/nodes.h
+++ b/compiler/optimizing/nodes.h
@@ -143,6 +143,7 @@
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 @@
// users remaining when being visited.
if (!AnalyzeNaturalLoops()) return false;
TransformToSsa();
+ in_ssa_form_ = true;
return true;
}
@@ -216,11 +218,16 @@
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 @@
}
uint16_t GetNumberOfVRegs() const {
+ DCHECK(!in_ssa_form_);
return number_of_vregs_;
}
@@ -237,6 +245,7 @@
}
uint16_t GetNumberOfLocalVRegs() const {
+ DCHECK(!in_ssa_form_);
return number_of_vregs_ - number_of_in_vregs_;
}
@@ -381,6 +390,11 @@
// 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 @@
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 @@
}
}
- 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 @@
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 @@
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 @@
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 @@
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 @@
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 782cde4..fef77aa 100644
--- a/compiler/optimizing/nodes_test.cc
+++ b/compiler/optimizing/nodes_test.cc
@@ -51,7 +51,7 @@
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 @@
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 @@
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 538736b..a249aa9 100644
--- a/compiler/optimizing/prepare_for_register_allocation.cc
+++ b/compiler/optimizing/prepare_for_register_allocation.cc
@@ -88,7 +88,11 @@
// 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 @@
// 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 d4ff4d8..9a859bf 100644
--- a/compiler/optimizing/register_allocator.cc
+++ b/compiler/optimizing/register_allocator.cc
@@ -1583,7 +1583,7 @@
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 c51d248..c4612af 100644
--- a/compiler/optimizing/ssa_builder.cc
+++ b/compiler/optimizing/ssa_builder.cc
@@ -548,7 +548,8 @@
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 4b19c5b..4cbe29a 100644
--- a/compiler/optimizing/ssa_liveness_analysis.h
+++ b/compiler/optimizing/ssa_liveness_analysis.h
@@ -117,6 +117,7 @@
|| user->IsPhi()
|| (GetPosition() == user->GetLifetimePosition() + 1)
|| (GetPosition() == user->GetLifetimePosition()));
+ DCHECK(environment == nullptr || user == nullptr);
DCHECK(next_ == nullptr || next->GetPosition() >= GetPosition());
}
@@ -128,6 +129,7 @@
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 @@
}
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 @@
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 625e695..526fb8d 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 @@
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 c029eeb..33d7065 100644
--- a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
+++ b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
@@ -297,10 +297,37 @@
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 @@
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 f7b96ea..09b56a1 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 @@
} 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 @@
if (UNLIKELY(!should_continue)) {
return;
}
+ cur_depth_++;
}
}
}
diff --git a/test/004-StackWalk/src/Main.java b/test/004-StackWalk/src/Main.java
index 1e2a91b..782f51d 100644
--- a/test/004-StackWalk/src/Main.java
+++ b/test/004-StackWalk/src/Main.java
@@ -2,9 +2,14 @@
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 @@
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 c40de7e..97afe1c 100644
--- a/test/004-StackWalk/stack_walk_jni.cc
+++ b/test/004-StackWalk/stack_walk_jni.cc
@@ -45,11 +45,11 @@
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 3118085..851506b 100644
--- a/test/466-get-live-vreg/src/Main.java
+++ b/test/466-get-live-vreg/src/Main.java
@@ -53,18 +53,30 @@
}
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 d5592aa..51be912 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 @@
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 0000000..2230482
--- /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 0000000..9f5df8b
--- /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 0000000..70384d5
--- /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 0000000..f615d3a
--- /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 0000000..9abd93c
--- /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 0000000..c1f25b3
--- /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();
+ }
+}