diff options
author | 2017-02-13 19:03:47 -0800 | |
---|---|---|
committer | 2017-03-03 13:12:31 -0800 | |
commit | e0ac1151b360be7147fa20320c0b427688b1424f (patch) | |
tree | c33419b00c47b731075bcfd7b75b81f366beda91 | |
parent | 425b5d23e2c60d295471817a75b1b554481c5334 (diff) |
Extend profman to generate profiles with inline caches
Extend profman logic to generate profiles based on a simple textual
respresentation. This will help writing tests for profile guided
compilation.
Before this CL, profman was able to generate profiles based on a list of
classes like:
java.lang.Comparable
java.lang.Math
java.lang.Object
This CL, enables profman to understand methods and classes alike. The
new format is:
# Classes
Ljava/lang/Comparable;
Ljava/lang/Math;
# Methods with inline caches
LTestInline;->inlinePolymorhic(LSuper;)I+LSubA;,LSubB;,LSubC;
LTestInline;->noInlineCache(LSuper;)I
"LTestInline;->inlinePolymorhic(LSuper;)I+LSubA;,LSubB;,LSubC;"
means that method `int inlineMonomorphicSubA(Super)` from class Main
will be added to the profile with the inline cache (SubA,SubB) for its
one and only invoke virtual.
@Main#noInlineCache:(LSuper;)I+;
meaning that method `int noInlineCache' from class Main will be added
to the profile with no inline cache.
Note that the methods are allowed to have a single invoke virtual in
their dex bytecode. That is to keep the parsing the file format
simple and easy to use.
Also, add a few more tests for profiles and fix an issue caused by
writing the dex files in a possibly wrong order.
Test: m run-test-host-gtest-profile_assistant_test
Bug: 32434870
Change-Id: I6b7340cf613007117d9818be206ccb3a27b815bf
-rw-r--r-- | compiler/common_compiler_test.cc | 4 | ||||
-rw-r--r-- | compiler/dex/dex_to_dex_decompiler.cc | 2 | ||||
-rw-r--r-- | compiler/driver/compiler_driver_test.cc | 5 | ||||
-rw-r--r-- | dexlayout/dexlayout_test.cc | 2 | ||||
-rw-r--r-- | profman/profile_assistant_test.cc | 190 | ||||
-rw-r--r-- | profman/profman.cc | 234 | ||||
-rw-r--r-- | runtime/bytecode_utils.h (renamed from compiler/optimizing/bytecode_utils.h) | 6 | ||||
-rw-r--r-- | runtime/jit/profile_compilation_info.cc | 39 | ||||
-rw-r--r-- | runtime/jit/profile_compilation_info.h | 8 | ||||
-rw-r--r-- | test/ProfileTestMultiDex/Main.java | 38 | ||||
-rw-r--r-- | test/ProfileTestMultiDex/Second.java | 5 | ||||
-rw-r--r-- | test/ProfileTestMultiDex/main.jpp | 22 | ||||
-rw-r--r-- | test/ProfileTestMultiDex/main.list | 6 |
13 files changed, 481 insertions, 80 deletions
diff --git a/compiler/common_compiler_test.cc b/compiler/common_compiler_test.cc index d89cdbabf8..9a45379a05 100644 --- a/compiler/common_compiler_test.cc +++ b/compiler/common_compiler_test.cc @@ -52,10 +52,10 @@ void CommonCompilerTest::MakeExecutable(ArtMethod* method) { compiler_driver_->GetCompiledMethod(MethodReference(&dex_file, method->GetDexMethodIndex())); } - if (compiled_method != nullptr) { + // If the code size is 0 it means the method was skipped due to profile guided compilation. + if (compiled_method != nullptr && compiled_method->GetQuickCode().size() != 0u) { ArrayRef<const uint8_t> code = compiled_method->GetQuickCode(); uint32_t code_size = code.size(); - CHECK_NE(0u, code_size); ArrayRef<const uint8_t> vmap_table = compiled_method->GetVmapTable(); uint32_t vmap_table_offset = vmap_table.empty() ? 0u : sizeof(OatQuickMethodHeader) + vmap_table.size(); diff --git a/compiler/dex/dex_to_dex_decompiler.cc b/compiler/dex/dex_to_dex_decompiler.cc index bfd485d126..53601033da 100644 --- a/compiler/dex/dex_to_dex_decompiler.cc +++ b/compiler/dex/dex_to_dex_decompiler.cc @@ -20,7 +20,7 @@ #include "base/mutex.h" #include "dex_file-inl.h" #include "dex_instruction-inl.h" -#include "optimizing/bytecode_utils.h" +#include "bytecode_utils.h" namespace art { namespace optimizer { diff --git a/compiler/driver/compiler_driver_test.cc b/compiler/driver/compiler_driver_test.cc index 97954f3c29..562f97b3ae 100644 --- a/compiler/driver/compiler_driver_test.cc +++ b/compiler/driver/compiler_driver_test.cc @@ -240,9 +240,8 @@ class CompilerDriverProfileTest : public CompilerDriverTest { ProfileCompilationInfo info; for (const std::unique_ptr<const DexFile>& dex_file : dex_files) { - std::string key = ProfileCompilationInfo::GetProfileDexFileKey(dex_file->GetLocation()); - profile_info_.AddMethodIndex(key, dex_file->GetLocationChecksum(), 1); - profile_info_.AddMethodIndex(key, dex_file->GetLocationChecksum(), 2); + profile_info_.AddMethodIndex(dex_file->GetLocation(), dex_file->GetLocationChecksum(), 1); + profile_info_.AddMethodIndex(dex_file->GetLocation(), dex_file->GetLocationChecksum(), 2); } return &profile_info_; } diff --git a/dexlayout/dexlayout_test.cc b/dexlayout/dexlayout_test.cc index 2d084c1990..952909c1e2 100644 --- a/dexlayout/dexlayout_test.cc +++ b/dexlayout/dexlayout_test.cc @@ -41,7 +41,7 @@ static const char kDexFileLayoutInputDex[] = "AAAAdQEAAAAQAAABAAAAjAEAAA=="; static const char kDexFileLayoutInputProfile[] = - "cHJvADAwMwABCwABAAAAAAD1KW3+Y2xhc3Nlcy5kZXgBAA=="; + "cHJvADAwNAABCwABAAAAAAD1KW3+Y2xhc3Nlcy5kZXgBAA=="; static const char kDexFileLayoutExpectedOutputDex[] = "ZGV4CjAzNQD1KW3+B8NAB0f2A/ZVIBJ0aHrGIqcpVTAUAgAAcAAAAHhWNBIAAAAAAAAAAIwBAAAH" diff --git a/profman/profile_assistant_test.cc b/profman/profile_assistant_test.cc index d395c170bf..5a758aedf4 100644 --- a/profman/profile_assistant_test.cc +++ b/profman/profile_assistant_test.cc @@ -16,11 +16,14 @@ #include <gtest/gtest.h> +#include "art_method-inl.h" #include "base/unix_file/fd_file.h" #include "common_runtime_test.h" #include "exec_utils.h" -#include "profile_assistant.h" #include "jit/profile_compilation_info.h" +#include "mirror/class-inl.h" +#include "profile_assistant.h" +#include "scoped_thread_state_change-inl.h" #include "utils.h" namespace art { @@ -95,10 +98,12 @@ class ProfileAssistantTest : public CommonRuntimeTest { return ExecAndReturnCode(argv_str, &error); } - bool CreateProfile(std::string class_file_contents, const std::string& filename) { + bool CreateProfile(std::string profile_file_contents, + const std::string& filename, + const std::string& dex_location) { ScratchFile class_names_file; File* file = class_names_file.GetFile(); - EXPECT_TRUE(file->WriteFully(class_file_contents.c_str(), class_file_contents.length())); + EXPECT_TRUE(file->WriteFully(profile_file_contents.c_str(), profile_file_contents.length())); EXPECT_EQ(0, file->Flush()); EXPECT_TRUE(file->ResetOffset()); std::string profman_cmd = GetProfmanCmd(); @@ -106,8 +111,8 @@ class ProfileAssistantTest : public CommonRuntimeTest { argv_str.push_back(profman_cmd); argv_str.push_back("--create-profile-from=" + class_names_file.GetFilename()); argv_str.push_back("--reference-profile-file=" + filename); - argv_str.push_back("--apk=" + GetLibCoreDexFileNames()[0]); - argv_str.push_back("--dex-location=classes.dex"); + argv_str.push_back("--apk=" + dex_location); + argv_str.push_back("--dex-location=" + dex_location); std::string error; EXPECT_EQ(ExecAndReturnCode(argv_str, &error), 0); return true; @@ -121,7 +126,7 @@ class ProfileAssistantTest : public CommonRuntimeTest { argv_str.push_back("--dump-classes"); argv_str.push_back("--profile-file=" + filename); argv_str.push_back("--apk=" + GetLibCoreDexFileNames()[0]); - argv_str.push_back("--dex-location=classes.dex"); + argv_str.push_back("--dex-location=" + GetLibCoreDexFileNames()[0]); argv_str.push_back("--dump-output-to-fd=" + std::to_string(GetFd(class_names_file))); std::string error; EXPECT_EQ(ExecAndReturnCode(argv_str, &error), 0); @@ -137,11 +142,72 @@ class ProfileAssistantTest : public CommonRuntimeTest { bool CreateAndDump(const std::string& input_file_contents, std::string* output_file_contents) { ScratchFile profile_file; - EXPECT_TRUE(CreateProfile(input_file_contents, profile_file.GetFilename())); + EXPECT_TRUE(CreateProfile(input_file_contents, + profile_file.GetFilename(), + GetLibCoreDexFileNames()[0])); profile_file.GetFile()->ResetOffset(); EXPECT_TRUE(DumpClasses(profile_file.GetFilename(), output_file_contents)); return true; } + + mirror::Class* GetClass(jobject class_loader, const std::string& clazz) { + ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); + Thread* self = Thread::Current(); + ScopedObjectAccess soa(self); + StackHandleScope<1> hs(self); + Handle<mirror::ClassLoader> h_loader( + hs.NewHandle(self->DecodeJObject(class_loader)->AsClassLoader())); + return class_linker->FindClass(self, clazz.c_str(), h_loader); + } + + ArtMethod* GetVirtualMethod(jobject class_loader, + const std::string& clazz, + const std::string& name) { + mirror::Class* klass = GetClass(class_loader, clazz); + ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); + const auto pointer_size = class_linker->GetImagePointerSize(); + ArtMethod* method = nullptr; + Thread* self = Thread::Current(); + ScopedObjectAccess soa(self); + for (auto& m : klass->GetVirtualMethods(pointer_size)) { + if (name == m.GetName()) { + EXPECT_TRUE(method == nullptr); + method = &m; + } + } + return method; + } + + // Verify that given method has the expected inline caches and nothing else. + void AssertInlineCaches(ArtMethod* method, + const std::set<mirror::Class*>& expected_clases, + const ProfileCompilationInfo& info, + bool megamorphic) + REQUIRES_SHARED(Locks::mutator_lock_) { + ProfileCompilationInfo::OfflineProfileMethodInfo pmi; + ASSERT_TRUE(info.GetMethod(method->GetDexFile()->GetLocation(), + method->GetDexFile()->GetLocationChecksum(), + method->GetDexMethodIndex(), + &pmi)); + ASSERT_EQ(pmi.inline_caches.size(), 1u); + ProfileCompilationInfo::DexPcData dex_pc_data = pmi.inline_caches.begin()->second; + + ASSERT_EQ(dex_pc_data.is_megamorphic, megamorphic); + ASSERT_EQ(expected_clases.size(), dex_pc_data.classes.size()); + size_t found = 0; + for (mirror::Class* it : expected_clases) { + for (const auto& class_ref : dex_pc_data.classes) { + ProfileCompilationInfo::DexReference dex_ref = + pmi.dex_references[class_ref.dex_profile_index]; + if (dex_ref.MatchesDex(&(it->GetDexFile())) && + class_ref.type_index == it->GetDexTypeIndex()) { + found++; + } + } + } + + ASSERT_EQ(expected_clases.size(), found); + } }; TEST_F(ProfileAssistantTest, AdviseCompilationEmptyReferences) { @@ -358,25 +424,28 @@ TEST_F(ProfileAssistantTest, TestProfileGeneration) { TEST_F(ProfileAssistantTest, TestProfileCreationAllMatch) { // Class names put here need to be in sorted order. std::vector<std::string> class_names = { - "java.lang.Comparable", - "java.lang.Math", - "java.lang.Object" + "Ljava/lang/Comparable;", + "Ljava/lang/Math;", + "Ljava/lang/Object;" }; std::string input_file_contents; + std::string expected_contents; for (std::string& class_name : class_names) { input_file_contents += class_name + std::string("\n"); + expected_contents += DescriptorToDot(class_name.c_str()) + + std::string("\n"); } std::string output_file_contents; ASSERT_TRUE(CreateAndDump(input_file_contents, &output_file_contents)); - ASSERT_EQ(output_file_contents, input_file_contents); + ASSERT_EQ(output_file_contents, expected_contents); } TEST_F(ProfileAssistantTest, TestProfileCreationOneNotMatched) { // Class names put here need to be in sorted order. std::vector<std::string> class_names = { - "doesnt.match.this.one", - "java.lang.Comparable", - "java.lang.Object" + "Ldoesnt/match/this/one;", + "Ljava/lang/Comparable;", + "Ljava/lang/Object;" }; std::string input_file_contents; for (std::string& class_name : class_names) { @@ -385,16 +454,17 @@ TEST_F(ProfileAssistantTest, TestProfileCreationOneNotMatched) { std::string output_file_contents; ASSERT_TRUE(CreateAndDump(input_file_contents, &output_file_contents)); std::string expected_contents = - class_names[1] + std::string("\n") + class_names[2] + std::string("\n"); + DescriptorToDot(class_names[1].c_str()) + std::string("\n") + + DescriptorToDot(class_names[2].c_str()) + std::string("\n"); ASSERT_EQ(output_file_contents, expected_contents); } TEST_F(ProfileAssistantTest, TestProfileCreationNoneMatched) { // Class names put here need to be in sorted order. std::vector<std::string> class_names = { - "doesnt.match.this.one", - "doesnt.match.this.one.either", - "nor.this.one" + "Ldoesnt/match/this/one;", + "Ldoesnt/match/this/one/either;", + "Lnor/this/one;" }; std::string input_file_contents; for (std::string& class_name : class_names) { @@ -406,4 +476,88 @@ TEST_F(ProfileAssistantTest, TestProfileCreationNoneMatched) { ASSERT_EQ(output_file_contents, expected_contents); } +TEST_F(ProfileAssistantTest, TestProfileCreateInlineCache) { + // Create the profile content. + std::vector<std::string> methods = { + "LTestInline;->inlineMonomorphic(LSuper;)I+LSubA;", + "LTestInline;->inlinePolymorphic(LSuper;)I+LSubA;,LSubB;,LSubC;", + "LTestInline;->inlineMegamorphic(LSuper;)I+LSubA;,LSubB;,LSubC;,LSubD;,LSubE;", + "LTestInline;->noInlineCache(LSuper;)I" + }; + std::string input_file_contents; + for (std::string& m : methods) { + input_file_contents += m + std::string("\n"); + } + + // Create the profile and save it to disk. + ScratchFile profile_file; + ASSERT_TRUE(CreateProfile(input_file_contents, + profile_file.GetFilename(), + GetTestDexFileName("ProfileTestMultiDex"))); + + // Load the profile from disk. + ProfileCompilationInfo info; + profile_file.GetFile()->ResetOffset(); + ASSERT_TRUE(info.Load(GetFd(profile_file))); + + // Load the dex files and verify that the profile contains the expected methods info. + ScopedObjectAccess soa(Thread::Current()); + jobject class_loader = LoadDex("ProfileTestMultiDex"); + ASSERT_NE(class_loader, nullptr); + + mirror::Class* sub_a = GetClass(class_loader, "LSubA;"); + mirror::Class* sub_b = GetClass(class_loader, "LSubB;"); + mirror::Class* sub_c = GetClass(class_loader, "LSubC;"); + + ASSERT_TRUE(sub_a != nullptr); + ASSERT_TRUE(sub_b != nullptr); + ASSERT_TRUE(sub_c != nullptr); + + { + // Verify that method inlineMonomorphic has the expected inline caches and nothing else. + ArtMethod* inline_monomorphic = GetVirtualMethod(class_loader, + "LTestInline;", + "inlineMonomorphic"); + ASSERT_TRUE(inline_monomorphic != nullptr); + std::set<mirror::Class*> expected_monomorphic; + expected_monomorphic.insert(sub_a); + AssertInlineCaches(inline_monomorphic, expected_monomorphic, info, /*megamorphic*/ false); + } + + { + // Verify that method inlinePolymorphic has the expected inline caches and nothing else. + ArtMethod* inline_polymorhic = GetVirtualMethod(class_loader, + "LTestInline;", + "inlinePolymorphic"); + ASSERT_TRUE(inline_polymorhic != nullptr); + std::set<mirror::Class*> expected_polymorphic; + expected_polymorphic.insert(sub_a); + expected_polymorphic.insert(sub_b); + expected_polymorphic.insert(sub_c); + AssertInlineCaches(inline_polymorhic, expected_polymorphic, info, /*megamorphic*/ false); + } + + { + // Verify that method inlineMegamorphic has the expected inline caches and nothing else. + ArtMethod* inline_megamorphic = GetVirtualMethod(class_loader, + "LTestInline;", + "inlineMegamorphic"); + ASSERT_TRUE(inline_megamorphic != nullptr); + std::set<mirror::Class*> expected_megamorphic; + AssertInlineCaches(inline_megamorphic, expected_megamorphic, info, /*megamorphic*/ true); + } + + { + // Verify that method noInlineCache has no inline caches in the profile. + ArtMethod* no_inline_cache = GetVirtualMethod(class_loader, "LTestInline;", "noInlineCache"); + ASSERT_TRUE(no_inline_cache != nullptr); + ProfileCompilationInfo::OfflineProfileMethodInfo pmi_no_inline_cache; + ASSERT_TRUE(info.GetMethod(no_inline_cache->GetDexFile()->GetLocation(), + no_inline_cache->GetDexFile()->GetLocationChecksum(), + no_inline_cache->GetDexMethodIndex(), + &pmi_no_inline_cache)); + ASSERT_TRUE(pmi_no_inline_cache.inline_caches.empty()); + } +} + } // namespace art diff --git a/profman/profman.cc b/profman/profman.cc index a42e4f1db1..a99a0ea2da 100644 --- a/profman/profman.cc +++ b/profman/profman.cc @@ -36,6 +36,7 @@ #include "base/stringpiece.h" #include "base/time_utils.h" #include "base/unix_file/fd_file.h" +#include "bytecode_utils.h" #include "dex_file.h" #include "jit/profile_compilation_info.h" #include "runtime.h" @@ -136,6 +137,14 @@ static constexpr uint16_t kDefaultTestProfileNumDex = 20; static constexpr uint16_t kDefaultTestProfileMethodRatio = 5; static constexpr uint16_t kDefaultTestProfileClassRatio = 5; +// Separators used when parsing human friendly representation of profiles. +static const std::string kMethodSep = "->"; +static constexpr char kProfileParsingInlineChacheSep = '+'; +static constexpr char kProfileParsingTypeSep = ','; +static constexpr char kProfileParsingFirstCharInSignature = '('; + +// TODO(calin): This class has grown too much from its initial design. Split the functionality +// into smaller, more contained pieces. class ProfMan FINAL { public: ProfMan() : @@ -522,6 +531,180 @@ class ProfMan FINAL { return output.release(); } + // Find class klass_descriptor in the given dex_files and store its reference + // in the out parameter class_ref. + // Return true if the definition of the class was found in any of the dex_files. + bool FindClass(const std::vector<std::unique_ptr<const DexFile>>& dex_files, + const std::string& klass_descriptor, + /*out*/ProfileMethodInfo::ProfileClassReference* class_ref) { + for (const std::unique_ptr<const DexFile>& dex_file_ptr : dex_files) { + const DexFile* dex_file = dex_file_ptr.get(); + const DexFile::TypeId* type_id = dex_file->FindTypeId(klass_descriptor.c_str()); + if (type_id == nullptr) { + continue; + } + dex::TypeIndex type_index = dex_file->GetIndexForTypeId(*type_id); + if (dex_file->FindClassDef(type_index) == nullptr) { + // Class is only referenced in the current dex file but not defined in it. + continue; + } + class_ref->dex_file = dex_file; + class_ref->type_index = type_index; + return true; + } + return false; + } + + // Find the method specified by method_spec in the class class_ref. The method + // must have a single INVOKE_VIRTUAL in its byte code. + // Upon success it returns true and stores the method index and the invoke dex pc + // in the output parameters. + // The format of the method spec is "inlinePolymorphic(LSuper;)I+LSubA;,LSubB;,LSubC;". + // + // TODO(calin): support INVOKE_INTERFACE and the range variants. + bool FindMethodWithSingleInvoke(const ProfileMethodInfo::ProfileClassReference& class_ref, + const std::string& method_spec, + /*out*/uint16_t* method_index, + /*out*/uint32_t* dex_pc) { + std::vector<std::string> name_and_signature; + Split(method_spec, kProfileParsingFirstCharInSignature, &name_and_signature); + if (name_and_signature.size() != 2) { + LOG(ERROR) << "Invalid method name and signature " << method_spec; + } + const std::string& name = name_and_signature[0]; + const std::string& signature = kProfileParsingFirstCharInSignature + name_and_signature[1]; + const DexFile* dex_file = class_ref.dex_file; + + const DexFile::StringId* name_id = dex_file->FindStringId(name.c_str()); + if (name_id == nullptr) { + LOG(ERROR) << "Could not find name: " << name; + return false; + } + dex::TypeIndex return_type_idx; + std::vector<dex::TypeIndex> param_type_idxs; + if (!dex_file->CreateTypeList(signature, &return_type_idx, ¶m_type_idxs)) { + LOG(ERROR) << "Could not create type list" << signature; + return false; + } + const DexFile::ProtoId* proto_id = dex_file->FindProtoId(return_type_idx, param_type_idxs); + if (proto_id == nullptr) { + LOG(ERROR) << "Could not find proto_id: " << name; + return false; + } + const DexFile::MethodId* method_id = dex_file->FindMethodId( + dex_file->GetTypeId(class_ref.type_index), *name_id, *proto_id); + if (method_id == nullptr) { + LOG(ERROR) << "Could not find method_id: " << name; + return false; + } + + *method_index = dex_file->GetIndexForMethodId(*method_id); + + uint32_t offset = dex_file->FindCodeItemOffset( + *dex_file->FindClassDef(class_ref.type_index), + *method_index); + const DexFile::CodeItem* code_item = dex_file->GetCodeItem(offset); + + bool found_invoke = false; + for (CodeItemIterator it(*code_item); !it.Done(); it.Advance()) { + if (it.CurrentInstruction().Opcode() == Instruction::INVOKE_VIRTUAL) { + if (found_invoke) { + LOG(ERROR) << "Multiple invoke INVOKE_VIRTUAL found: " << name; + return false; + } + found_invoke = true; + *dex_pc = it.CurrentDexPc(); + } + } + if (!found_invoke) { + LOG(ERROR) << "Could not find any INVOKE_VIRTUAL: " << name; + } + return found_invoke; + } + + // Process a line defining a class or a method and its inline caches. + // Upon success return true and add the class or the method info to profile. + // The format of the method line is: + // "LTestInline;->inlinePolymorphic(LSuper;)I+LSubA;,LSubB;,LSubC;". + // The method and classes are searched only in the given dex files. + bool ProcessLine(const std::vector<std::unique_ptr<const DexFile>>& dex_files, + const std::string& line, + /*out*/ProfileCompilationInfo* profile) { + std::string klass; + std::string method_str; + size_t method_sep_index = line.find(kMethodSep); + if (method_sep_index == std::string::npos) { + klass = line; + } else { + klass = line.substr(0, method_sep_index); + method_str = line.substr(method_sep_index + kMethodSep.size()); + } + + ProfileMethodInfo::ProfileClassReference class_ref; + if (!FindClass(dex_files, klass, &class_ref)) { + LOG(ERROR) << "Could not find class: " << klass; + return false; + } + + if (method_str.empty()) { + // No method to add. Just add the class. + std::set<DexCacheResolvedClasses> resolved_class_set; + const DexFile* dex_file = class_ref.dex_file; + const auto& dex_resolved_classes = resolved_class_set.emplace( + dex_file->GetLocation(), + dex_file->GetBaseLocation(), + dex_file->GetLocationChecksum()); + dex_resolved_classes.first->AddClass(class_ref.type_index); + profile->AddMethodsAndClasses(std::vector<ProfileMethodInfo>(), resolved_class_set); + return true; + } + + // Process the method. + std::string method_spec; + std::vector<std::string> inline_cache_elems; + + std::vector<std::string> method_elems; + Split(method_str, kProfileParsingInlineChacheSep, &method_elems); + if (method_elems.size() == 2) { + method_spec = method_elems[0]; + Split(method_elems[1], kProfileParsingTypeSep, &inline_cache_elems); + } else if (method_elems.size() == 1) { + method_spec = method_elems[0]; + } else { + LOG(ERROR) << "Invalid method line: " << line; + return false; + } + + uint16_t method_index; + uint32_t dex_pc; + if (!FindMethodWithSingleInvoke(class_ref, method_spec, &method_index, &dex_pc)) { + return false; + } + std::vector<ProfileMethodInfo::ProfileClassReference> classes(inline_cache_elems.size()); + size_t class_it = 0; + for (const std::string ic_class : inline_cache_elems) { + if (!FindClass(dex_files, ic_class, &(classes[class_it++]))) { + LOG(ERROR) << "Could not find class: " << ic_class; + return false; + } + } + std::vector<ProfileMethodInfo::ProfileInlineCache> inline_caches; + inline_caches.emplace_back(dex_pc, classes); + std::vector<ProfileMethodInfo> pmi; + pmi.emplace_back(class_ref.dex_file, method_index, inline_caches); + + profile->AddMethodsAndClasses(pmi, std::set<DexCacheResolvedClasses>()); + return true; + } + + // Creates a profile from a human friendly textual representation. + // The expected input format is: + // # Classes + // Ljava/lang/Comparable; + // Ljava/lang/Math; + // # Methods with inline caches + // LTestInline;->inlinePolymorphic(LSuper;)I+LSubA;,LSubB;,LSubC; + // LTestInline;->noInlineCache(LSuper;)I int CreateProfile() { // Validate parameters for this command. if (apk_files_.empty() && apks_fd_.empty()) { @@ -550,51 +733,22 @@ class ProfMan FINAL { return -1; } } - // Read the user-specified list of classes (dot notation rather than descriptors). + // Read the user-specified list of classes and methods. std::unique_ptr<std::unordered_set<std::string>> - user_class_list(ReadCommentedInputFromFile<std::unordered_set<std::string>>( + user_lines(ReadCommentedInputFromFile<std::unordered_set<std::string>>( create_profile_from_file_.c_str(), nullptr)); // No post-processing. - std::unordered_set<std::string> matched_user_classes; - // Open the dex files to look up class names. + + // Open the dex files to look up classes and methods. std::vector<std::unique_ptr<const DexFile>> dex_files; OpenApkFilesFromLocations(&dex_files); - // Iterate over the dex files looking for class names in the input stream. - std::set<DexCacheResolvedClasses> resolved_class_set; - for (auto& dex_file : dex_files) { - // Compute the set of classes to be added for this dex file first. This - // avoids creating an entry in the profile information for dex files that - // contribute no classes. - std::unordered_set<dex::TypeIndex> classes_to_be_added; - for (const auto& klass : *user_class_list) { - std::string descriptor = DotToDescriptor(klass.c_str()); - const DexFile::TypeId* type_id = dex_file->FindTypeId(descriptor.c_str()); - if (type_id == nullptr) { - continue; - } - classes_to_be_added.insert(dex_file->GetIndexForTypeId(*type_id)); - matched_user_classes.insert(klass); - } - if (classes_to_be_added.empty()) { - continue; - } - // Insert the DexCacheResolved Classes into the set expected for - // AddMethodsAndClasses. - std::set<DexCacheResolvedClasses>::iterator dex_resolved_classes = - resolved_class_set.emplace(dex_file->GetLocation(), - dex_file->GetBaseLocation(), - dex_file->GetLocationChecksum()).first; - dex_resolved_classes->AddClasses(classes_to_be_added.begin(), classes_to_be_added.end()); - } - // Warn the user if we didn't find matches for every class. - for (const auto& klass : *user_class_list) { - if (matched_user_classes.find(klass) == matched_user_classes.end()) { - LOG(WARNING) << "requested class '" << klass << "' was not matched in any dex file"; - } - } - // Generate the profile data structure. + + // Process the lines one by one and add the successful ones to the profile. ProfileCompilationInfo info; - std::vector<ProfileMethodInfo> methods; // No methods for now. - info.AddMethodsAndClasses(methods, resolved_class_set); + + for (const auto& line : *user_lines) { + ProcessLine(dex_files, line, &info); + } + // Write the profile file. CHECK(info.Save(fd)); if (close(fd) < 0) { diff --git a/compiler/optimizing/bytecode_utils.h b/runtime/bytecode_utils.h index 133afa47fe..fa87b1d6da 100644 --- a/compiler/optimizing/bytecode_utils.h +++ b/runtime/bytecode_utils.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef ART_COMPILER_OPTIMIZING_BYTECODE_UTILS_H_ -#define ART_COMPILER_OPTIMIZING_BYTECODE_UTILS_H_ +#ifndef ART_RUNTIME_BYTECODE_UTILS_H_ +#define ART_RUNTIME_BYTECODE_UTILS_H_ #include "base/arena_object.h" #include "dex_file.h" @@ -177,4 +177,4 @@ inline bool IsThrowingDexInstruction(const Instruction& instruction) { } // namespace art -#endif // ART_COMPILER_OPTIMIZING_BYTECODE_UTILS_H_ +#endif // ART_RUNTIME_BYTECODE_UTILS_H_ diff --git a/runtime/jit/profile_compilation_info.cc b/runtime/jit/profile_compilation_info.cc index 627cc93f38..58699f0011 100644 --- a/runtime/jit/profile_compilation_info.cc +++ b/runtime/jit/profile_compilation_info.cc @@ -37,7 +37,8 @@ namespace art { const uint8_t ProfileCompilationInfo::kProfileMagic[] = { 'p', 'r', 'o', '\0' }; -const uint8_t ProfileCompilationInfo::kProfileVersion[] = { '0', '0', '3', '\0' }; // inline caches +// Last profile version: fix the order of dex files in the profile. +const uint8_t ProfileCompilationInfo::kProfileVersion[] = { '0', '0', '4', '\0' }; static constexpr uint16_t kMaxDexFileKeyLength = PATH_MAX; @@ -222,15 +223,23 @@ bool ProfileCompilationInfo::Save(int fd) { DCHECK_LE(info_.size(), std::numeric_limits<uint8_t>::max()); AddUintToBuffer(&buffer, static_cast<uint8_t>(info_.size())); + // Make sure we write the dex files in order of their profile index. This + // avoids writing the index in the output file and simplifies the parsing logic. + std::vector<const std::string*> ordered_info_location(info_.size()); + std::vector<const DexFileData*> ordered_info_data(info_.size()); for (const auto& it : info_) { + ordered_info_location[it.second.profile_index] = &(it.first); + ordered_info_data[it.second.profile_index] = &(it.second); + } + for (size_t i = 0; i < info_.size(); i++) { if (buffer.size() > kMaxSizeToKeepBeforeWriting) { if (!WriteBuffer(fd, buffer.data(), buffer.size())) { return false; } buffer.clear(); } - const std::string& dex_location = it.first; - const DexFileData& dex_data = it.second; + const std::string& dex_location = *ordered_info_location[i]; + const DexFileData& dex_data = *ordered_info_data[i]; // Note that we allow dex files without any methods or classes, so that // inline caches can refer valid dex files. @@ -797,10 +806,13 @@ bool ProfileCompilationInfo::MergeWith(const ProfileCompilationInfo& other) { SafeMap<uint8_t, uint8_t> dex_profile_index_remap; for (const auto& other_it : other.info_) { const std::string& other_dex_location = other_it.first; + uint32_t other_checksum = other_it.second.checksum; const DexFileData& other_dex_data = other_it.second; - auto info_it = info_.FindOrAdd(other_dex_location, DexFileData(other_dex_data.checksum, 0)); - const DexFileData& dex_data = info_it->second; - dex_profile_index_remap.Put(other_dex_data.profile_index, dex_data.profile_index); + const DexFileData* dex_data = GetOrAddDexFileData(other_dex_location, other_checksum); + if (dex_data == nullptr) { + return false; // Could happen if we exceed the number of allowed dex files. + } + dex_profile_index_remap.Put(other_dex_data.profile_index, dex_data->profile_index); } // Merge the actual profile data. @@ -949,10 +961,17 @@ std::string ProfileCompilationInfo::DumpInfo(const std::vector<const DexFile*>* os << "ProfileInfo:"; const std::string kFirstDexFileKeySubstitute = ":classes.dex"; + // Write the entries in profile index order. + std::vector<const std::string*> ordered_info_location(info_.size()); + std::vector<const DexFileData*> ordered_info_data(info_.size()); for (const auto& it : info_) { + ordered_info_location[it.second.profile_index] = &(it.first); + ordered_info_data[it.second.profile_index] = &(it.second); + } + for (size_t profile_index = 0; profile_index < info_.size(); profile_index++) { os << "\n"; - const std::string& location = it.first; - const DexFileData& dex_data = it.second; + const std::string& location = *ordered_info_location[profile_index]; + const DexFileData& dex_data = *ordered_info_data[profile_index]; if (print_full_dex_location) { os << location; } else { @@ -960,6 +979,7 @@ std::string ProfileCompilationInfo::DumpInfo(const std::vector<const DexFile*>* std::string multidex_suffix = DexFile::GetMultiDexSuffix(location); os << (multidex_suffix.empty() ? kFirstDexFileKeySubstitute : multidex_suffix); } + os << " [index=" << static_cast<uint32_t>(dex_data.profile_index) << "]"; const DexFile* dex_file = nullptr; if (dex_files != nullptr) { for (size_t i = 0; i < dex_files->size(); i++) { @@ -1022,7 +1042,8 @@ void ProfileCompilationInfo::GetClassNames(const std::vector<const DexFile*>* de const DexFile* dex_file = nullptr; if (dex_files != nullptr) { for (size_t i = 0; i < dex_files->size(); i++) { - if (location == (*dex_files)[i]->GetLocation()) { + if (location == GetProfileDexFileKey((*dex_files)[i]->GetLocation()) && + dex_data.checksum == (*dex_files)[i]->GetLocationChecksum()) { dex_file = (*dex_files)[i]; } } diff --git a/runtime/jit/profile_compilation_info.h b/runtime/jit/profile_compilation_info.h index 4bfbfcd287..9e2fb46cc3 100644 --- a/runtime/jit/profile_compilation_info.h +++ b/runtime/jit/profile_compilation_info.h @@ -36,11 +36,12 @@ namespace art { */ struct ProfileMethodInfo { struct ProfileClassReference { + ProfileClassReference() : dex_file(nullptr) {} ProfileClassReference(const DexFile* dex, const dex::TypeIndex& index) : dex_file(dex), type_index(index) {} const DexFile* dex_file; - const dex::TypeIndex type_index; + dex::TypeIndex type_index; }; struct ProfileInlineCache { @@ -91,6 +92,11 @@ class ProfileCompilationInfo { return dex_checksum == other.dex_checksum && dex_location == other.dex_location; } + bool MatchesDex(const DexFile* dex_file) const { + return dex_checksum == dex_file->GetLocationChecksum() && + dex_location == GetProfileDexFileKey(dex_file->GetLocation()); + } + std::string dex_location; uint32_t dex_checksum; }; diff --git a/test/ProfileTestMultiDex/Main.java b/test/ProfileTestMultiDex/Main.java index 41532ea8f7..73fbb00d25 100644 --- a/test/ProfileTestMultiDex/Main.java +++ b/test/ProfileTestMultiDex/Main.java @@ -25,3 +25,41 @@ class Main { return "C"; } } + +class TestInline { + public int inlineMonomorphic(Super s) { + return s.getValue(); + } + + public int inlinePolymorphic(Super s) { + return s.getValue(); + } + + public int inlineMegamorphic(Super s) { + return s.getValue(); + } + + public int noInlineCache(Super s) { + return s.getValue(); + } +} + +abstract class Super { + abstract int getValue(); +} + +class SubA extends Super { + int getValue() { return 42; } +} + +class SubB extends Super { + int getValue() { return 38; }; +} + +class SubD extends Super { + int getValue() { return 20; }; +} + +class SubE extends Super { + int getValue() { return 16; }; +} diff --git a/test/ProfileTestMultiDex/Second.java b/test/ProfileTestMultiDex/Second.java index 4ac5abc300..4b3c7a479b 100644 --- a/test/ProfileTestMultiDex/Second.java +++ b/test/ProfileTestMultiDex/Second.java @@ -25,3 +25,8 @@ class Second { return "Z"; } } + +class SubC extends Super { + int getValue() { return 24; } +} + diff --git a/test/ProfileTestMultiDex/main.jpp b/test/ProfileTestMultiDex/main.jpp index f2e3b4e14c..5e55e96874 100644 --- a/test/ProfileTestMultiDex/main.jpp +++ b/test/ProfileTestMultiDex/main.jpp @@ -1,3 +1,21 @@ -main: +Main: @@com.android.jack.annotations.ForceInMainDex - class Second + class Main +TestInqline: + @@com.android.jack.annotations.ForceInMainDex + class TestInline +Super: + @@com.android.jack.annotations.ForceInMainDex + class Super +SubA: + @@com.android.jack.annotations.ForceInMainDex + class SubA +SubB: + @@com.android.jack.annotations.ForceInMainDex + class SubB +SubD: + @@com.android.jack.annotations.ForceInMainDex + class SubD +SubE: + @@com.android.jack.annotations.ForceInMainDex + class SubE diff --git a/test/ProfileTestMultiDex/main.list b/test/ProfileTestMultiDex/main.list index 44ba78ead5..ec131f0f71 100644 --- a/test/ProfileTestMultiDex/main.list +++ b/test/ProfileTestMultiDex/main.list @@ -1 +1,7 @@ Main.class +TestInline.class +Super.class +SubA.class +SubB.class +SubD.class +SubE.class |