From 13439f0c4769a0768cf1bbaa7b3f2f9ee5a317c5 Mon Sep 17 00:00:00 2001 From: Calin Juravle Date: Tue, 21 Feb 2017 01:17:21 -0800 Subject: [PATCH] Use offline inline caches during AOT compilation Also: - extend the testing script to understand profile when --profile is passed - filter inline cache types which are not loaded by the caller class loader Test: m test-art-host-run-test-638-checker-inline-caches Bug: 32434870 Change-Id: Ifcc27b3cebc79b84617412aaae64a73324151b55 --- compiler/driver/compiler_driver.h | 4 + compiler/optimizing/inliner.cc | 303 +++++++++++++++------ compiler/optimizing/inliner.h | 49 ++++ runtime/jit/jit_code_cache.cc | 10 + test/638-checker-inline-caches/expected.txt | 0 test/638-checker-inline-caches/info.txt | 1 + test/638-checker-inline-caches/multidex.jpp | 12 + test/638-checker-inline-caches/profile | 6 + test/638-checker-inline-caches/run | 17 ++ .../src-multidex/SubC.java | 19 ++ test/638-checker-inline-caches/src/Main.java | 192 +++++++++++++ test/638-checker-inline-caches/src/Super.java | 19 ++ test/etc/run-test-jar | 26 +- 13 files changed, 578 insertions(+), 80 deletions(-) create mode 100644 test/638-checker-inline-caches/expected.txt create mode 100644 test/638-checker-inline-caches/info.txt create mode 100644 test/638-checker-inline-caches/multidex.jpp create mode 100644 test/638-checker-inline-caches/profile create mode 100644 test/638-checker-inline-caches/run create mode 100644 test/638-checker-inline-caches/src-multidex/SubC.java create mode 100644 test/638-checker-inline-caches/src/Main.java create mode 100644 test/638-checker-inline-caches/src/Super.java diff --git a/compiler/driver/compiler_driver.h b/compiler/driver/compiler_driver.h index 1e5c43d83..cbde58724 100644 --- a/compiler/driver/compiler_driver.h +++ b/compiler/driver/compiler_driver.h @@ -355,6 +355,10 @@ class CompilerDriver { return current_dex_to_dex_methods_; } + const ProfileCompilationInfo* GetProfileCompilationInfo() const { + return profile_compilation_info_; + } + private: // Can `referrer_class` access the resolved `member`? // Dispatch call to mirror::Class::CanAccessResolvedField or diff --git a/compiler/optimizing/inliner.cc b/compiler/optimizing/inliner.cc index 0b96005a1..664b95aeb 100644 --- a/compiler/optimizing/inliner.cc +++ b/compiler/optimizing/inliner.cc @@ -249,20 +249,25 @@ class ScopedProfilingInfoInlineUse { ProfilingInfo* const profiling_info_; }; -static bool IsMonomorphic(Handle> classes) - REQUIRES_SHARED(Locks::mutator_lock_) { - DCHECK_GE(InlineCache::kIndividualCacheSize, 2); - return classes->Get(0) != nullptr && classes->Get(1) == nullptr; -} - -static bool IsMegamorphic(Handle> classes) - REQUIRES_SHARED(Locks::mutator_lock_) { - for (size_t i = 0; i < InlineCache::kIndividualCacheSize; ++i) { - if (classes->Get(i) == nullptr) { - return false; +HInliner::InlineCacheType HInliner::GetInlineCacheType( + const Handle>& classes) + REQUIRES_SHARED(Locks::mutator_lock_) { + uint8_t number_of_types = 0; + for (; number_of_types < InlineCache::kIndividualCacheSize; ++number_of_types) { + if (classes->Get(number_of_types) == nullptr) { + break; } } - return true; + + if (number_of_types == 0) { + return kInlineCacheUninitialized; + } else if (number_of_types == 1) { + return kInlineCacheMonomorphic; + } else if (number_of_types == InlineCache::kIndividualCacheSize) { + return kInlineCacheMegamorphic; + } else { + return kInlineCachePolymorphic; + } } static mirror::Class* GetMonomorphicType(Handle> classes) @@ -271,18 +276,6 @@ static mirror::Class* GetMonomorphicType(HandleGet(0); } -static bool IsUninitialized(Handle> classes) - REQUIRES_SHARED(Locks::mutator_lock_) { - return classes->Get(0) == nullptr; -} - -static bool IsPolymorphic(Handle> classes) - REQUIRES_SHARED(Locks::mutator_lock_) { - DCHECK_GE(InlineCache::kIndividualCacheSize, 3); - return classes->Get(1) != nullptr && - classes->Get(InlineCache::kIndividualCacheSize - 1) == nullptr; -} - ArtMethod* HInliner::TryCHADevirtualization(ArtMethod* resolved_method) { if (!resolved_method->HasSingleImplementation()) { return nullptr; @@ -353,67 +346,209 @@ bool HInliner::TryInline(HInvoke* invoke_instruction) { } return result; } - DCHECK(!invoke_instruction->IsInvokeStaticOrDirect()); - // Check if we can use an inline cache. - ArtMethod* caller = graph_->GetArtMethod(); - if (Runtime::Current()->UseJitCompilation()) { - // Under JIT, we should always know the caller. - DCHECK(caller != nullptr); - ScopedProfilingInfoInlineUse spiis(caller, soa.Self()); - ProfilingInfo* profiling_info = spiis.GetProfilingInfo(); - if (profiling_info != nullptr) { - StackHandleScope<1> hs(soa.Self()); - ClassLinker* class_linker = caller_compilation_unit_.GetClassLinker(); - Handle> inline_cache = hs.NewHandle( - mirror::ObjectArray::Alloc( - soa.Self(), - class_linker->GetClassRoot(ClassLinker::kClassArrayClass), - InlineCache::kIndividualCacheSize)); - if (inline_cache == nullptr) { - // We got an OOME. Just clear the exception, and don't inline. - DCHECK(soa.Self()->IsExceptionPending()); - soa.Self()->ClearException(); - VLOG(compiler) << "Out of memory in the compiler when trying to inline"; - return false; + // Try using inline caches. + return TryInlineFromInlineCache(caller_dex_file, invoke_instruction, resolved_method); +} + +static Handle> AllocateInlineCacheHolder( + const DexCompilationUnit& compilation_unit, + StackHandleScope<1>* hs) + REQUIRES_SHARED(Locks::mutator_lock_) { + Thread* self = Thread::Current(); + ClassLinker* class_linker = compilation_unit.GetClassLinker(); + Handle> inline_cache = hs->NewHandle( + mirror::ObjectArray::Alloc( + self, + class_linker->GetClassRoot(ClassLinker::kClassArrayClass), + InlineCache::kIndividualCacheSize)); + if (inline_cache == nullptr) { + // We got an OOME. Just clear the exception, and don't inline. + DCHECK(self->IsExceptionPending()); + self->ClearException(); + VLOG(compiler) << "Out of memory in the compiler when trying to inline"; + } + return inline_cache; +} + +bool HInliner::TryInlineFromInlineCache(const DexFile& caller_dex_file, + HInvoke* invoke_instruction, + ArtMethod* resolved_method) + REQUIRES_SHARED(Locks::mutator_lock_) { + StackHandleScope<1> hs(Thread::Current()); + Handle> inline_cache; + InlineCacheType inline_cache_type = Runtime::Current()->IsAotCompiler() + ? GetInlineCacheAOT(caller_dex_file, invoke_instruction, &hs, &inline_cache) + : GetInlineCacheJIT(invoke_instruction, &hs, &inline_cache); + + switch (inline_cache_type) { + case kInlineCacheNoData: + break; + + case kInlineCacheUninitialized: + VLOG(compiler) << "Interface or virtual call to " + << caller_dex_file.PrettyMethod(invoke_instruction->GetDexMethodIndex()) + << " is not hit and not inlined"; + return false; + + case kInlineCacheMonomorphic: + MaybeRecordStat(kMonomorphicCall); + if (outermost_graph_->IsCompilingOsr()) { + // If we are compiling OSR, we pretend this call is polymorphic, as we may come from the + // interpreter and it may have seen different receiver types. + return TryInlinePolymorphicCall(invoke_instruction, resolved_method, inline_cache); } else { - Runtime::Current()->GetJit()->GetCodeCache()->CopyInlineCacheInto( - *profiling_info->GetInlineCache(invoke_instruction->GetDexPc()), - inline_cache); - if (IsUninitialized(inline_cache)) { - VLOG(compiler) << "Interface or virtual call to " - << caller_dex_file.PrettyMethod(method_index) - << " is not hit and not inlined"; - return false; - } else if (IsMonomorphic(inline_cache)) { - MaybeRecordStat(kMonomorphicCall); - if (outermost_graph_->IsCompilingOsr()) { - // If we are compiling OSR, we pretend this call is polymorphic, as we may come from the - // interpreter and it may have seen different receiver types. - return TryInlinePolymorphicCall(invoke_instruction, resolved_method, inline_cache); - } else { - return TryInlineMonomorphicCall(invoke_instruction, resolved_method, inline_cache); - } - } else if (IsPolymorphic(inline_cache)) { - MaybeRecordStat(kPolymorphicCall); - return TryInlinePolymorphicCall(invoke_instruction, resolved_method, inline_cache); - } else { - DCHECK(IsMegamorphic(inline_cache)); - VLOG(compiler) << "Interface or virtual call to " - << caller_dex_file.PrettyMethod(method_index) - << " is megamorphic and not inlined"; - MaybeRecordStat(kMegamorphicCall); - return false; - } + return TryInlineMonomorphicCall(invoke_instruction, resolved_method, inline_cache); } + + case kInlineCachePolymorphic: + MaybeRecordStat(kPolymorphicCall); + return TryInlinePolymorphicCall(invoke_instruction, resolved_method, inline_cache); + + case kInlineCacheMegamorphic: + VLOG(compiler) << "Interface or virtual call to " + << caller_dex_file.PrettyMethod(invoke_instruction->GetDexMethodIndex()) + << " is megamorphic and not inlined"; + MaybeRecordStat(kMegamorphicCall); + return false; + + case kInlineCacheMissingTypes: + VLOG(compiler) << "Interface or virtual call to " + << caller_dex_file.PrettyMethod(invoke_instruction->GetDexMethodIndex()) + << " is missing types and not inlined"; + return false; + } + UNREACHABLE(); +} + +HInliner::InlineCacheType HInliner::GetInlineCacheJIT( + HInvoke* invoke_instruction, + StackHandleScope<1>* hs, + /*out*/Handle>* inline_cache) + REQUIRES_SHARED(Locks::mutator_lock_) { + DCHECK(Runtime::Current()->UseJitCompilation()); + + ArtMethod* caller = graph_->GetArtMethod(); + // Under JIT, we should always know the caller. + DCHECK(caller != nullptr); + ScopedProfilingInfoInlineUse spiis(caller, Thread::Current()); + ProfilingInfo* profiling_info = spiis.GetProfilingInfo(); + + if (profiling_info == nullptr) { + return kInlineCacheNoData; + } + + *inline_cache = AllocateInlineCacheHolder(caller_compilation_unit_, hs); + if (inline_cache->Get() == nullptr) { + // We can't extract any data if we failed to allocate; + return kInlineCacheNoData; + } else { + Runtime::Current()->GetJit()->GetCodeCache()->CopyInlineCacheInto( + *profiling_info->GetInlineCache(invoke_instruction->GetDexPc()), + *inline_cache); + return GetInlineCacheType(*inline_cache); + } +} + +HInliner::InlineCacheType HInliner::GetInlineCacheAOT( + const DexFile& caller_dex_file, + HInvoke* invoke_instruction, + StackHandleScope<1>* hs, + /*out*/Handle>* inline_cache) + REQUIRES_SHARED(Locks::mutator_lock_) { + DCHECK(Runtime::Current()->IsAotCompiler()); + const ProfileCompilationInfo* pci = compiler_driver_->GetProfileCompilationInfo(); + if (pci == nullptr) { + return kInlineCacheNoData; + } + + ProfileCompilationInfo::OfflineProfileMethodInfo offline_profile; + bool found = pci->GetMethod(caller_dex_file.GetLocation(), + caller_dex_file.GetLocationChecksum(), + caller_compilation_unit_.GetDexMethodIndex(), + &offline_profile); + if (!found) { + return kInlineCacheNoData; // no profile information for this invocation. + } + + *inline_cache = AllocateInlineCacheHolder(caller_compilation_unit_, hs); + if (inline_cache == nullptr) { + // We can't extract any data if we failed to allocate; + return kInlineCacheNoData; + } else { + return ExtractClassesFromOfflineProfile(invoke_instruction, + offline_profile, + *inline_cache); + } +} + +HInliner::InlineCacheType HInliner::ExtractClassesFromOfflineProfile( + const HInvoke* invoke_instruction, + const ProfileCompilationInfo::OfflineProfileMethodInfo& offline_profile, + /*out*/Handle> inline_cache) + REQUIRES_SHARED(Locks::mutator_lock_) { + const auto it = offline_profile.inline_caches.find(invoke_instruction->GetDexPc()); + if (it == offline_profile.inline_caches.end()) { + return kInlineCacheUninitialized; + } + + const ProfileCompilationInfo::DexPcData& dex_pc_data = it->second; + + if (dex_pc_data.is_missing_types) { + return kInlineCacheMissingTypes; + } + if (dex_pc_data.is_megamorphic) { + return kInlineCacheMegamorphic; + } + + DCHECK_LE(dex_pc_data.classes.size(), InlineCache::kIndividualCacheSize); + Thread* self = Thread::Current(); + // We need to resolve the class relative to the containing dex file. + // So first, build a mapping from the index of dex file in the profile to + // its dex cache. This will avoid repeating the lookup when walking over + // the inline cache types. + std::vector> dex_profile_index_to_dex_cache( + offline_profile.dex_references.size()); + for (size_t i = 0; i < offline_profile.dex_references.size(); i++) { + bool found = false; + for (const DexFile* dex_file : compiler_driver_->GetDexFilesForOatFile()) { + if (offline_profile.dex_references[i].MatchesDex(dex_file)) { + dex_profile_index_to_dex_cache[i] = + caller_compilation_unit_.GetClassLinker()->FindDexCache(self, *dex_file); + found = true; + } + } + if (!found) { + VLOG(compiler) << "Could not find profiled dex file: " + << offline_profile.dex_references[i].dex_location; + return kInlineCacheMissingTypes; } } - VLOG(compiler) << "Interface or virtual call to " - << caller_dex_file.PrettyMethod(method_index) - << " could not be statically determined"; - return false; + // Walk over the classes and resolve them. If we cannot find a type we return + // kInlineCacheMissingTypes. + int ic_index = 0; + for (const ProfileCompilationInfo::ClassReference& class_ref : dex_pc_data.classes) { + ObjPtr dex_cache = + dex_profile_index_to_dex_cache[class_ref.dex_profile_index]; + DCHECK(dex_cache != nullptr); + ObjPtr clazz = ClassLinker::LookupResolvedType( + class_ref.type_index, + dex_cache, + caller_compilation_unit_.GetClassLoader().Get()); + if (clazz != nullptr) { + inline_cache->Set(ic_index++, clazz); + } else { + VLOG(compiler) << "Could not resolve class from inline cache in AOT mode " + << caller_compilation_unit_.GetDexFile()->PrettyMethod( + invoke_instruction->GetDexMethodIndex()) << " : " + << caller_compilation_unit_ + .GetDexFile()->StringByTypeIdx(class_ref.type_index); + return kInlineCacheMissingTypes; + } + } + return GetInlineCacheType(inline_cache); } HInstanceFieldGet* HInliner::BuildGetReceiverClass(ClassLinker* class_linker, @@ -556,6 +691,13 @@ HInstruction* HInliner::AddTypeGuard(HInstruction* receiver, // Insert before setting the kind, as setting the kind affects the inputs. bb_cursor->InsertInstructionAfter(load_class, receiver_class); load_class->SetLoadKind(kind); + // In AOT mode, we will most likely load the class from BSS, which will involve a call + // to the runtime. In this case, the load instruction will need an environment so copy + // it from the invoke instruction. + if (load_class->NeedsEnvironment()) { + DCHECK(Runtime::Current()->IsAotCompiler()); + load_class->CopyEnvironmentFrom(invoke_instruction->GetEnvironment()); + } HNotEqual* compare = new (graph_->GetArena()) HNotEqual(load_class, receiver_class); bb_cursor->InsertInstructionAfter(compare, load_class); @@ -746,7 +888,10 @@ bool HInliner::TryInlinePolymorphicCallToSameTarget( ArtMethod* resolved_method, Handle> classes) { // This optimization only works under JIT for now. - DCHECK(Runtime::Current()->UseJitCompilation()); + if (!Runtime::Current()->UseJitCompilation()) { + return false; + } + if (graph_->GetInstructionSet() == kMips64) { // TODO: Support HClassTableGet for mips64. return false; diff --git a/compiler/optimizing/inliner.h b/compiler/optimizing/inliner.h index 75d025ae4..8f8b268cb 100644 --- a/compiler/optimizing/inliner.h +++ b/compiler/optimizing/inliner.h @@ -20,6 +20,7 @@ #include "dex_file_types.h" #include "invoke_type.h" #include "optimization.h" +#include "jit/profile_compilation_info.h" namespace art { @@ -59,6 +60,15 @@ class HInliner : public HOptimization { static constexpr const char* kInlinerPassName = "inliner"; private: + enum InlineCacheType { + kInlineCacheNoData = 0, + kInlineCacheUninitialized = 1, + kInlineCacheMonomorphic = 2, + kInlineCachePolymorphic = 3, + kInlineCacheMegamorphic = 4, + kInlineCacheMissingTypes = 5 + }; + bool TryInline(HInvoke* invoke_instruction); // Try to inline `resolved_method` in place of `invoke_instruction`. `do_rtp` is whether @@ -106,6 +116,45 @@ class HInliner : public HOptimization { HInstruction* obj, HInstruction* value); + // Try inlining the invoke instruction using inline caches. + bool TryInlineFromInlineCache( + const DexFile& caller_dex_file, + HInvoke* invoke_instruction, + ArtMethod* resolved_method) + REQUIRES_SHARED(Locks::mutator_lock_); + + // Try getting the inline cache from JIT code cache. + // Return true if the inline cache was successfully allocated and the + // invoke info was found in the profile info. + InlineCacheType GetInlineCacheJIT( + HInvoke* invoke_instruction, + StackHandleScope<1>* hs, + /*out*/Handle>* inline_cache) + REQUIRES_SHARED(Locks::mutator_lock_); + + // Try getting the inline cache from AOT offline profile. + // Return true if the inline cache was successfully allocated and the + // invoke info was found in the profile info. + InlineCacheType GetInlineCacheAOT(const DexFile& caller_dex_file, + HInvoke* invoke_instruction, + StackHandleScope<1>* hs, + /*out*/Handle>* inline_cache) + REQUIRES_SHARED(Locks::mutator_lock_); + + // Extract the mirror classes from the offline profile and add them to the `inline_cache`. + // Note that even if we have profile data for the invoke the inline_cache might contain + // only null entries if the types cannot be resolved. + InlineCacheType ExtractClassesFromOfflineProfile( + const HInvoke* invoke_instruction, + const ProfileCompilationInfo::OfflineProfileMethodInfo& offline_profile, + /*out*/Handle> inline_cache) + REQUIRES_SHARED(Locks::mutator_lock_); + + // Compute the inline cache type. + InlineCacheType GetInlineCacheType( + const Handle>& classes) + REQUIRES_SHARED(Locks::mutator_lock_); + // Try to inline the target of a monomorphic call. If successful, the code // in the graph will look like: // if (receiver.getClass() != ic.GetMonomorphicType()) deopt diff --git a/runtime/jit/jit_code_cache.cc b/runtime/jit/jit_code_cache.cc index 8b2a2b4d1..e7b23dcfa 100644 --- a/runtime/jit/jit_code_cache.cc +++ b/runtime/jit/jit_code_cache.cc @@ -1262,6 +1262,7 @@ void JitCodeCache::GetProfiledMethods(const std::set& dex_base_loca for (size_t i = 0; i < info->number_of_inline_caches_; ++i) { std::vector profile_classes; const InlineCache& cache = info->cache_[i]; + ArtMethod* caller = info->GetMethod(); bool is_missing_types = false; for (size_t k = 0; k < InlineCache::kIndividualCacheSize; k++) { mirror::Class* cls = cache.classes_[k].Read(); @@ -1269,6 +1270,15 @@ void JitCodeCache::GetProfiledMethods(const std::set& dex_base_loca break; } + // Check if the receiver is in the boot class path or if it's in the + // same class loader as the caller. If not, skip it, as there is not + // much we can do during AOT. + if (!cls->IsBootStrapClassLoaded() && + caller->GetClassLoader() != cls->GetClassLoader()) { + is_missing_types = true; + continue; + } + const DexFile* class_dex_file = nullptr; dex::TypeIndex type_index; diff --git a/test/638-checker-inline-caches/expected.txt b/test/638-checker-inline-caches/expected.txt new file mode 100644 index 000000000..e69de29bb diff --git a/test/638-checker-inline-caches/info.txt b/test/638-checker-inline-caches/info.txt new file mode 100644 index 000000000..1fac6280e --- /dev/null +++ b/test/638-checker-inline-caches/info.txt @@ -0,0 +1 @@ +Verify the use of inline caches in AOT mode. diff --git a/test/638-checker-inline-caches/multidex.jpp b/test/638-checker-inline-caches/multidex.jpp new file mode 100644 index 000000000..69a2cc1ff --- /dev/null +++ b/test/638-checker-inline-caches/multidex.jpp @@ -0,0 +1,12 @@ +Main: + @@com.android.jack.annotations.ForceInMainDex + class Main +Super: + @@com.android.jack.annotations.ForceInMainDex + class Super +SubA: + @@com.android.jack.annotations.ForceInMainDex + class SubA +SubB + @@com.android.jack.annotations.ForceInMainDex + class SubB diff --git a/test/638-checker-inline-caches/profile b/test/638-checker-inline-caches/profile new file mode 100644 index 000000000..1ca6d7b4f --- /dev/null +++ b/test/638-checker-inline-caches/profile @@ -0,0 +1,6 @@ +LMain;->inlineMonomorphicSubA(LSuper;)I+LSubA; +LMain;->inlinePolymophicSubASubB(LSuper;)I+LSubA;,LSubB; +LMain;->inlinePolymophicCrossDexSubASubC(LSuper;)I+LSubA;,LSubC; +LMain;->inlineMegamorphic(LSuper;)I+LSubA;,LSubB;,LSubC;,LSubD;,LSubE; +LMain;->inlineMissingTypes(LSuper;)I+missing_types +LMain;->noInlineCache(LSuper;)I diff --git a/test/638-checker-inline-caches/run b/test/638-checker-inline-caches/run new file mode 100644 index 000000000..146e18000 --- /dev/null +++ b/test/638-checker-inline-caches/run @@ -0,0 +1,17 @@ +#!/bin/bash +# +# Copyright (C) 2017 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. + +exec ${RUN} $@ --profile -Xcompiler-option --compiler-filter=speed-profile diff --git a/test/638-checker-inline-caches/src-multidex/SubC.java b/test/638-checker-inline-caches/src-multidex/SubC.java new file mode 100644 index 000000000..f7e3c08ff --- /dev/null +++ b/test/638-checker-inline-caches/src-multidex/SubC.java @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2017 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 SubC extends Super { + public int getValue() { return 24; } +} diff --git a/test/638-checker-inline-caches/src/Main.java b/test/638-checker-inline-caches/src/Main.java new file mode 100644 index 000000000..2cee47e24 --- /dev/null +++ b/test/638-checker-inline-caches/src/Main.java @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2017 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. + */ + +class SubA extends Super { + int getValue() { return 42; } +} + +class SubB extends Super { + int getValue() { return 38; } +} + +class SubD extends Super { + int getValue() { return 10; } +} + +class SubE extends Super { + int getValue() { return -4; } +} + +public class Main { + + /// CHECK-START: int Main.inlineMonomorphicSubA(Super) inliner (before) + /// CHECK: InvokeVirtual method_name:Super.getValue + + /// CHECK-START: int Main.inlineMonomorphicSubA(Super) inliner (after) + /// CHECK-NOT: InvokeVirtual method_name:Super.getValue + + /// CHECK-START: int Main.inlineMonomorphicSubA(Super) inliner (after) + /// CHECK: <> IntConstant 42 + /// CHECK: <> InstanceFieldGet field_name:java.lang.Object.shadow$_klass_ + /// CHECK: <> LoadClass class_name:SubA + /// CHECK: <> NotEqual [<>,<>] + /// CHECK: Deoptimize [<>] + /// CHECK: Return [<>] + public static int inlineMonomorphicSubA(Super a) { + return a.getValue(); + } + + /// CHECK-START: int Main.inlinePolymophicSubASubB(Super) inliner (before) + /// CHECK: InvokeVirtual method_name:Super.getValue + + /// CHECK-START: int Main.inlinePolymophicSubASubB(Super) inliner (after) + /// CHECK-NOT: InvokeVirtual method_name:Super.getValue + + // Note that the order in which the types are added to the inline cache in the profile matters. + + /// CHECK-START: int Main.inlinePolymophicSubASubB(Super) inliner (after) + /// CHECK-DAG: <> IntConstant 42 + /// CHECK-DAG: <> IntConstant 38 + /// CHECK: <> InstanceFieldGet field_name:java.lang.Object.shadow$_klass_ + /// CHECK: <> LoadClass class_name:SubA + /// CHECK: <> NotEqual [<>,<>] + /// CHECK: If [<>] + + /// CHECK: <> InstanceFieldGet field_name:java.lang.Object.shadow$_klass_ + /// CHECK: <> LoadClass class_name:SubB + /// CHECK: <> NotEqual [<>,<>] + /// CHECK: Deoptimize [<>] + + /// CHECK: <> Phi [<>,<>] + /// CHECK: Return [<>] + public static int inlinePolymophicSubASubB(Super a) { + return a.getValue(); + } + + /// CHECK-START: int Main.inlinePolymophicCrossDexSubASubC(Super) inliner (before) + /// CHECK: InvokeVirtual method_name:Super.getValue + + /// CHECK-START: int Main.inlinePolymophicCrossDexSubASubC(Super) inliner (after) + /// CHECK-NOT: InvokeVirtual method_name:Super.getValue + + // Note that the order in which the types are added to the inline cache in the profile matters. + + /// CHECK-START: int Main.inlinePolymophicCrossDexSubASubC(Super) inliner (after) + /// CHECK-DAG: <> IntConstant 42 + /// CHECK-DAG: <> IntConstant 24 + /// CHECK: <> InstanceFieldGet field_name:java.lang.Object.shadow$_klass_ + /// CHECK: <> LoadClass class_name:SubA + /// CHECK: <> NotEqual [<>,<>] + /// CHECK: If [<>] + + /// CHECK: <> InstanceFieldGet field_name:java.lang.Object.shadow$_klass_ + /// CHECK: <> LoadClass class_name:SubC + /// CHECK: <> NotEqual [<>,<>] + /// CHECK: Deoptimize [<>] + + /// CHECK: <> Phi [<>,<>] + /// CHECK: Return [<>] + public static int inlinePolymophicCrossDexSubASubC(Super a) { + return a.getValue(); + } + + /// CHECK-START: int Main.inlineMegamorphic(Super) inliner (before) + /// CHECK: InvokeVirtual method_name:Super.getValue + + /// CHECK-START: int Main.inlineMegamorphic(Super) inliner (after) + /// CHECK: InvokeVirtual method_name:Super.getValue + public static int inlineMegamorphic(Super a) { + return a.getValue(); + } + + /// CHECK-START: int Main.inlineMissingTypes(Super) inliner (before) + /// CHECK: InvokeVirtual method_name:Super.getValue + + /// CHECK-START: int Main.inlineMissingTypes(Super) inliner (after) + /// CHECK: InvokeVirtual method_name:Super.getValue + public static int inlineMissingTypes(Super a) { + return a.getValue(); + } + + /// CHECK-START: int Main.noInlineCache(Super) inliner (before) + /// CHECK: InvokeVirtual method_name:Super.getValue + + /// CHECK-START: int Main.noInlineCache(Super) inliner (after) + /// CHECK: InvokeVirtual method_name:Super.getValue + public static int noInlineCache(Super a) { + return a.getValue(); + } + + public static void testInlineMonomorphic() { + if (inlineMonomorphicSubA(new SubA()) != 42) { + throw new Error("Expected 42"); + } + + // Call with a different type than the one from the inline cache. + if (inlineMonomorphicSubA(new SubB()) != 38) { + throw new Error("Expected 38"); + } + } + + public static void testInlinePolymorhic() { + if (inlinePolymophicSubASubB(new SubA()) != 42) { + throw new Error("Expected 42"); + } + + if (inlinePolymophicSubASubB(new SubB()) != 38) { + throw new Error("Expected 38"); + } + + // Call with a different type than the one from the inline cache. + if (inlinePolymophicSubASubB(new SubC()) != 24) { + throw new Error("Expected 25"); + } + + if (inlinePolymophicCrossDexSubASubC(new SubA()) != 42) { + throw new Error("Expected 42"); + } + + if (inlinePolymophicCrossDexSubASubC(new SubC()) != 24) { + throw new Error("Expected 24"); + } + + // Call with a different type than the one from the inline cache. + if (inlinePolymophicCrossDexSubASubC(new SubB()) != 38) { + throw new Error("Expected 38"); + } + } + + public static void testInlineMegamorphic() { + if (inlineMegamorphic(new SubA()) != 42) { + throw new Error("Expected 42"); + } + } + + + public static void testNoInlineCache() { + if (noInlineCache(new SubA()) != 42) { + throw new Error("Expected 42"); + } + } + + public static void main(String[] args) { + testInlineMonomorphic(); + testInlinePolymorhic(); + testInlineMegamorphic(); + testNoInlineCache(); + } + +} diff --git a/test/638-checker-inline-caches/src/Super.java b/test/638-checker-inline-caches/src/Super.java new file mode 100644 index 000000000..30cdf30db --- /dev/null +++ b/test/638-checker-inline-caches/src/Super.java @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2017 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 abstract class Super { + abstract int getValue(); +} diff --git a/test/etc/run-test-jar b/test/etc/run-test-jar index 9d1f8a2b5..de1625641 100755 --- a/test/etc/run-test-jar +++ b/test/etc/run-test-jar @@ -63,6 +63,7 @@ TEST_VDEX="n" TEST_IS_NDEBUG="n" APP_IMAGE="y" VDEX_FILTER="" +PROFILE="n" # if "y", run 'sync' before dalvikvm to make sure all files from # build step (e.g. dex2oat) were finished writing. @@ -269,6 +270,9 @@ while true; do elif [ "x$1" = "x--sync" ]; then SYNC_BEFORE_RUN="y" shift + elif [ "x$1" = "x--profile" ]; then + PROFILE="y" + shift elif expr "x$1" : "x--" >/dev/null 2>&1; then echo "unknown $0 option: $1" 1>&2 exit 1 @@ -511,6 +515,7 @@ if [ ${#VDEX_NAME} -gt $max_filename_size ]; then exit 1 fi +profman_cmdline="true" dex2oat_cmdline="true" vdex_cmdline="true" mkdir_locations="${DEX_LOCATION}/dalvik-cache/$ISA" @@ -536,6 +541,15 @@ if [ "$PREBUILD" = "y" ]; then dex2oat_cmdline="${dex2oat_cmdline} --instruction-set-features=${INSTRUCTION_SET_FEATURES}" fi + if [ "$PROFILE" = "y" ]; then + profman_cmdline="${ANDROID_ROOT}/bin/profman \ + --apk=$DEX_LOCATION/$TEST_NAME.jar \ + --dex-location=$DEX_LOCATION/$TEST_NAME.jar \ + --create-profile-from=$DEX_LOCATION/profile \ + --reference-profile-file=$TEST_NAME.prof" + dex2oat_cmdline="${dex2oat_cmdline} --profile-file=$TEST_NAME.prof" + fi + # Add in a timeout. This is important for testing the compilation/verification time of # pathological cases. # Note: as we don't know how decent targets are (e.g., emulator), only do this on the host for @@ -594,6 +608,7 @@ dalvikvm_cmdline="$INVOKE_WITH $GDB $ANDROID_ROOT/bin/$DALVIKVM \ dex2oat_cmdline=$(echo $dex2oat_cmdline) dalvikvm_cmdline=$(echo $dalvikvm_cmdline) vdex_cmdline=$(echo $vdex_cmdline) +profman_cmdline=$(echo $profman_cmdline) if [ "$HOST" = "n" ]; then adb root > /dev/null @@ -603,11 +618,18 @@ if [ "$HOST" = "n" ]; then adb shell mkdir -p $DEX_LOCATION adb push $TEST_NAME.jar $DEX_LOCATION adb push $TEST_NAME-ex.jar $DEX_LOCATION + if [ "$PROFILE" = "y" ]; then + adb push profile $DEX_LOCATION + fi else adb shell rm -r $DEX_LOCATION >/dev/null 2>&1 adb shell mkdir -p $DEX_LOCATION >/dev/null 2>&1 adb push $TEST_NAME.jar $DEX_LOCATION >/dev/null 2>&1 adb push $TEST_NAME-ex.jar $DEX_LOCATION >/dev/null 2>&1 + if [ "$PROFILE" = "y" ]; then + adb push profile $DEX_LOCATION >/dev/null 2>&1 + fi + fi LD_LIBRARY_PATH=/data/$TEST_DIRECTORY/art/$ISA @@ -634,6 +656,7 @@ if [ "$HOST" = "n" ]; then mkdir -p ${mkdir_locations} && \ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH && \ export PATH=$ANDROID_ROOT/bin:$PATH && \ + $profman_cmdline && \ $dex2oat_cmdline && \ $vdex_cmdline && \ $strip_cmdline && \ @@ -710,13 +733,14 @@ else fi if [ "$DEV_MODE" = "y" ]; then - echo "mkdir -p ${mkdir_locations} && $dex2oat_cmdline && $vdex_cmdline && $strip_cmdline && $sync_cmdline && $cmdline" + echo "mkdir -p ${mkdir_locations} && $profman_cmdline && $dex2oat_cmdline && $vdex_cmdline && $strip_cmdline && $sync_cmdline && $cmdline" fi cd $ANDROID_BUILD_TOP rm -rf ${DEX_LOCATION}/dalvik-cache/ mkdir -p ${mkdir_locations} || exit 1 + $profman_cmdline || { echo "Profman failed." >&2 ; exit 2; } $dex2oat_cmdline || { echo "Dex2oat failed." >&2 ; exit 2; } $vdex_cmdline || { echo "Dex2oat failed." >&2 ; exit 2; } $strip_cmdline || { echo "Strip failed." >&2 ; exit 3; } -- 2.11.0