Add inline caches to offline profiles

Add support for inline caches in profiles:
- extract inline caches from the jit cache when the profile saver
queries the hot methods
- bump profile version to support the new data
- add new tests
- inline caches are only supported for same-apk calls (including
multidex)

Test: m art-test-host-gtest-profile_compilation_info_test
Bug: 32434870
Change-Id: I38b4ca0a54568d2224765ff76023baef1b8fd1a2
diff --git a/dexlayout/dexlayout_test.cc b/dexlayout/dexlayout_test.cc
index da1e1d2..2d85e8f 100644
--- a/dexlayout/dexlayout_test.cc
+++ b/dexlayout/dexlayout_test.cc
@@ -41,7 +41,7 @@
     "AAAAdQEAAAAQAAABAAAAjAEAAA==";
 
 static const char kDexFileLayoutInputProfile[] =
-    "cHJvADAwMgABAAsAAAABAPUpbf5jbGFzc2VzLmRleAEA";
+    "cHJvADAwMwABCwABAAAAAAD1KW3+Y2xhc3Nlcy5kZXgBAA==";
 
 static const char kDexFileLayoutExpectedOutputDex[] =
     "ZGV4CjAzNQD1KW3+B8NAB0f2A/ZVIBJ0aHrGIqcpVTAUAgAAcAAAAHhWNBIAAAAAAAAAAIwBAAAH"
diff --git a/profman/profman.cc b/profman/profman.cc
index 8f35a76..a42e4f1 100644
--- a/profman/profman.cc
+++ b/profman/profman.cc
@@ -593,7 +593,7 @@
     }
     // Generate the profile data structure.
     ProfileCompilationInfo info;
-    std::vector<MethodReference> methods;  // No methods for now.
+    std::vector<ProfileMethodInfo> methods;  // No methods for now.
     info.AddMethodsAndClasses(methods, resolved_class_set);
     // Write the profile file.
     CHECK(info.Save(fd));
diff --git a/runtime/jit/jit_code_cache.cc b/runtime/jit/jit_code_cache.cc
index 60ab275..c226a38 100644
--- a/runtime/jit/jit_code_cache.cc
+++ b/runtime/jit/jit_code_cache.cc
@@ -1245,15 +1245,40 @@
 }
 
 void JitCodeCache::GetProfiledMethods(const std::set<std::string>& dex_base_locations,
-                                      std::vector<MethodReference>& methods) {
+                                      std::vector<ProfileMethodInfo>& methods) {
   ScopedTrace trace(__FUNCTION__);
   MutexLock mu(Thread::Current(), lock_);
   for (const ProfilingInfo* info : profiling_infos_) {
     ArtMethod* method = info->GetMethod();
     const DexFile* dex_file = method->GetDexFile();
-    if (ContainsElement(dex_base_locations, dex_file->GetBaseLocation())) {
-      methods.emplace_back(dex_file,  method->GetDexMethodIndex());
+    if (!ContainsElement(dex_base_locations, dex_file->GetBaseLocation())) {
+      // Skip dex files which are not profiled.
+      continue;
     }
+    std::vector<ProfileMethodInfo::ProfileInlineCache> inline_caches;
+    for (size_t i = 0; i < info->number_of_inline_caches_; ++i) {
+      std::vector<ProfileMethodInfo::ProfileClassReference> profile_classes;
+      const InlineCache& cache = info->cache_[i];
+      for (size_t k = 0; k < InlineCache::kIndividualCacheSize; k++) {
+        mirror::Class* cls = cache.classes_[k].Read();
+        if (cls == nullptr) {
+          break;
+        }
+        const DexFile& class_dex_file = cls->GetDexFile();
+        dex::TypeIndex type_index = cls->GetDexTypeIndex();
+        if (ContainsElement(dex_base_locations, class_dex_file.GetBaseLocation())) {
+          // Only consider classes from the same apk (including multidex).
+          profile_classes.emplace_back(/*ProfileMethodInfo::ProfileClassReference*/
+              &class_dex_file, type_index);
+        }
+      }
+      if (!profile_classes.empty()) {
+        inline_caches.emplace_back(/*ProfileMethodInfo::ProfileInlineCache*/
+            cache.dex_pc_, profile_classes);
+      }
+    }
+    methods.emplace_back(/*ProfileMethodInfo*/
+        dex_file, method->GetDexMethodIndex(), inline_caches);
   }
 }
 
diff --git a/runtime/jit/jit_code_cache.h b/runtime/jit/jit_code_cache.h
index b5e3176..33a792f 100644
--- a/runtime/jit/jit_code_cache.h
+++ b/runtime/jit/jit_code_cache.h
@@ -30,6 +30,7 @@
 #include "method_reference.h"
 #include "oat_file.h"
 #include "object_callbacks.h"
+#include "profile_compilation_info.h"
 #include "safe_map.h"
 #include "thread_pool.h"
 
@@ -192,7 +193,7 @@
 
   // Adds to `methods` all profiled methods which are part of any of the given dex locations.
   void GetProfiledMethods(const std::set<std::string>& dex_base_locations,
-                          std::vector<MethodReference>& methods)
+                          std::vector<ProfileMethodInfo>& methods)
       REQUIRES(!lock_)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
