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
diff --git a/compiler/common_compiler_test.cc b/compiler/common_compiler_test.cc
index d89cdba..9a45379 100644
--- a/compiler/common_compiler_test.cc
+++ b/compiler/common_compiler_test.cc
@@ -52,10 +52,10 @@
         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 bfd485d..5360103 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 97954f3..562f97b 100644
--- a/compiler/driver/compiler_driver_test.cc
+++ b/compiler/driver/compiler_driver_test.cc
@@ -240,9 +240,8 @@
 
     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 2d084c1..952909c1 100644
--- a/dexlayout/dexlayout_test.cc
+++ b/dexlayout/dexlayout_test.cc
@@ -41,7 +41,7 @@
     "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 d395c17..5a758ae 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 @@
     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 @@
     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 @@
     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 @@
 
   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, 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 @@
   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 @@
   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 a42e4f1..a99a0ea 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 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 @@
     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, &param_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 @@
         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
similarity index 96%
rename from compiler/optimizing/bytecode_utils.h
rename to runtime/bytecode_utils.h
index 133afa4..fa87b1d 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 @@
 
 }  // 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 627cc93..58699f0 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 @@
   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 @@
   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 @@
   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 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 @@
     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 4bfbfcd..9e2fb46 100644
--- a/runtime/jit/profile_compilation_info.h
+++ b/runtime/jit/profile_compilation_info.h
@@ -36,11 +36,12 @@
  */
 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 @@
       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 41532ea..73fbb00 100644
--- a/test/ProfileTestMultiDex/Main.java
+++ b/test/ProfileTestMultiDex/Main.java
@@ -25,3 +25,41 @@
     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 4ac5abc..4b3c7a4 100644
--- a/test/ProfileTestMultiDex/Second.java
+++ b/test/ProfileTestMultiDex/Second.java
@@ -25,3 +25,8 @@
     return "Z";
   }
 }
+
+class SubC extends Super {
+  int getValue() { return 24; }
+}
+
diff --git a/test/ProfileTestMultiDex/main.jpp b/test/ProfileTestMultiDex/main.jpp
index f2e3b4e..5e55e96 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 44ba78e..ec131f0 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