diff --git a/runtime/jit/profile_compilation_info.cc b/runtime/jit/profile_compilation_info.cc
index 54fc038..5638ce1 100644
--- a/runtime/jit/profile_compilation_info.cc
+++ b/runtime/jit/profile_compilation_info.cc
@@ -37,7 +37,7 @@
 namespace art {
 
 const uint8_t ProfileCompilationInfo::kProfileMagic[] = { 'p', 'r', 'o', '\0' };
-const uint8_t ProfileCompilationInfo::kProfileVersion[] = { '0', '0', '2', '\0' };
+const uint8_t ProfileCompilationInfo::kProfileVersion[] = { '0', '0', '3', '\0' };  // inline caches
 
 static constexpr uint16_t kMaxDexFileKeyLength = PATH_MAX;
 
@@ -46,6 +46,25 @@
 // using the same test profile.
 static constexpr bool kDebugIgnoreChecksum = false;
 
+static constexpr uint8_t kMegamorphicEncoding = 7;
+
+static_assert(sizeof(InlineCache::kIndividualCacheSize) == sizeof(uint8_t),
+              "InlineCache::kIndividualCacheSize does not have the expect type size");
+static_assert(InlineCache::kIndividualCacheSize < kMegamorphicEncoding,
+              "InlineCache::kIndividualCacheSize is larger than expected");
+
+void ProfileCompilationInfo::DexPcData::AddClass(uint16_t dex_profile_idx,
+                                                 const dex::TypeIndex& type_idx) {
+  if (is_megamorphic) {
+    return;
+  }
+  classes.emplace(dex_profile_idx, type_idx);
+  if (classes.size() >= InlineCache::kIndividualCacheSize) {
+    is_megamorphic = true;
+    classes.clear();
+  }
+}
+
 // Transform the actual dex location into relative paths.
 // Note: this is OK because we don't store profiles of different apps into the same file.
 // Apps with split apks don't cause trouble because each split has a different name and will not
@@ -62,12 +81,10 @@
 }
 
 bool ProfileCompilationInfo::AddMethodsAndClasses(
-    const std::vector<MethodReference>& methods,
+    const std::vector<ProfileMethodInfo>& methods,
     const std::set<DexCacheResolvedClasses>& resolved_classes) {
-  for (const MethodReference& method : methods) {
-    if (!AddMethodIndex(GetProfileDexFileKey(method.dex_file->GetLocation()),
-                        method.dex_file->GetLocationChecksum(),
-                        method.dex_method_index)) {
+  for (const ProfileMethodInfo& method : methods) {
+    if (!AddMethod(method)) {
       return false;
     }
   }
@@ -170,29 +187,40 @@
 }
 
 static constexpr size_t kLineHeaderSize =
-    3 * sizeof(uint16_t) +  // method_set.size + class_set.size + dex_location.size
-    sizeof(uint32_t);       // checksum
+    2 * sizeof(uint16_t) +  // class_set.size + dex_location.size
+    2 * sizeof(uint32_t);   // method_map.size + checksum
 
 /**
  * Serialization format:
- *    magic,version,number_of_lines
- *    dex_location1,number_of_methods1,number_of_classes1,dex_location_checksum1, \
- *        method_id11,method_id12...,class_id1,class_id2...
- *    dex_location2,number_of_methods2,number_of_classes2,dex_location_checksum2, \
- *        method_id21,method_id22...,,class_id1,class_id2...
+ *    magic,version,number_of_dex_files
+ *    dex_location1,number_of_classes1,methods_region_size,dex_location_checksum1, \
+ *        method_encoding_11,method_encoding_12...,class_id1,class_id2...
+ *    dex_location2,number_of_classes2,methods_region_size,dex_location_checksum2, \
+ *        method_encoding_21,method_encoding_22...,,class_id1,class_id2...
  *    .....
+ * The method_encoding is:
+ *    method_id,number_of_inline_caches,inline_cache1,inline_cache2...
+ * The inline_cache is:
+ *    dex_pc,[M|dex_map_size], dex_profile_index,class_id1,class_id2...,dex_profile_index2,...
+ *    dex_map_size is the number of dex_indeces that follows.
+ *       Classes are grouped per their dex files and the line
+ *       `dex_profile_index,class_id1,class_id2...,dex_profile_index2,...` encodes the
+ *       mapping from `dex_profile_index` to the set of classes `class_id1,class_id2...`
+ *    M stands for megamorphic and it's encoded as the byte kMegamorphicEncoding.
+ *    When present, there will be no class ids following.
  **/
 bool ProfileCompilationInfo::Save(int fd) {
   ScopedTrace trace(__PRETTY_FUNCTION__);
   DCHECK_GE(fd, 0);
 
-  // Cache at most 5KB before writing.
-  static constexpr size_t kMaxSizeToKeepBeforeWriting = 5 * KB;
+  // Cache at most 50KB before writing.
+  static constexpr size_t kMaxSizeToKeepBeforeWriting = 50 * KB;
   // Use a vector wrapper to avoid keeping track of offsets when we add elements.
   std::vector<uint8_t> buffer;
   WriteBuffer(fd, kProfileMagic, sizeof(kProfileMagic));
   WriteBuffer(fd, kProfileVersion, sizeof(kProfileVersion));
-  AddUintToBuffer(&buffer, static_cast<uint16_t>(info_.size()));
+  DCHECK_LE(info_.size(), std::numeric_limits<uint8_t>::max());
+  AddUintToBuffer(&buffer, static_cast<uint8_t>(info_.size()));
 
   for (const auto& it : info_) {
     if (buffer.size() > kMaxSizeToKeepBeforeWriting) {
@@ -203,9 +231,9 @@
     }
     const std::string& dex_location = it.first;
     const DexFileData& dex_data = it.second;
-    if (dex_data.method_set.empty() && dex_data.class_set.empty()) {
-      continue;
-    }
+
+    // Note that we allow dex files without any methods or classes, so that
+    // inline caches can refer valid dex files.
 
     if (dex_location.size() >= kMaxDexFileKeyLength) {
       LOG(WARNING) << "DexFileKey exceeds allocated limit";
@@ -214,42 +242,128 @@
 
     // Make sure that the buffer has enough capacity to avoid repeated resizings
     // while we add data.
+    uint32_t methods_region_size = GetMethodsRegionSize(dex_data);
     size_t required_capacity = buffer.size() +
         kLineHeaderSize +
         dex_location.size() +
-        sizeof(uint16_t) * (dex_data.class_set.size() + dex_data.method_set.size());
+        sizeof(uint16_t) * dex_data.class_set.size() +
+        methods_region_size;
 
     buffer.reserve(required_capacity);
-
     DCHECK_LE(dex_location.size(), std::numeric_limits<uint16_t>::max());
-    DCHECK_LE(dex_data.method_set.size(), std::numeric_limits<uint16_t>::max());
     DCHECK_LE(dex_data.class_set.size(), std::numeric_limits<uint16_t>::max());
     AddUintToBuffer(&buffer, static_cast<uint16_t>(dex_location.size()));
-    AddUintToBuffer(&buffer, static_cast<uint16_t>(dex_data.method_set.size()));
     AddUintToBuffer(&buffer, static_cast<uint16_t>(dex_data.class_set.size()));
+    AddUintToBuffer(&buffer, methods_region_size);  // uint32_t
     AddUintToBuffer(&buffer, dex_data.checksum);  // uint32_t
 
     AddStringToBuffer(&buffer, dex_location);
 
-    for (auto method_it : dex_data.method_set) {
-      AddUintToBuffer(&buffer, method_it);
+    for (const auto& method_it : dex_data.method_map) {
+      AddUintToBuffer(&buffer, method_it.first);
+      AddInlineCacheToBuffer(&buffer, method_it.second);
     }
-    for (auto class_id : dex_data.class_set) {
+    for (const auto& class_id : dex_data.class_set) {
       AddUintToBuffer(&buffer, class_id.index_);
     }
-    DCHECK_EQ(required_capacity, buffer.size())
+
+    DCHECK_LE(required_capacity, buffer.size())
         << "Failed to add the expected number of bytes in the buffer";
   }
 
   return WriteBuffer(fd, buffer.data(), buffer.size());
 }
 
+void ProfileCompilationInfo::AddInlineCacheToBuffer(std::vector<uint8_t>* buffer,
+                                                    const InlineCacheMap& inline_cache_map) {
+  // Add inline cache map size.
+  AddUintToBuffer(buffer, static_cast<uint16_t>(inline_cache_map.size()));
+  if (inline_cache_map.size() == 0) {
+    return;
+  }
+  for (const auto& inline_cache_it : inline_cache_map) {
+    uint16_t dex_pc = inline_cache_it.first;
+    const DexPcData dex_pc_data = inline_cache_it.second;
+    const ClassSet& classes = dex_pc_data.classes;
+
+    // Add the dex pc.
+    AddUintToBuffer(buffer, dex_pc);
+
+    if (dex_pc_data.is_megamorphic) {
+      // Add the megamorphic encoding if needed and continue.
+      // If megamorphic, we don't add the rest of the classes.
+      AddUintToBuffer(buffer, kMegamorphicEncoding);
+      continue;
+    }
+
+    DCHECK_LT(classes.size(), InlineCache::kIndividualCacheSize);
+    DCHECK_NE(classes.size(), 0u) << "InlineCache contains a dex_pc with 0 classes";
+
+    SafeMap<uint8_t, std::vector<dex::TypeIndex>> dex_to_classes_map;
+    // Group the classes by dex. We expect that most of the classes will come from
+    // the same dex, so this will be more efficient than encoding the dex index
+    // for each class reference.
+    GroupClassesByDex(classes, &dex_to_classes_map);
+    // Add the dex map size.
+    AddUintToBuffer(buffer, static_cast<uint8_t>(dex_to_classes_map.size()));
+    for (const auto& dex_it : dex_to_classes_map) {
+      uint8_t dex_profile_index = dex_it.first;
+      const std::vector<dex::TypeIndex>& dex_classes = dex_it.second;
+      // Add the dex profile index.
+      AddUintToBuffer(buffer, dex_profile_index);
+      // Add the the number of classes for each dex profile index.
+      AddUintToBuffer(buffer, static_cast<uint8_t>(dex_classes.size()));
+      for (size_t i = 0; i < dex_classes.size(); i++) {
+        // Add the type index of the classes.
+        AddUintToBuffer(buffer, dex_classes[i].index_);
+      }
+    }
+  }
+}
+
+uint32_t ProfileCompilationInfo::GetMethodsRegionSize(const DexFileData& dex_data) {
+  // ((uint16_t)method index + (uint16_t)inline cache size) * number of methods
+  uint32_t size = 2 * sizeof(uint16_t) * dex_data.method_map.size();
+  for (const auto& method_it : dex_data.method_map) {
+    const InlineCacheMap& inline_cache = method_it.second;
+    size += sizeof(uint16_t) * inline_cache.size();  // dex_pc
+    for (const auto& inline_cache_it : inline_cache) {
+      const ClassSet& classes = inline_cache_it.second.classes;
+      SafeMap<uint8_t, std::vector<dex::TypeIndex>> dex_to_classes_map;
+      GroupClassesByDex(classes, &dex_to_classes_map);
+      size += sizeof(uint8_t);  // dex_to_classes_map size
+      for (const auto& dex_it : dex_to_classes_map) {
+        size += sizeof(uint8_t);  // dex profile index
+        size += sizeof(uint8_t);  // number of classes
+        const std::vector<dex::TypeIndex>& dex_classes = dex_it.second;
+        size += sizeof(uint16_t) * dex_classes.size();  // the actual classes
+      }
+    }
+  }
+  return size;
+}
+
+void ProfileCompilationInfo::GroupClassesByDex(
+    const ClassSet& classes,
+    /*out*/SafeMap<uint8_t, std::vector<dex::TypeIndex>>* dex_to_classes_map) {
+  for (const auto& classes_it : classes) {
+    auto dex_it = dex_to_classes_map->FindOrAdd(classes_it.dex_profile_index);
+    dex_it->second.push_back(classes_it.type_index);
+  }
+}
+
 ProfileCompilationInfo::DexFileData* ProfileCompilationInfo::GetOrAddDexFileData(
     const std::string& dex_location,
     uint32_t checksum) {
-  auto info_it = info_.find(dex_location);
-  if (info_it == info_.end()) {
-    info_it = info_.Put(dex_location, DexFileData(checksum));
+  auto info_it = info_.FindOrAdd(dex_location, DexFileData(checksum, info_.size()));
+  if (info_.size() > std::numeric_limits<uint8_t>::max()) {
+    // Allow only 255 dex files to be profiled. This allows us to save bytes
+    // when encoding. The number is well above what we expect for normal applications.
+    if (kIsDebugBuild) {
+      LOG(WARNING) << "Exceeded the maximum number of dex files (255). Something went wrong";
+    }
+    info_.erase(dex_location);
+    return nullptr;
   }
   if (info_it->second.checksum != checksum) {
     LOG(WARNING) << "Checksum mismatch for dex " << dex_location;
@@ -270,13 +384,65 @@
 }
 
 bool ProfileCompilationInfo::AddMethodIndex(const std::string& dex_location,
-                                            uint32_t checksum,
-                                            uint16_t method_idx) {
-  DexFileData* const data = GetOrAddDexFileData(dex_location, checksum);
-  if (data == nullptr) {
+                                            uint32_t dex_checksum,
+                                            uint16_t method_index) {
+  return AddMethod(dex_location, dex_checksum, method_index, OfflineProfileMethodInfo());
+}
+
+bool ProfileCompilationInfo::AddMethod(const std::string& dex_location,
+                                       uint32_t dex_checksum,
+                                       uint16_t method_index,
+                                       const OfflineProfileMethodInfo& pmi) {
+  DexFileData* const data = GetOrAddDexFileData(
+      GetProfileDexFileKey(dex_location),
+      dex_checksum);
+  if (data == nullptr) {  // checksum mismatch
     return false;
   }
-  data->method_set.insert(method_idx);
+  auto inline_cache_it = data->method_map.FindOrAdd(method_index);
+  for (const auto& pmi_inline_cache_it : pmi.inline_caches) {
+    uint16_t pmi_ic_dex_pc = pmi_inline_cache_it.first;
+    const DexPcData& pmi_ic_dex_pc_data = pmi_inline_cache_it.second;
+    auto dex_pc_data_it = inline_cache_it->second.FindOrAdd(pmi_ic_dex_pc);
+    if (pmi_ic_dex_pc_data.is_megamorphic) {
+      dex_pc_data_it->second.SetMegamorphic();
+      continue;
+    }
+    for (const ClassReference& class_ref : pmi_ic_dex_pc_data.classes) {
+      const DexReference& dex_ref = pmi.dex_references[class_ref.dex_profile_index];
+      DexFileData* class_dex_data = GetOrAddDexFileData(
+          GetProfileDexFileKey(dex_ref.dex_location),
+          dex_ref.dex_checksum);
+      if (class_dex_data == nullptr) {  // checksum mismatch
+        return false;
+      }
+      dex_pc_data_it->second.AddClass(class_dex_data->profile_index, class_ref.type_index);
+    }
+  }
+  return true;
+}
+
+bool ProfileCompilationInfo::AddMethod(const ProfileMethodInfo& pmi) {
+  DexFileData* const data = GetOrAddDexFileData(
+      GetProfileDexFileKey(pmi.dex_file->GetLocation()),
+      pmi.dex_file->GetLocationChecksum());
+  if (data == nullptr) {  // checksum mismatch
+    return false;
+  }
+  auto inline_cache_it = data->method_map.FindOrAdd(pmi.dex_method_index);
+
+  for (const ProfileMethodInfo::ProfileInlineCache& cache : pmi.inline_caches) {
+    for (const ProfileMethodInfo::ProfileClassReference& class_ref : cache.classes) {
+      DexFileData* class_dex_data = GetOrAddDexFileData(
+          GetProfileDexFileKey(class_ref.dex_file->GetLocation()),
+          class_ref.dex_file->GetLocationChecksum());
+      if (class_dex_data == nullptr) {  // checksum mismatch
+        return false;
+      }
+      auto dex_pc_data_it = inline_cache_it->second.FindOrAdd(cache.dex_pc);
+      dex_pc_data_it->second.AddClass(class_dex_data->profile_index, class_ref.type_index);
+    }
+  }
   return true;
 }
 
@@ -291,21 +457,79 @@
   return true;
 }
 
-bool ProfileCompilationInfo::ProcessLine(SafeBuffer& line_buffer,
-                                         uint16_t method_set_size,
-                                         uint16_t class_set_size,
-                                         uint32_t checksum,
-                                         const std::string& dex_location) {
-  for (uint16_t i = 0; i < method_set_size; i++) {
-    uint16_t method_idx = line_buffer.ReadUintAndAdvance<uint16_t>();
-    if (!AddMethodIndex(dex_location, checksum, method_idx)) {
+#define READ_UINT(type, buffer, dest, error)          \
+  do {                                                \
+    if (!buffer.ReadUintAndAdvance<type>(&dest)) {    \
+      *error = "Could not read "#dest;                \
+      return false;                                   \
+    }                                                 \
+  }                                                   \
+  while (false)
+
+bool ProfileCompilationInfo::ReadInlineCache(SafeBuffer& buffer,
+                                             uint8_t number_of_dex_files,
+                                             /*out*/ InlineCacheMap* inline_cache,
+                                             /*out*/ std::string* error) {
+  uint16_t inline_cache_size;
+  READ_UINT(uint16_t, buffer, inline_cache_size, error);
+  for (; inline_cache_size > 0; inline_cache_size--) {
+    uint16_t dex_pc;
+    uint8_t dex_to_classes_map_size;
+    READ_UINT(uint16_t, buffer, dex_pc, error);
+    READ_UINT(uint8_t, buffer, dex_to_classes_map_size, error);
+    auto dex_pc_data_it = inline_cache->FindOrAdd(dex_pc);
+    if (dex_to_classes_map_size == kMegamorphicEncoding) {
+      dex_pc_data_it->second.SetMegamorphic();
+      continue;
+    }
+    for (; dex_to_classes_map_size > 0; dex_to_classes_map_size--) {
+      uint8_t dex_profile_index;
+      uint8_t dex_classes_size;
+      READ_UINT(uint8_t, buffer, dex_profile_index, error);
+      READ_UINT(uint8_t, buffer, dex_classes_size, error);
+      if (dex_profile_index >= number_of_dex_files) {
+        *error = "dex_profile_index out of bounds ";
+        *error += std::to_string(dex_profile_index) + " " + std::to_string(number_of_dex_files);
+        return false;
+      }
+      for (; dex_classes_size > 0; dex_classes_size--) {
+        uint16_t type_index;
+        READ_UINT(uint16_t, buffer, type_index, error);
+        dex_pc_data_it->second.AddClass(dex_profile_index, dex::TypeIndex(type_index));
+      }
+    }
+  }
+  return true;
+}
+
+bool ProfileCompilationInfo::ReadMethods(SafeBuffer& buffer,
+                                         uint8_t number_of_dex_files,
+                                         const ProfileLineHeader& line_header,
+                                         /*out*/std::string* error) {
+  while (buffer.HasMoreData()) {
+    DexFileData* const data = GetOrAddDexFileData(line_header.dex_location, line_header.checksum);
+    uint16_t method_index;
+    READ_UINT(uint16_t, buffer, method_index, error);
+
+    auto it = data->method_map.FindOrAdd(method_index);
+    if (!ReadInlineCache(buffer, number_of_dex_files, &(it->second), error)) {
       return false;
     }
   }
 
-  for (uint16_t i = 0; i < class_set_size; i++) {
-    uint16_t type_idx = line_buffer.ReadUintAndAdvance<uint16_t>();
-    if (!AddClassIndex(dex_location, checksum, dex::TypeIndex(type_idx))) {
+  return true;
+}
+
+bool ProfileCompilationInfo::ReadClasses(SafeBuffer& buffer,
+                                         uint16_t classes_to_read,
+                                         const ProfileLineHeader& line_header,
+                                         /*out*/std::string* error) {
+  for (uint16_t i = 0; i < classes_to_read; i++) {
+    uint16_t type_index;
+    READ_UINT(uint16_t, buffer, type_index, error);
+    if (!AddClassIndex(line_header.dex_location,
+                       line_header.checksum,
+                       dex::TypeIndex(type_index))) {
       return false;
     }
   }
@@ -324,15 +548,17 @@
 
 // Reads an uint value previously written with AddUintToBuffer.
 template <typename T>
-T ProfileCompilationInfo::SafeBuffer::ReadUintAndAdvance() {
+bool ProfileCompilationInfo::SafeBuffer::ReadUintAndAdvance(/*out*/T* value) {
   static_assert(std::is_unsigned<T>::value, "Type is not unsigned");
-  CHECK_LE(ptr_current_ + sizeof(T), ptr_end_);
-  T value = 0;
+  if (ptr_current_ + sizeof(T) > ptr_end_) {
+    return false;
+  }
+  *value = 0;
   for (size_t i = 0; i < sizeof(T); i++) {
-    value += ptr_current_[i] << (i * kBitsPerByte);
+    *value += ptr_current_[i] << (i * kBitsPerByte);
   }
   ptr_current_ += sizeof(T);
-  return value;
+  return true;
 }
 
 bool ProfileCompilationInfo::SafeBuffer::CompareAndAdvance(const uint8_t* data, size_t data_size) {
@@ -346,6 +572,10 @@
   return false;
 }
 
+bool ProfileCompilationInfo::SafeBuffer::HasMoreData() {
+  return ptr_current_ < ptr_end_;
+}
+
 ProfileCompilationInfo::ProfileLoadSatus ProfileCompilationInfo::SafeBuffer::FillFromFd(
       int fd,
       const std::string& source,
@@ -369,13 +599,13 @@
 
 ProfileCompilationInfo::ProfileLoadSatus ProfileCompilationInfo::ReadProfileHeader(
       int fd,
-      /*out*/uint16_t* number_of_lines,
+      /*out*/uint8_t* number_of_dex_files,
       /*out*/std::string* error) {
   // Read magic and version
   const size_t kMagicVersionSize =
     sizeof(kProfileMagic) +
     sizeof(kProfileVersion) +
-    sizeof(uint16_t);  // number of lines
+    sizeof(uint8_t);  // number of dex files
 
   SafeBuffer safe_buffer(kMagicVersionSize);
 
@@ -392,24 +622,38 @@
     *error = "Profile version mismatch";
     return kProfileLoadVersionMismatch;
   }
-  *number_of_lines = safe_buffer.ReadUintAndAdvance<uint16_t>();
+  if (!safe_buffer.ReadUintAndAdvance<uint8_t>(number_of_dex_files)) {
+    *error = "Cannot read the number of dex files";
+    return kProfileLoadBadData;
+  }
   return kProfileLoadSuccess;
 }
 
+bool ProfileCompilationInfo::ReadProfileLineHeaderElements(SafeBuffer& buffer,
+                                                           /*out*/uint16_t* dex_location_size,
+                                                           /*out*/ProfileLineHeader* line_header,
+                                                           /*out*/std::string* error) {
+  READ_UINT(uint16_t, buffer, *dex_location_size, error);
+  READ_UINT(uint16_t, buffer, line_header->class_set_size, error);
+  READ_UINT(uint32_t, buffer, line_header->method_region_size_bytes, error);
+  READ_UINT(uint32_t, buffer, line_header->checksum, error);
+  return true;
+}
+
 ProfileCompilationInfo::ProfileLoadSatus ProfileCompilationInfo::ReadProfileLineHeader(
       int fd,
       /*out*/ProfileLineHeader* line_header,
       /*out*/std::string* error) {
   SafeBuffer header_buffer(kLineHeaderSize);
-  ProfileLoadSatus status = header_buffer.FillFromFd(fd, "ReadProfileHeader", error);
+  ProfileLoadSatus status = header_buffer.FillFromFd(fd, "ReadProfileLineHeader", error);
   if (status != kProfileLoadSuccess) {
     return status;
   }
 
-  uint16_t dex_location_size = header_buffer.ReadUintAndAdvance<uint16_t>();
-  line_header->method_set_size = header_buffer.ReadUintAndAdvance<uint16_t>();
-  line_header->class_set_size = header_buffer.ReadUintAndAdvance<uint16_t>();
-  line_header->checksum = header_buffer.ReadUintAndAdvance<uint32_t>();
+  uint16_t dex_location_size;
+  if (!ReadProfileLineHeaderElements(header_buffer, &dex_location_size, line_header, error)) {
+    return kProfileLoadBadData;
+  }
 
   if (dex_location_size == 0 || dex_location_size > kMaxDexFileKeyLength) {
     *error = "DexFileKey has an invalid size: " +
@@ -429,37 +673,38 @@
 
 ProfileCompilationInfo::ProfileLoadSatus ProfileCompilationInfo::ReadProfileLine(
       int fd,
+      uint8_t number_of_dex_files,
       const ProfileLineHeader& line_header,
       /*out*/std::string* error) {
-  // Make sure that we don't try to read everything in memory (in case the profile if full).
-  // Split readings in chunks of at most 10kb.
-  static constexpr uint16_t kMaxNumberOfEntriesToRead = 5120;
-  uint16_t methods_left_to_read = line_header.method_set_size;
-  uint16_t classes_left_to_read = line_header.class_set_size;
+  if (GetOrAddDexFileData(line_header.dex_location, line_header.checksum) == nullptr) {
+    *error = "Error when reading profile file line header: checksum mismatch for "
+        + line_header.dex_location;
+    return kProfileLoadBadData;
+  }
 
-  while ((methods_left_to_read > 0) || (classes_left_to_read > 0)) {
-    uint16_t methods_to_read = std::min(kMaxNumberOfEntriesToRead, methods_left_to_read);
-    uint16_t max_classes_to_read = kMaxNumberOfEntriesToRead - methods_to_read;
-    uint16_t classes_to_read = std::min(max_classes_to_read, classes_left_to_read);
-
-    size_t line_size = sizeof(uint16_t) * (methods_to_read + classes_to_read);
-    SafeBuffer line_buffer(line_size);
-
-    ProfileLoadSatus status = line_buffer.FillFromFd(fd, "ReadProfileLine", error);
+  {
+    SafeBuffer buffer(line_header.method_region_size_bytes);
+    ProfileLoadSatus status = buffer.FillFromFd(fd, "ReadProfileLineMethods", error);
     if (status != kProfileLoadSuccess) {
       return status;
     }
-    if (!ProcessLine(line_buffer,
-                     methods_to_read,
-                     classes_to_read,
-                     line_header.checksum,
-                     line_header.dex_location)) {
-      *error = "Error when reading profile file line";
+
+    if (!ReadMethods(buffer, number_of_dex_files, line_header, error)) {
       return kProfileLoadBadData;
     }
-    methods_left_to_read -= methods_to_read;
-    classes_left_to_read -= classes_to_read;
   }
+
+  {
+    SafeBuffer buffer(sizeof(uint16_t) * line_header.class_set_size);
+    ProfileLoadSatus status = buffer.FillFromFd(fd, "ReadProfileLineClasses", error);
+    if (status != kProfileLoadSuccess) {
+      return status;
+    }
+    if (!ReadClasses(buffer, line_header.class_set_size, line_header, error)) {
+      return kProfileLoadBadData;
+    }
+  }
+
   return kProfileLoadSuccess;
 }
 
@@ -470,7 +715,7 @@
   if (status == kProfileLoadSuccess) {
     return true;
   } else {
-    PLOG(WARNING) << "Error when reading profile " << error;
+    LOG(WARNING) << "Error when reading profile: " << error;
     return false;
   }
 }
@@ -490,15 +735,16 @@
   if (stat_buffer.st_size == 0) {
     return kProfileLoadSuccess;
   }
-  // Read profile header: magic + version + number_of_lines.
-  uint16_t number_of_lines;
-  ProfileLoadSatus status = ReadProfileHeader(fd, &number_of_lines, error);
+  // Read profile header: magic + version + number_of_dex_files.
+  uint8_t number_of_dex_files;
+  ProfileLoadSatus status = ReadProfileHeader(fd, &number_of_dex_files, error);
   if (status != kProfileLoadSuccess) {
     return status;
   }
 
-  while (number_of_lines > 0) {
+  for (uint8_t k = 0; k < number_of_dex_files; k++) {
     ProfileLineHeader line_header;
+
     // First, read the line header to get the amount of data we need to read.
     status = ReadProfileLineHeader(fd, &line_header, error);
     if (status != kProfileLoadSuccess) {
@@ -506,11 +752,10 @@
     }
 
     // Now read the actual profile line.
-    status = ReadProfileLine(fd, line_header, error);
+    status = ReadProfileLine(fd, number_of_dex_files, line_header, error);
     if (status != kProfileLoadSuccess) {
       return status;
     }
-    number_of_lines--;
   }
 
   // Check that we read everything and that profiles don't contain junk data.
@@ -538,37 +783,112 @@
     }
   }
   // All checksums match. Import the data.
+
+  // The other profile might have a different indexing of dex files.
+  // That is because each dex files gets a 'dex_profile_index' on a first come first served basis.
+  // That means that the order in with the methods are added to the profile matters for the
+  // actual indices.
+  // The reason we cannot rely on the actual multidex index is that a single profile may store
+  // data from multiple splits. This means that a profile may contain a classes2.dex from split-A
+  // and one from split-B.
+
+  // First, build a mapping from other_dex_profile_index to this_dex_profile_index.
+  // This will make sure that the ClassReferences  will point to the correct dex file.
+  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;
+    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);
+  }
+
+  // Merge the actual profile data.
   for (const auto& other_it : other.info_) {
     const std::string& other_dex_location = other_it.first;
     const DexFileData& other_dex_data = other_it.second;
     auto info_it = info_.find(other_dex_location);
-    if (info_it == info_.end()) {
-      info_it = info_.Put(other_dex_location, DexFileData(other_dex_data.checksum));
-    }
-    info_it->second.method_set.insert(other_dex_data.method_set.begin(),
-                                      other_dex_data.method_set.end());
+    DCHECK(info_it != info_.end());
+
+    // Merge the classes.
     info_it->second.class_set.insert(other_dex_data.class_set.begin(),
                                      other_dex_data.class_set.end());
+
+    // Merge the methods and the inline caches.
+    for (const auto& other_method_it : other_dex_data.method_map) {
+      uint16_t other_method_index = other_method_it.first;
+      auto method_it = info_it->second.method_map.FindOrAdd(other_method_index);
+      const auto& other_inline_cache = other_method_it.second;
+      for (const auto& other_ic_it : other_inline_cache) {
+        uint16_t other_dex_pc = other_ic_it.first;
+        const ClassSet& other_class_set = other_ic_it.second.classes;
+        auto class_set = method_it->second.FindOrAdd(other_dex_pc);
+        for (const auto& class_it : other_class_set) {
+          class_set->second.AddClass(dex_profile_index_remap.Get(
+              class_it.dex_profile_index), class_it.type_index);
+        }
+      }
+    }
   }
   return true;
 }
 
+static bool ChecksumMatch(uint32_t dex_file_checksum, uint32_t checksum) {
+  return kDebugIgnoreChecksum || dex_file_checksum == checksum;
+}
+
 static bool ChecksumMatch(const DexFile& dex_file, uint32_t checksum) {
-  return kDebugIgnoreChecksum || dex_file.GetLocationChecksum() == checksum;
+  return ChecksumMatch(dex_file.GetLocationChecksum(), checksum);
 }
 
 bool ProfileCompilationInfo::ContainsMethod(const MethodReference& method_ref) const {
-  auto info_it = info_.find(GetProfileDexFileKey(method_ref.dex_file->GetLocation()));
-  if (info_it != info_.end()) {
-    if (!ChecksumMatch(*method_ref.dex_file, info_it->second.checksum)) {
-      return false;
-    }
-    const std::set<uint16_t>& methods = info_it->second.method_set;
-    return methods.find(method_ref.dex_method_index) != methods.end();
-  }
-  return false;
+  return FindMethod(method_ref.dex_file->GetLocation(),
+                    method_ref.dex_file->GetLocationChecksum(),
+                    method_ref.dex_method_index) != nullptr;
 }
 
+const ProfileCompilationInfo::InlineCacheMap*
+ProfileCompilationInfo::FindMethod(const std::string& dex_location,
+                                   uint32_t dex_checksum,
+                                   uint16_t dex_method_index) const {
+  auto info_it = info_.find(GetProfileDexFileKey(dex_location));
+  if (info_it != info_.end()) {
+    if (!ChecksumMatch(dex_checksum, info_it->second.checksum)) {
+      return nullptr;
+    }
+    const MethodMap& methods = info_it->second.method_map;
+    const auto method_it = methods.find(dex_method_index);
+    return method_it == methods.end() ? nullptr : &(method_it->second);
+  }
+  return nullptr;
+}
+
+void ProfileCompilationInfo::DexFileToProfileIndex(
+    /*out*/std::vector<DexReference>* dex_references) const {
+  dex_references->resize(info_.size());
+  for (const auto& info_it : info_) {
+    DexReference& dex_ref = (*dex_references)[info_it.second.profile_index];
+    dex_ref.dex_location = info_it.first;
+    dex_ref.dex_checksum = info_it.second.checksum;
+  }
+}
+
+bool ProfileCompilationInfo::GetMethod(const std::string& dex_location,
+                                       uint32_t dex_checksum,
+                                       uint16_t dex_method_index,
+                                       /*out*/OfflineProfileMethodInfo* pmi) const {
+  const InlineCacheMap* inline_caches = FindMethod(dex_location, dex_checksum, dex_method_index);
+  if (inline_caches == nullptr) {
+    return false;
+  }
+
+  DexFileToProfileIndex(&pmi->dex_references);
+  // TODO(calin): maybe expose a direct pointer to avoid copying
+  pmi->inline_caches = *inline_caches;
+  return true;
+}
+
+
 bool ProfileCompilationInfo::ContainsClass(const DexFile& dex_file, dex::TypeIndex type_idx) const {
   auto info_it = info_.find(GetProfileDexFileKey(dex_file.GetLocation()));
   if (info_it != info_.end()) {
@@ -584,7 +904,7 @@
 uint32_t ProfileCompilationInfo::GetNumberOfMethods() const {
   uint32_t total = 0;
   for (const auto& it : info_) {
-    total += it.second.method_set.size();
+    total += it.second.method_map.size();
   }
   return total;
 }
@@ -645,19 +965,34 @@
       }
     }
     os << "\n\tmethods: ";
-    for (const auto method_it : dex_data.method_set) {
+    for (const auto method_it : dex_data.method_map) {
       if (dex_file != nullptr) {
-        os << "\n\t\t" << dex_file->PrettyMethod(method_it, true);
+        os << "\n\t\t" << dex_file->PrettyMethod(method_it.first, true);
       } else {
-        os << method_it << ",";
+        os << method_it.first;
       }
+
+      os << "[";
+      for (const auto& inline_cache_it : method_it.second) {
+        os << "{" << std::hex << inline_cache_it.first << std::dec << ":";
+        if (inline_cache_it.second.is_megamorphic) {
+          os << "M";
+        } else {
+          for (const ClassReference& class_ref : inline_cache_it.second.classes) {
+            os << "(" << static_cast<uint32_t>(class_ref.dex_profile_index)
+               << "," << class_ref.type_index.index_ << ")";
+          }
+        }
+        os << "}";
+      }
+      os << "], ";
     }
     os << "\n\tclasses: ";
     for (const auto class_it : dex_data.class_set) {
       if (dex_file != nullptr) {
         os << "\n\t\t" << dex_file->PrettyType(class_it);
       } else {
-        os << class_it << ",";
+        os << class_it.index_ << ",";
       }
     }
   }
@@ -762,4 +1097,44 @@
   return info.Save(fd);
 }
 
+bool ProfileCompilationInfo::OfflineProfileMethodInfo::operator==(
+      const OfflineProfileMethodInfo& other) const {
+  if (inline_caches.size() != other.inline_caches.size()) {
+    return false;
+  }
+
+  // We can't use a simple equality test because we need to match the dex files
+  // of the inline caches which might have different profile indices.
+  for (const auto& inline_cache_it : inline_caches) {
+    uint16_t dex_pc = inline_cache_it.first;
+    const DexPcData dex_pc_data = inline_cache_it.second;
+    const auto other_it = other.inline_caches.find(dex_pc);
+    if (other_it == other.inline_caches.end()) {
+      return false;
+    }
+    const DexPcData& other_dex_pc_data = other_it->second;
+    if (dex_pc_data.is_megamorphic != other_dex_pc_data.is_megamorphic) {
+      return false;
+    }
+    for (const ClassReference& class_ref : dex_pc_data.classes) {
+      bool found = false;
+      for (const ClassReference& other_class_ref : other_dex_pc_data.classes) {
+        CHECK_LE(class_ref.dex_profile_index, dex_references.size());
+        CHECK_LE(other_class_ref.dex_profile_index, other.dex_references.size());
+        const DexReference& dex_ref = dex_references[class_ref.dex_profile_index];
+        const DexReference& other_dex_ref = other.dex_references[other_class_ref.dex_profile_index];
+        if (class_ref.type_index == other_class_ref.type_index &&
+            dex_ref == other_dex_ref) {
+          found = true;
+          break;
+        }
+      }
+      if (!found) {
+        return false;
+      }
+    }
+  }
+  return true;
+}
+
 }  // namespace art
diff --git a/runtime/jit/profile_compilation_info.h b/runtime/jit/profile_compilation_info.h
index 758b46d..4bfbfcd 100644
--- a/runtime/jit/profile_compilation_info.h
+++ b/runtime/jit/profile_compilation_info.h
@@ -31,6 +31,40 @@
 namespace art {
 
 /**
+ *  Convenient class to pass around profile information (including inline caches)
+ *  without the need to hold GC-able objects.
+ */
+struct ProfileMethodInfo {
+  struct ProfileClassReference {
+    ProfileClassReference(const DexFile* dex, const dex::TypeIndex& index)
+        : dex_file(dex), type_index(index) {}
+
+    const DexFile* dex_file;
+    const dex::TypeIndex type_index;
+  };
+
+  struct ProfileInlineCache {
+    ProfileInlineCache(uint32_t pc, const std::vector<ProfileClassReference>& profile_classes)
+        : dex_pc(pc), classes(profile_classes) {}
+
+    const uint32_t dex_pc;
+    const std::vector<ProfileClassReference> classes;
+  };
+
+  ProfileMethodInfo(const DexFile* dex, uint32_t method_index)
+      : dex_file(dex), dex_method_index(method_index) {}
+
+  ProfileMethodInfo(const DexFile* dex,
+                    uint32_t method_index,
+                    const std::vector<ProfileInlineCache>& caches)
+      : dex_file(dex), dex_method_index(method_index), inline_caches(caches) {}
+
+  const DexFile* dex_file;
+  const uint32_t dex_method_index;
+  const std::vector<ProfileInlineCache> inline_caches;
+};
+
+/**
  * Profile information in a format suitable to be queried by the compiler and
  * performing profile guided compilation.
  * It is a serialize-friendly format based on information collected by the
@@ -42,34 +76,130 @@
   static const uint8_t kProfileMagic[];
   static const uint8_t kProfileVersion[];
 
+  // Data structures for encoding the offline representation of inline caches.
+  // This is exposed as public in order to make it available to dex2oat compilations
+  // (see compiler/optimizing/inliner.cc).
+
+  // A dex location together with its checksum.
+  struct DexReference {
+    DexReference() {}
+
+    DexReference(const std::string& location, uint32_t checksum)
+        : dex_location(location), dex_checksum(checksum) {}
+
+    bool operator==(const DexReference& other) const {
+      return dex_checksum == other.dex_checksum && dex_location == other.dex_location;
+    }
+
+    std::string dex_location;
+    uint32_t dex_checksum;
+  };
+
+  // Encodes a class reference in the profile.
+  // The owning dex file is encoded as the index (dex_profile_index) it has in the
+  // profile rather than as a full DexRefence(location,checksum).
+  // This avoids excessive string copying when managing the profile data.
+  // The dex_profile_index is an index in either of:
+  //  - OfflineProfileMethodInfo#dex_references vector (public use)
+  //  - DexFileData#profile_index (internal use).
+  // Note that the dex_profile_index is not necessary the multidex index.
+  // We cannot rely on the actual multidex index because a single profile may store
+  // data from multiple splits. This means that a profile may contain a classes2.dex from split-A
+  // and one from split-B.
+  struct ClassReference {
+    ClassReference(uint8_t dex_profile_idx, const dex::TypeIndex& type_idx) :
+      dex_profile_index(dex_profile_idx), type_index(type_idx) {}
+
+    bool operator==(const ClassReference& other) const {
+      return dex_profile_index == other.dex_profile_index && type_index == other.type_index;
+    }
+    bool operator<(const ClassReference& other) const {
+      return dex_profile_index == other.dex_profile_index
+          ? type_index < other.type_index
+          : dex_profile_index < other.dex_profile_index;
+    }
+
+    uint8_t dex_profile_index;  // the index of the owning dex in the profile info
+    dex::TypeIndex type_index;  // the type index of the class
+  };
+
+  // The set of classes that can be found at a given dex pc.
+  using ClassSet = std::set<ClassReference>;
+
+  // Encodes the actual inline cache for a given dex pc (whether or not the receiver is
+  // megamorphic and its possible types).
+  // If the receiver is megamorphic the set of classes will be empty.
+  struct DexPcData {
+    DexPcData() : is_megamorphic(false) {}
+    void AddClass(uint16_t dex_profile_idx, const dex::TypeIndex& type_idx);
+    void SetMegamorphic() {
+      is_megamorphic = true;
+      classes.clear();
+    }
+    bool operator==(const DexPcData& other) const {
+      return is_megamorphic == other.is_megamorphic && classes == other.classes;
+    }
+
+    bool is_megamorphic;
+    ClassSet classes;
+  };
+
+  // The inline cache map: DexPc -> DexPcData.
+  using InlineCacheMap = SafeMap<uint16_t, DexPcData>;
+
+  // Encodes the full set of inline caches for a given method.
+  // The dex_references vector is indexed according to the ClassReference::dex_profile_index.
+  // i.e. the dex file of any ClassReference present in the inline caches can be found at
+  // dex_references[ClassReference::dex_profile_index].
+  struct OfflineProfileMethodInfo {
+    bool operator==(const OfflineProfileMethodInfo& other) const;
+
+    std::vector<DexReference> dex_references;
+    InlineCacheMap inline_caches;
+  };
+
+  // Public methods to create, extend or query the profile.
+
   // Add the given methods and classes to the current profile object.
-  bool AddMethodsAndClasses(const std::vector<MethodReference>& methods,
+  bool AddMethodsAndClasses(const std::vector<ProfileMethodInfo>& methods,
                             const std::set<DexCacheResolvedClasses>& resolved_classes);
-  // Loads profile information from the given file descriptor.
+
+  // Load profile information from the given file descriptor.
   bool Load(int fd);
+
   // Merge the data from another ProfileCompilationInfo into the current object.
   bool MergeWith(const ProfileCompilationInfo& info);
-  // Saves the profile data to the given file descriptor.
+
+  // Save the profile data to the given file descriptor.
   bool Save(int fd);
-  // Loads and merges profile information from the given file into the current
+
+  // Load and merge profile information from the given file into the current
   // object and tries to save it back to disk.
   // If `force` is true then the save will go through even if the given file
   // has bad data or its version does not match. In this cases the profile content
   // is ignored.
   bool MergeAndSave(const std::string& filename, uint64_t* bytes_written, bool force);
 
-  // Returns the number of methods that were profiled.
+  // Return the number of methods that were profiled.
   uint32_t GetNumberOfMethods() const;
-  // Returns the number of resolved classes that were profiled.
+
+  // Return the number of resolved classes that were profiled.
   uint32_t GetNumberOfResolvedClasses() const;
 
-  // Returns true if the method reference is present in the profiling info.
+  // Return true if the method reference is present in the profiling info.
   bool ContainsMethod(const MethodReference& method_ref) const;
 
-  // Returns true if the class's type is present in the profiling info.
+  // Return true if the class's type is present in the profiling info.
   bool ContainsClass(const DexFile& dex_file, dex::TypeIndex type_idx) const;
 
-  // Dumps all the loaded profile info into a string and returns it.
+  // Return true if the method is present in the profiling info.
+  // If the method is found, `pmi` is populated with its inline caches.
+  bool GetMethod(const std::string& dex_location,
+                 uint32_t dex_checksum,
+                 uint16_t dex_method_index,
+                 /*out*/OfflineProfileMethodInfo* pmi) const;
+
+  // Dump all the loaded profile info into a string and returns it.
   // If dex_files is not null then the method indices will be resolved to their
   // names.
   // This is intended for testing and debugging.
@@ -80,26 +210,35 @@
 
   void GetClassNames(const std::vector<std::unique_ptr<const DexFile>>* dex_files,
                      std::set<std::string>* class_names) const;
+
   void GetClassNames(const std::vector<const DexFile*>* dex_files,
                      std::set<std::string>* class_names) const;
 
+  // Perform an equality test with the `other` profile information.
   bool Equals(const ProfileCompilationInfo& other);
 
-  static std::string GetProfileDexFileKey(const std::string& dex_location);
-
-  // Returns the class descriptors for all of the classes in the profiles' class sets.
+  // Return the class descriptors for all of the classes in the profiles' class sets.
   // Note the dex location is actually the profile key, the caller needs to call back in to the
   // profile info stuff to generate a map back to the dex location.
   std::set<DexCacheResolvedClasses> GetResolvedClasses() const;
 
-  // Clears the resolved classes from the current object.
+  // Clear the resolved classes from the current object.
   void ClearResolvedClasses();
 
+  // Return the profile key associated with the given dex location.
+  static std::string GetProfileDexFileKey(const std::string& dex_location);
+
+  // Generate a test profile which will contain a percentage of the total maximum
+  // number of methods and classes (method_ratio and class_ratio).
   static bool GenerateTestProfile(int fd,
                                   uint16_t number_of_dex_files,
                                   uint16_t method_ratio,
                                   uint16_t class_ratio);
 
+  // Check that the given profile method info contain the same data.
+  static bool Equals(const ProfileCompilationInfo::OfflineProfileMethodInfo& pmi1,
+                     const ProfileCompilationInfo::OfflineProfileMethodInfo& pmi2);
+
  private:
   enum ProfileLoadSatus {
     kProfileLoadIOError,
@@ -108,30 +247,71 @@
     kProfileLoadSuccess
   };
 
+  // Maps a method dex index to its inline cache.
+  using MethodMap = SafeMap<uint16_t, InlineCacheMap>;
+
+  // Internal representation of the profile information belonging to a dex file.
   struct DexFileData {
-    explicit DexFileData(uint32_t location_checksum) : checksum(location_checksum) {}
+    DexFileData(uint32_t location_checksum, uint16_t index)
+         : profile_index(index), checksum(location_checksum) {}
+    // The profile index of this dex file (matches ClassReference#dex_profile_index)
+    uint8_t profile_index;
+    // The dex checksum
     uint32_t checksum;
-    std::set<uint16_t> method_set;
+    // The methonds' profile information
+    MethodMap method_map;
+    // The classes which have been profiled. Note that these don't necessarily include
+    // all the classes that can be found in the inline caches reference.
     std::set<dex::TypeIndex> class_set;
 
     bool operator==(const DexFileData& other) const {
-      return checksum == other.checksum && method_set == other.method_set;
+      return checksum == other.checksum && method_map == other.method_map;
     }
   };
 
+  // Maps dex file to their profile information.
   using DexFileToProfileInfoMap = SafeMap<const std::string, DexFileData>;
 
+  // Return the profile data for the given dex location or null if the dex location
+  // already exists but has a different checksum
   DexFileData* GetOrAddDexFileData(const std::string& dex_location, uint32_t checksum);
+
+  // Add a method index to the profile (without inline caches).
   bool AddMethodIndex(const std::string& dex_location, uint32_t checksum, uint16_t method_idx);
+
+  // Add a method to the profile using its online representation (containing runtime structures).
+  bool AddMethod(const ProfileMethodInfo& pmi);
+
+  // Add a method to the profile using its offline representation.
+  // This is mostly used to facilitate testing.
+  bool AddMethod(const std::string& dex_location,
+                 uint32_t dex_checksum,
+                 uint16_t method_index,
+                 const OfflineProfileMethodInfo& pmi);
+
+  // Add a class index to the profile.
   bool AddClassIndex(const std::string& dex_location, uint32_t checksum, dex::TypeIndex type_idx);
+
+  // Add all classes from the given dex cache to the the profile.
   bool AddResolvedClasses(const DexCacheResolvedClasses& classes);
 
+  // Search for the given method in the profile.
+  // If found, its inline cache map is returned, otherwise the method returns null.
+  const InlineCacheMap* FindMethod(const std::string& dex_location,
+                                   uint32_t dex_checksum,
+                                   uint16_t dex_method_index) const;
+
+  // Encode the known dex_files into a vector. The index of a dex_reference will
+  // be the same as the profile index of the dex file (used to encode the ClassReferences).
+  void DexFileToProfileIndex(/*out*/std::vector<DexReference>* dex_references) const;
+
   // Parsing functionality.
 
+  // The information present in the header of each profile line.
   struct ProfileLineHeader {
     std::string dex_location;
-    uint16_t method_set_size;
     uint16_t class_set_size;
+    uint32_t method_region_size_bytes;
     uint32_t checksum;
   };
 
@@ -150,12 +330,15 @@
 
     // Reads an uint value (high bits to low bits) and advances the current pointer
     // with the number of bits read.
-    template <typename T> T ReadUintAndAdvance();
+    template <typename T> bool ReadUintAndAdvance(/*out*/ T* value);
 
     // Compares the given data with the content current pointer. If the contents are
     // equal it advances the current pointer by data_size.
     bool CompareAndAdvance(const uint8_t* data, size_t data_size);
 
+    // Returns true if the buffer has more data to read.
+    bool HasMoreData();
+
     // Get the underlying raw buffer.
     uint8_t* Get() { return storage_.get(); }
 
@@ -165,24 +348,63 @@
     uint8_t* ptr_end_;
   };
 
+  // Entry point for profile loding functionality.
   ProfileLoadSatus LoadInternal(int fd, std::string* error);
 
+  // Read the profile header from the given fd and store the number of profile
+  // lines into number_of_dex_files.
   ProfileLoadSatus ReadProfileHeader(int fd,
-                                     /*out*/uint16_t* number_of_lines,
+                                     /*out*/uint8_t* number_of_dex_files,
                                      /*out*/std::string* error);
 
+  // Read the header of a profile line from the given fd.
   ProfileLoadSatus ReadProfileLineHeader(int fd,
                                          /*out*/ProfileLineHeader* line_header,
                                          /*out*/std::string* error);
+
+  // Read individual elements from the profile line header.
+  bool ReadProfileLineHeaderElements(SafeBuffer& buffer,
+                                     /*out*/uint16_t* dex_location_size,
+                                     /*out*/ProfileLineHeader* line_header,
+                                     /*out*/std::string* error);
+
+  // Read a single profile line from the given fd.
   ProfileLoadSatus ReadProfileLine(int fd,
+                                   uint8_t number_of_dex_files,
                                    const ProfileLineHeader& line_header,
                                    /*out*/std::string* error);
 
-  bool ProcessLine(SafeBuffer& line_buffer,
-                   uint16_t method_set_size,
-                   uint16_t class_set_size,
-                   uint32_t checksum,
-                   const std::string& dex_location);
+  // Read all the classes from the buffer into the profile `info_` structure.
+  bool ReadClasses(SafeBuffer& buffer,
+                   uint16_t classes_to_read,
+                   const ProfileLineHeader& line_header,
+                   /*out*/std::string* error);
+
+  // Read all the methods from the buffer into the profile `info_` structure.
+  bool ReadMethods(SafeBuffer& buffer,
+                   uint8_t number_of_dex_files,
+                   const ProfileLineHeader& line_header,
+                   /*out*/std::string* error);
+
+  // Read the inline cache encoding from line_bufer into inline_cache.
+  bool ReadInlineCache(SafeBuffer& buffer,
+                       uint8_t number_of_dex_files,
+                       /*out*/InlineCacheMap* inline_cache,
+                       /*out*/std::string* error);
+
+  // Encode the inline cache into the given buffer.
+  void AddInlineCacheToBuffer(std::vector<uint8_t>* buffer,
+                              const InlineCacheMap& inline_cache);
+
+  // Return the number of bytes needed to encode the profile information
+  // for the methods in dex_data.
+  uint32_t GetMethodsRegionSize(const DexFileData& dex_data);
+
+  // Group `classes` by their owning dex profile index and put the result in
+  // `dex_to_classes_map`.
+  void GroupClassesByDex(
+      const ClassSet& classes,
+      /*out*/SafeMap<uint8_t, std::vector<dex::TypeIndex>>* dex_to_classes_map);
 
   friend class ProfileCompilationInfoTest;
   friend class CompilerDriverProfileTest;
diff --git a/runtime/jit/profile_compilation_info_test.cc b/runtime/jit/profile_compilation_info_test.cc
index 835a5f3..93b47ac 100644
--- a/runtime/jit/profile_compilation_info_test.cc
+++ b/runtime/jit/profile_compilation_info_test.cc
@@ -57,6 +57,14 @@
     return info->AddMethodIndex(dex_location, checksum, method_index);
   }
 
+  bool AddMethod(const std::string& dex_location,
+                 uint32_t checksum,
+                 uint16_t method_index,
+                 const ProfileCompilationInfo::OfflineProfileMethodInfo& pmi,
+                 ProfileCompilationInfo* info) {
+    return info->AddMethod(dex_location, checksum, method_index, pmi);
+  }
+
   bool AddClass(const std::string& dex_location,
                 uint32_t checksum,
                 uint16_t class_index,
@@ -73,17 +81,132 @@
       const std::vector<ArtMethod*>& methods,
       const std::set<DexCacheResolvedClasses>& resolved_classes) {
     ProfileCompilationInfo info;
-    std::vector<MethodReference> method_refs;
+    std::vector<ProfileMethodInfo> profile_methods;
     ScopedObjectAccess soa(Thread::Current());
     for (ArtMethod* method : methods) {
-      method_refs.emplace_back(method->GetDexFile(), method->GetDexMethodIndex());
+      profile_methods.emplace_back(method->GetDexFile(), method->GetDexMethodIndex());
     }
-    if (!info.AddMethodsAndClasses(method_refs, resolved_classes)) {
+    if (!info.AddMethodsAndClasses(profile_methods, resolved_classes)) {
+      return false;
+    }
+    if (info.GetNumberOfMethods() != profile_methods.size()) {
       return false;
     }
     return info.MergeAndSave(filename, nullptr, false);
   }
 
+  // Saves the given art methods to a profile backed by 'filename' and adds
+  // some fake inline caches to it. The added inline caches are returned in
+  // the out map `profile_methods_map`.
+  bool SaveProfilingInfoWithFakeInlineCaches(
+      const std::string& filename,
+      const std::vector<ArtMethod*>& methods,
+      /*out*/ SafeMap<ArtMethod*, ProfileMethodInfo>* profile_methods_map) {
+    ProfileCompilationInfo info;
+    std::vector<ProfileMethodInfo> profile_methods;
+    ScopedObjectAccess soa(Thread::Current());
+    for (ArtMethod* method : methods) {
+      std::vector<ProfileMethodInfo::ProfileInlineCache> caches;
+      // Monomorphic
+      for (uint16_t dex_pc = 0; dex_pc < 1; dex_pc++) {
+        std::vector<ProfileMethodInfo::ProfileClassReference> classes;
+        classes.emplace_back(method->GetDexFile(), dex::TypeIndex(0));
+        caches.emplace_back(dex_pc, classes);
+      }
+      // Polymorphic
+      for (uint16_t dex_pc = 1; dex_pc < 2; dex_pc++) {
+        std::vector<ProfileMethodInfo::ProfileClassReference> classes;
+        for (uint16_t k = 0; k < InlineCache::kIndividualCacheSize / 2; k++) {
+          classes.emplace_back(method->GetDexFile(), dex::TypeIndex(k));
+        }
+        caches.emplace_back(dex_pc, classes);
+      }
+      // Megamorphic
+      for (uint16_t dex_pc = 2; dex_pc < 3; dex_pc++) {
+        std::vector<ProfileMethodInfo::ProfileClassReference> classes;
+        for (uint16_t k = 0; k < 2 * InlineCache::kIndividualCacheSize; k++) {
+          classes.emplace_back(method->GetDexFile(), dex::TypeIndex(k));
+        }
+        caches.emplace_back(dex_pc, classes);
+      }
+      ProfileMethodInfo pmi(method->GetDexFile(), method->GetDexMethodIndex(), caches);
+      profile_methods.push_back(pmi);
+      profile_methods_map->Put(method, pmi);
+    }
+
+    if (!info.AddMethodsAndClasses(profile_methods, std::set<DexCacheResolvedClasses>())) {
+      return false;
+    }
+    if (info.GetNumberOfMethods() != profile_methods.size()) {
+      return false;
+    }
+    return info.MergeAndSave(filename, nullptr, false);
+  }
+
+  ProfileCompilationInfo::OfflineProfileMethodInfo ConvertProfileMethodInfo(
+        const ProfileMethodInfo& pmi) {
+    ProfileCompilationInfo::OfflineProfileMethodInfo offline_pmi;
+    SafeMap<DexFile*, uint8_t> dex_map;  // dex files to profile index
+    for (const auto& inline_cache : pmi.inline_caches) {
+      for (const auto& class_ref : inline_cache.classes) {
+        uint8_t dex_profile_index = dex_map.FindOrAdd(const_cast<DexFile*>(class_ref.dex_file),
+                                                      static_cast<uint8_t>(dex_map.size()))->second;
+        offline_pmi.inline_caches
+            .FindOrAdd(inline_cache.dex_pc)->second
+            .AddClass(dex_profile_index, class_ref.type_index);
+        if (dex_profile_index >= offline_pmi.dex_references.size()) {
+          // This is a new dex.
+          const std::string& dex_key = ProfileCompilationInfo::GetProfileDexFileKey(
+              class_ref.dex_file->GetLocation());
+          offline_pmi.dex_references.emplace_back(dex_key,
+                                                  class_ref.dex_file->GetLocationChecksum());
+        }
+      }
+    }
+    return offline_pmi;
+  }
+
+  // Creates an offline profile used for testing inline caches.
+  ProfileCompilationInfo::OfflineProfileMethodInfo GetOfflineProfileMethodInfo() {
+    ProfileCompilationInfo::OfflineProfileMethodInfo pmi;
+
+    pmi.dex_references.emplace_back("dex_location1", /* checksum */ 1);
+    pmi.dex_references.emplace_back("dex_location2", /* checksum */ 2);
+    pmi.dex_references.emplace_back("dex_location3", /* checksum */ 3);
+
+    // Monomorphic
+    for (uint16_t dex_pc = 0; dex_pc < 1; dex_pc++) {
+      ProfileCompilationInfo::DexPcData dex_pc_data;
+      dex_pc_data.AddClass(0, dex::TypeIndex(0));
+      pmi.inline_caches.Put(dex_pc, dex_pc_data);
+    }
+    // Polymorphic
+    for (uint16_t dex_pc = 1; dex_pc < 2; dex_pc++) {
+      ProfileCompilationInfo::DexPcData dex_pc_data;
+      dex_pc_data.AddClass(0, dex::TypeIndex(0));
+      dex_pc_data.AddClass(1, dex::TypeIndex(1));
+      dex_pc_data.AddClass(2, dex::TypeIndex(2));
+
+       pmi.inline_caches.Put(dex_pc, dex_pc_data);
+    }
+    // Megamorphic
+    for (uint16_t dex_pc = 2; dex_pc < 3; dex_pc++) {
+      ProfileCompilationInfo::DexPcData dex_pc_data;
+      dex_pc_data.is_megamorphic = true;
+      pmi.inline_caches.Put(dex_pc, dex_pc_data);
+    }
+
+    return pmi;
+  }
+
+  void MakeMegamorphic(/*out*/ProfileCompilationInfo::OfflineProfileMethodInfo* pmi) {
+    for (auto it : pmi->inline_caches) {
+      for (uint16_t k = 0; k <= 2 * InlineCache::kIndividualCacheSize; k++) {
+        it.second.AddClass(0, dex::TypeIndex(k));
+      }
+    }
+  }
+
   // Cannot sizeof the actual arrays so hardcode the values here.
   // They should not change anyway.
   static constexpr int kProfileMagicSize = 4;
@@ -235,12 +358,12 @@
 TEST_F(ProfileCompilationInfoTest, LoadEmpty) {
   ScratchFile profile;
 
-  ProfileCompilationInfo empyt_info;
+  ProfileCompilationInfo empty_info;
 
   ProfileCompilationInfo loaded_info;
   ASSERT_TRUE(profile.GetFile()->ResetOffset());
   ASSERT_TRUE(loaded_info.Load(GetFd(profile)));
-  ASSERT_TRUE(loaded_info.Equals(empyt_info));
+  ASSERT_TRUE(loaded_info.Equals(empty_info));
 }
 
 TEST_F(ProfileCompilationInfoTest, BadMagic) {
@@ -324,4 +447,214 @@
   ASSERT_FALSE(loaded_info.Load(GetFd(profile)));
 }
 
+TEST_F(ProfileCompilationInfoTest, SaveInlineCaches) {
+  ScratchFile profile;
+
+  ProfileCompilationInfo saved_info;
+  ProfileCompilationInfo::OfflineProfileMethodInfo pmi = GetOfflineProfileMethodInfo();
+
+  // Add methods with inline caches.
+  for (uint16_t method_idx = 0; method_idx < 10; method_idx++) {
+    // Add a method which is part of the same dex file as one of the
+    // class from the inline caches.
+    ASSERT_TRUE(AddMethod("dex_location1", /* checksum */ 1, method_idx, pmi, &saved_info));
+    // Add a method which is outside the set of dex files.
+    ASSERT_TRUE(AddMethod("dex_location4", /* checksum */ 4, method_idx, pmi, &saved_info));
+  }
+
+  ASSERT_TRUE(saved_info.Save(GetFd(profile)));
+  ASSERT_EQ(0, profile.GetFile()->Flush());
+
+  // Check that we get back what we saved.
+  ProfileCompilationInfo loaded_info;
+  ASSERT_TRUE(profile.GetFile()->ResetOffset());
+  ASSERT_TRUE(loaded_info.Load(GetFd(profile)));
+
+  ASSERT_TRUE(loaded_info.Equals(saved_info));
+
+  ProfileCompilationInfo::OfflineProfileMethodInfo loaded_pmi1;
+  ASSERT_TRUE(loaded_info.GetMethod("dex_location1",
+                                    /* checksum */ 1,
+                                    /* method_idx */ 3,
+                                    &loaded_pmi1));
+  ASSERT_TRUE(loaded_pmi1 == pmi);
+  ProfileCompilationInfo::OfflineProfileMethodInfo loaded_pmi2;
+  ASSERT_TRUE(loaded_info.GetMethod("dex_location4",
+                                    /* checksum */ 4,
+                                    /* method_idx */ 3,
+                                    &loaded_pmi2));
+  ASSERT_TRUE(loaded_pmi2 == pmi);
+}
+
+TEST_F(ProfileCompilationInfoTest, MegamorphicInlineCaches) {
+  ScratchFile profile;
+
+  ProfileCompilationInfo saved_info;
+  ProfileCompilationInfo::OfflineProfileMethodInfo pmi = GetOfflineProfileMethodInfo();
+
+  // Add methods with inline caches.
+  for (uint16_t method_idx = 0; method_idx < 10; method_idx++) {
+    ASSERT_TRUE(AddMethod("dex_location1", /* checksum */ 1, method_idx, pmi, &saved_info));
+  }
+
+  ASSERT_TRUE(saved_info.Save(GetFd(profile)));
+  ASSERT_EQ(0, profile.GetFile()->Flush());
+
+  // Make the inline caches megamorphic and add them to the profile again.
+  ProfileCompilationInfo saved_info_extra;
+  ProfileCompilationInfo::OfflineProfileMethodInfo pmi_extra = GetOfflineProfileMethodInfo();
+  MakeMegamorphic(&pmi_extra);
+  for (uint16_t method_idx = 0; method_idx < 10; method_idx++) {
+    ASSERT_TRUE(AddMethod("dex_location1", /* checksum */ 1, method_idx, pmi, &saved_info_extra));
+  }
+
+  ASSERT_TRUE(profile.GetFile()->ResetOffset());
+  ASSERT_TRUE(saved_info_extra.Save(GetFd(profile)));
+  ASSERT_EQ(0, profile.GetFile()->Flush());
+
+  // Merge the profiles so that we have the same view as the file.
+  ASSERT_TRUE(saved_info.MergeWith(saved_info_extra));
+
+  // Check that we get back what we saved.
+  ProfileCompilationInfo loaded_info;
+  ASSERT_TRUE(profile.GetFile()->ResetOffset());
+  ASSERT_TRUE(loaded_info.Load(GetFd(profile)));
+
+  ASSERT_TRUE(loaded_info.Equals(saved_info));
+
+  ProfileCompilationInfo::OfflineProfileMethodInfo loaded_pmi1;
+  ASSERT_TRUE(loaded_info.GetMethod("dex_location1",
+                                    /* checksum */ 1,
+                                    /* method_idx */ 3,
+                                    &loaded_pmi1));
+  ASSERT_TRUE(loaded_pmi1 == pmi_extra);
+}
+
+TEST_F(ProfileCompilationInfoTest, SaveArtMethodsWithInlineCaches) {
+  ScratchFile profile;
+
+  Thread* self = Thread::Current();
+  jobject class_loader;
+  {
+    ScopedObjectAccess soa(self);
+    class_loader = LoadDex("ProfileTestMultiDex");
+  }
+  ASSERT_NE(class_loader, nullptr);
+
+  // Save virtual methods from Main.
+  std::set<DexCacheResolvedClasses> resolved_classes;
+  std::vector<ArtMethod*> main_methods = GetVirtualMethods(class_loader, "LMain;");
+
+  SafeMap<ArtMethod*, ProfileMethodInfo> profile_methods_map;
+  ASSERT_TRUE(SaveProfilingInfoWithFakeInlineCaches(
+      profile.GetFilename(), main_methods,  &profile_methods_map));
+
+  // Check that what we saved is in the profile.
+  ProfileCompilationInfo info;
+  ASSERT_TRUE(info.Load(GetFd(profile)));
+  ASSERT_EQ(info.GetNumberOfMethods(), main_methods.size());
+  {
+    ScopedObjectAccess soa(self);
+    for (ArtMethod* m : main_methods) {
+      ASSERT_TRUE(info.ContainsMethod(MethodReference(m->GetDexFile(), m->GetDexMethodIndex())));
+      const ProfileMethodInfo& pmi = profile_methods_map.find(m)->second;
+      ProfileCompilationInfo::OfflineProfileMethodInfo offline_pmi;
+      ASSERT_TRUE(info.GetMethod(m->GetDexFile()->GetLocation(),
+                                 m->GetDexFile()->GetLocationChecksum(),
+                                 m->GetDexMethodIndex(),
+                                 &offline_pmi));
+      ProfileCompilationInfo::OfflineProfileMethodInfo converted_pmi =
+          ConvertProfileMethodInfo(pmi);
+      ASSERT_EQ(converted_pmi, offline_pmi);
+    }
+  }
+}
+
+TEST_F(ProfileCompilationInfoTest, InvalidChecksumInInlineCahce) {
+  ScratchFile profile;
+
+  ProfileCompilationInfo info;
+  ProfileCompilationInfo::OfflineProfileMethodInfo pmi1 = GetOfflineProfileMethodInfo();
+  ProfileCompilationInfo::OfflineProfileMethodInfo pmi2 = GetOfflineProfileMethodInfo();
+  // Modify the checksum to trigger a mismatch.
+  pmi2.dex_references[0].dex_checksum++;
+
+  ASSERT_TRUE(AddMethod("dex_location1", /* checksum */ 1, /*method_idx*/ 0, pmi1, &info));
+  ASSERT_FALSE(AddMethod("dex_location2", /* checksum */ 2, /*method_idx*/ 0, pmi2, &info));
+}
+
+// Verify that profiles behave correctly even if the methods are added in a different
+// order and with a different dex profile indices for the dex files.
+TEST_F(ProfileCompilationInfoTest, MergeInlineCacheTriggerReindex) {
+  ScratchFile profile;
+
+  ProfileCompilationInfo info;
+  ProfileCompilationInfo info_reindexed;
+
+  ProfileCompilationInfo::OfflineProfileMethodInfo pmi;
+  pmi.dex_references.emplace_back("dex_location1", /* checksum */ 1);
+  pmi.dex_references.emplace_back("dex_location2", /* checksum */ 2);
+  for (uint16_t dex_pc = 1; dex_pc < 5; dex_pc++) {
+    ProfileCompilationInfo::DexPcData dex_pc_data;
+    dex_pc_data.AddClass(0, dex::TypeIndex(0));
+    dex_pc_data.AddClass(1, dex::TypeIndex(1));
+    pmi.inline_caches.Put(dex_pc, dex_pc_data);
+  }
+
+  ProfileCompilationInfo::OfflineProfileMethodInfo pmi_reindexed;
+  pmi_reindexed.dex_references.emplace_back("dex_location2", /* checksum */ 2);
+  pmi_reindexed.dex_references.emplace_back("dex_location1", /* checksum */ 1);
+  for (uint16_t dex_pc = 1; dex_pc < 5; dex_pc++) {
+    ProfileCompilationInfo::DexPcData dex_pc_data;
+    dex_pc_data.AddClass(1, dex::TypeIndex(0));
+    dex_pc_data.AddClass(0, dex::TypeIndex(1));
+    pmi_reindexed.inline_caches.Put(dex_pc, dex_pc_data);
+  }
+
+  // Profile 1 and Profile 2 get the same methods but in different order.
+  // This will trigger a different dex numbers.
+  for (uint16_t method_idx = 0; method_idx < 10; method_idx++) {
+    ASSERT_TRUE(AddMethod("dex_location1", /* checksum */ 1, method_idx, pmi, &info));
+    ASSERT_TRUE(AddMethod("dex_location2", /* checksum */ 2, method_idx, pmi, &info));
+  }
+
+  for (uint16_t method_idx = 0; method_idx < 10; method_idx++) {
+    ASSERT_TRUE(AddMethod(
+      "dex_location2", /* checksum */ 2, method_idx, pmi_reindexed, &info_reindexed));
+    ASSERT_TRUE(AddMethod(
+      "dex_location1", /* checksum */ 1, method_idx, pmi_reindexed, &info_reindexed));
+  }
+
+  ProfileCompilationInfo info_backup = info;
+  ASSERT_TRUE(info.MergeWith(info_reindexed));
+  // Merging should have no effect as we're adding the exact same stuff.
+  ASSERT_TRUE(info.Equals(info_backup));
+  for (uint16_t method_idx = 0; method_idx < 10; method_idx++) {
+    ProfileCompilationInfo::OfflineProfileMethodInfo loaded_pmi1;
+    ASSERT_TRUE(info.GetMethod("dex_location1",
+                                      /* checksum */ 1,
+                                      /* method_idx */ method_idx,
+                                      &loaded_pmi1));
+    ASSERT_TRUE(loaded_pmi1 == pmi);
+    ProfileCompilationInfo::OfflineProfileMethodInfo loaded_pmi2;
+    ASSERT_TRUE(info.GetMethod("dex_location2",
+                                      /* checksum */ 2,
+                                      /* method_idx */ method_idx,
+                                      &loaded_pmi2));
+    ASSERT_TRUE(loaded_pmi2 == pmi);
+  }
+}
+
+TEST_F(ProfileCompilationInfoTest, AddMoreDexFileThanLimit) {
+  ProfileCompilationInfo info;
+  // Save a few methods.
+  for (uint16_t i = 0; i < std::numeric_limits<uint8_t>::max(); i++) {
+    std::string dex_location = std::to_string(i);
+    ASSERT_TRUE(AddMethod(dex_location, /* checksum */ 1, /* method_idx */ i, &info));
+  }
+  // We only support at most 255 dex files.
+  ASSERT_FALSE(AddMethod(
+      /*dex_location*/ "256", /* checksum */ 1, /* method_idx */ 0, &info));
+}
+
 }  // namespace art
diff --git a/runtime/jit/profile_saver.cc b/runtime/jit/profile_saver.cc
index 025d10c..61e6c41 100644
--- a/runtime/jit/profile_saver.cc
+++ b/runtime/jit/profile_saver.cc
@@ -236,10 +236,10 @@
     std::set<DexCacheResolvedClasses> resolved_classes_for_location;
     const std::string& filename = it.first;
     const std::set<std::string>& locations = it.second;
-    std::vector<MethodReference> methods_for_location;
+    std::vector<ProfileMethodInfo> profile_methods_for_location;
     for (const MethodReference& ref : methods) {
       if (locations.find(ref.dex_file->GetBaseLocation()) != locations.end()) {
-        methods_for_location.push_back(ref);
+        profile_methods_for_location.emplace_back(ref.dex_file, ref.dex_method_index);
       }
     }
     for (const DexCacheResolvedClasses& classes : resolved_classes) {
@@ -253,7 +253,7 @@
       }
     }
     ProfileCompilationInfo* info = GetCachedProfiledInfo(filename);
-    info->AddMethodsAndClasses(methods_for_location, resolved_classes_for_location);
+    info->AddMethodsAndClasses(profile_methods_for_location, resolved_classes_for_location);
     total_number_of_profile_entries_cached += resolved_classes_for_location.size();
   }
   max_number_of_profile_entries_cached_ = std::max(
@@ -280,15 +280,15 @@
     }
     const std::string& filename = it.first;
     const std::set<std::string>& locations = it.second;
-    std::vector<MethodReference> methods;
+    std::vector<ProfileMethodInfo> profile_methods;
     {
       ScopedObjectAccess soa(Thread::Current());
-      jit_code_cache_->GetProfiledMethods(locations, methods);
+      jit_code_cache_->GetProfiledMethods(locations, profile_methods);
       total_number_of_code_cache_queries_++;
     }
 
     ProfileCompilationInfo* cached_info = GetCachedProfiledInfo(filename);
-    cached_info->AddMethodsAndClasses(methods, std::set<DexCacheResolvedClasses>());
+    cached_info->AddMethodsAndClasses(profile_methods, std::set<DexCacheResolvedClasses>());
     int64_t delta_number_of_methods =
         cached_info->GetNumberOfMethods() -
         static_cast<int64_t>(last_save_number_of_methods_);
diff --git a/runtime/jit/profiling_info.h b/runtime/jit/profiling_info.h
index 1c58a83..f42a8da 100644
--- a/runtime/jit/profiling_info.h
+++ b/runtime/jit/profiling_info.h
@@ -39,7 +39,7 @@
 // Once the classes_ array is full, we consider the INVOKE to be megamorphic.
 class InlineCache {
  public:
-  static constexpr uint16_t kIndividualCacheSize = 5;
+  static constexpr uint8_t kIndividualCacheSize = 5;
 
  private:
   uint32_t dex_pc_;
diff --git a/runtime/safe_map.h b/runtime/safe_map.h
index 49f80f3..e638fdb 100644
--- a/runtime/safe_map.h
+++ b/runtime/safe_map.h
@@ -137,6 +137,16 @@
     return it->second;
   }
 
+  iterator FindOrAdd(const K& k, const V& v) {
+    iterator it = find(k);
+    return it == end() ? Put(k, v) : it;
+  }
+
+  iterator FindOrAdd(const K& k) {
+    iterator it = find(k);
+    return it == end() ? Put(k, V()) : it;
+  }
+
   bool Equals(const Self& rhs) const {
     return map_ == rhs.map_;
   }