Add support for generating boot image profile

Added three options:
--generate-boot-image-profile:
If this option is passed in, profman creates a boot image profile.

--boot-image-clean-class-threshold=<value>
Specifies how many occurrences of a likely clean class are required
before a class is added to the profile.

--boot-image-class-threshold=<value>
Specify how many occurrences of a possibly dirty class are required
before a class is added to the profile.

Added unit test.

Test: test-art-host

Bug: 37966211

Change-Id: I8e12b0ec34dfa1d1bed0b51f342fffde09815348
diff --git a/profman/Android.bp b/profman/Android.bp
index a327ef2..2a45c46 100644
--- a/profman/Android.bp
+++ b/profman/Android.bp
@@ -19,6 +19,7 @@
     host_supported: true,
     defaults: ["art_defaults"],
     srcs: [
+        "boot_image_profile.cc",
         "profman.cc",
         "profile_assistant.cc",
     ],
diff --git a/profman/boot_image_profile.cc b/profman/boot_image_profile.cc
new file mode 100644
index 0000000..21de083
--- /dev/null
+++ b/profman/boot_image_profile.cc
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <memory>
+#include <set>
+
+#include "boot_image_profile.h"
+#include "dex_file-inl.h"
+#include "method_reference.h"
+#include "type_reference.h"
+
+namespace art {
+
+using Hotness = ProfileCompilationInfo::MethodHotness;
+
+void GenerateBootImageProfile(
+    const std::vector<std::unique_ptr<const DexFile>>& dex_files,
+    const std::vector<std::unique_ptr<const ProfileCompilationInfo>>& profiles,
+    const BootImageOptions& options,
+    bool verbose,
+    ProfileCompilationInfo* out_profile) {
+  for (const std::unique_ptr<const ProfileCompilationInfo>& profile : profiles) {
+    // Avoid merging classes since we may want to only add classes that fit a certain criteria.
+    // If we merged the classes, every single class in each profile would be in the out_profile,
+    // but we want to only included classes that are in at least a few profiles.
+    out_profile->MergeWith(*profile, /*merge_classes*/ false);
+  }
+
+  // Image classes that were added because they are commonly used.
+  size_t class_count = 0;
+  // Image classes that were only added because they were clean.
+  size_t clean_class_count = 0;
+  // Total clean classes.
+  size_t clean_count = 0;
+  // Total dirty classes.
+  size_t dirty_count = 0;
+
+  for (const std::unique_ptr<const DexFile>& dex_file : dex_files) {
+    // Inferred classes are classes inferred from method samples.
+    std::set<std::pair<const ProfileCompilationInfo*, dex::TypeIndex>> inferred_classes;
+    for (size_t i = 0; i < dex_file->NumMethodIds(); ++i) {
+      MethodReference ref(dex_file.get(), i);
+      // This counter is how many profiles contain the method as sampled or hot.
+      size_t counter = 0;
+      for (const std::unique_ptr<const ProfileCompilationInfo>& profile : profiles) {
+        Hotness hotness = profile->GetMethodHotness(ref);
+        if (hotness.IsInProfile()) {
+          ++counter;
+          out_profile->AddMethodHotness(ref, hotness);
+          inferred_classes.emplace(profile.get(),
+                                   dex_file->GetMethodId(ref.dex_method_index).class_idx_);
+        }
+      }
+      // If the counter is greater or equal to the compile threshold, mark the method as hot.
+      // Note that all hot methods are also marked as hot in the out profile during the merging
+      // process.
+      if (counter >= options.compiled_method_threshold) {
+        Hotness hotness;
+        hotness.AddFlag(Hotness::kFlagHot);
+        out_profile->AddMethodHotness(ref, hotness);
+      }
+    }
+    // Walk all of the classes and add them to the profile if they meet the requirements.
+    for (size_t i = 0; i < dex_file->NumClassDefs(); ++i) {
+      const DexFile::ClassDef& class_def = dex_file->GetClassDef(i);
+      TypeReference ref(dex_file.get(), class_def.class_idx_);
+      bool is_clean = true;
+      const uint8_t* class_data = dex_file->GetClassData(class_def);
+      if (class_data != nullptr) {
+        ClassDataItemIterator it(*dex_file, class_data);
+        while (it.HasNextStaticField()) {
+          const uint32_t flags = it.GetFieldAccessFlags();
+          if ((flags & kAccFinal) == 0) {
+            // Not final static field will probably dirty the class.
+            is_clean = false;
+            break;
+          }
+          it.Next();
+        }
+        it.SkipInstanceFields();
+        while (it.HasNextDirectMethod() || it.HasNextVirtualMethod()) {
+          const uint32_t flags = it.GetMethodAccessFlags();
+          if ((flags & kAccNative) != 0 || (flags & kAccFastNative) != 0) {
+            // Native method will get dirtied.
+            is_clean = false;
+            break;
+          }
+          if ((flags & kAccConstructor) != 0 && (flags & kAccStatic) != 0) {
+            // Class initializer, may get dirtied (not sure).
+            is_clean = false;
+            break;
+          }
+          it.Next();
+        }
+      }
+      ++(is_clean ? clean_count : dirty_count);
+      // This counter is how many profiles contain the class.
+      size_t counter = 0;
+      for (const std::unique_ptr<const ProfileCompilationInfo>& profile : profiles) {
+        auto it = inferred_classes.find(std::make_pair(profile.get(), ref.type_index));
+        if (it != inferred_classes.end() ||
+            profile->ContainsClass(*ref.dex_file, ref.type_index)) {
+          ++counter;
+        }
+      }
+      if (counter == 0) {
+        continue;
+      }
+      if (counter >= options.image_class_theshold) {
+        ++class_count;
+        out_profile->AddClassesForDex(ref.dex_file, &ref.type_index, &ref.type_index + 1);
+      } else if (is_clean && counter >= options.image_class_clean_theshold) {
+        ++clean_class_count;
+        out_profile->AddClassesForDex(ref.dex_file, &ref.type_index, &ref.type_index + 1);
+      }
+    }
+  }
+  if (verbose) {
+    LOG(INFO) << "Image classes " << class_count + clean_class_count
+              << " added because clean " << clean_class_count
+              << " total clean " << clean_count << " total dirty " << dirty_count;
+  }
+}
+
+}  // namespace art
diff --git a/profman/boot_image_profile.h b/profman/boot_image_profile.h
new file mode 100644
index 0000000..d02e408
--- /dev/null
+++ b/profman/boot_image_profile.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ART_PROFMAN_BOOT_IMAGE_PROFILE_H_
+#define ART_PROFMAN_BOOT_IMAGE_PROFILE_H_
+
+#include <limits>
+#include <memory>
+#include <vector>
+
+#include "dex_file.h"
+#include "jit/profile_compilation_info.h"
+
+namespace art {
+
+struct BootImageOptions {
+ public:
+  // Threshold for classes that may be dirty or clean. The threshold specifies how
+  // many different profiles need to have the class before it gets added to the boot profile.
+  uint32_t image_class_theshold = 10;
+
+  // Threshold for classes that are likely to remain clean. The threshold specifies how
+  // many different profiles need to have the class before it gets added to the boot profile.
+  uint32_t image_class_clean_theshold = 3;
+
+  // Threshold for non-hot methods to be compiled. The threshold specifies how
+  // many different profiles need to have the method before it gets added to the boot profile.
+  uint32_t compiled_method_threshold = std::numeric_limits<uint32_t>::max();
+};
+
+// Merge a bunch of profiles together to generate a boot profile. Classes and methods are added
+// to the out_profile if they meet the options.
+void GenerateBootImageProfile(
+    const std::vector<std::unique_ptr<const DexFile>>& dex_files,
+    const std::vector<std::unique_ptr<const ProfileCompilationInfo>>& profiles,
+    const BootImageOptions& options,
+    bool verbose,
+    ProfileCompilationInfo* out_profile);
+
+}  // namespace art
+
+#endif  // ART_PROFMAN_BOOT_IMAGE_PROFILE_H_
diff --git a/profman/profile_assistant_test.cc b/profman/profile_assistant_test.cc
index c6b06af..75f8ec9 100644
--- a/profman/profile_assistant_test.cc
+++ b/profman/profile_assistant_test.cc
@@ -621,6 +621,100 @@
   EXPECT_GT(method_count, 0u);
 }
 
+TEST_F(ProfileAssistantTest, TestBootImageProfile) {
+  const std::string core_dex = GetLibCoreDexFileNames()[0];
+
+  std::vector<ScratchFile> profiles;
+
+  // In image with enough clean occurrences.
+  const std::string kCleanClass = "Ljava/lang/CharSequence;";
+  // In image with enough dirty occurrences.
+  const std::string kDirtyClass = "Ljava/lang/Object;";
+  // Not in image becauseof not enough occurrences.
+  const std::string kUncommonCleanClass = "Ljava/lang/Process;";
+  const std::string kUncommonDirtyClass = "Ljava/lang/Package;";
+  // Method that is hot.
+  // Also adds the class through inference since it is in each dex.
+  const std::string kHotMethod = "Ljava/lang/Comparable;->compareTo(Ljava/lang/Object;)I";
+  // Method that doesn't add the class since its only in one profile. Should still show up in the
+  // boot profile.
+  const std::string kOtherMethod = "Ljava/util/HashMap;-><init>()V";
+
+  // Thresholds for this test.
+  static const size_t kDirtyThreshold = 3;
+  static const size_t kCleanThreshold = 2;
+
+  // Create a bunch of boot profiles.
+  std::string dex1 =
+      kCleanClass + "\n" +
+      kDirtyClass + "\n" +
+      kUncommonCleanClass + "\n" +
+      "H" + kHotMethod + "\n" +
+      kUncommonDirtyClass;
+  profiles.emplace_back(ScratchFile());
+  EXPECT_TRUE(CreateProfile(dex1, profiles.back().GetFilename(), core_dex));
+
+  // Create a bunch of boot profiles.
+  std::string dex2 =
+      kCleanClass + "\n" +
+      kDirtyClass + "\n" +
+      "P" + kHotMethod + "\n" +
+      kUncommonDirtyClass;
+  profiles.emplace_back(ScratchFile());
+  EXPECT_TRUE(CreateProfile(dex2, profiles.back().GetFilename(), core_dex));
+
+  // Create a bunch of boot profiles.
+  std::string dex3 =
+      "S" + kHotMethod + "\n" +
+      "P" + kOtherMethod + "\n" +
+      kDirtyClass + "\n";
+  profiles.emplace_back(ScratchFile());
+  EXPECT_TRUE(CreateProfile(dex3, profiles.back().GetFilename(), core_dex));
+
+  // Generate the boot profile.
+  ScratchFile out_profile;
+  std::vector<std::string> args;
+  args.push_back(GetProfmanCmd());
+  args.push_back("--generate-boot-image-profile");
+  args.push_back("--boot-image-class-threshold=" + std::to_string(kDirtyThreshold));
+  args.push_back("--boot-image-clean-class-threshold=" + std::to_string(kCleanThreshold));
+  args.push_back("--reference-profile-file=" + out_profile.GetFilename());
+  args.push_back("--apk=" + core_dex);
+  args.push_back("--dex-location=" + core_dex);
+  for (const ScratchFile& profile : profiles) {
+    args.push_back("--profile-file=" + profile.GetFilename());
+  }
+  std::string error;
+  EXPECT_EQ(ExecAndReturnCode(args, &error), 0) << error;
+  ASSERT_EQ(0, out_profile.GetFile()->Flush());
+  ASSERT_TRUE(out_profile.GetFile()->ResetOffset());
+
+  // Verify the boot profile contents.
+  std::string output_file_contents;
+  EXPECT_TRUE(DumpClassesAndMethods(out_profile.GetFilename(), &output_file_contents));
+  // Common classes, should be in the classes of the profile.
+  EXPECT_NE(output_file_contents.find(kCleanClass + "\n"), std::string::npos)
+      << output_file_contents;
+  EXPECT_NE(output_file_contents.find(kDirtyClass + "\n"), std::string::npos)
+      << output_file_contents;
+  // Uncommon classes, should not fit preloaded class criteria and should not be in the profile.
+  EXPECT_EQ(output_file_contents.find(kUncommonCleanClass + "\n"), std::string::npos)
+      << output_file_contents;
+  EXPECT_EQ(output_file_contents.find(kUncommonDirtyClass + "\n"), std::string::npos)
+      << output_file_contents;
+  // Inferred class from a method common to all three profiles.
+  EXPECT_NE(output_file_contents.find("Ljava/lang/Comparable;\n"), std::string::npos)
+      << output_file_contents;
+  // Aggregated methods hotness information.
+  EXPECT_NE(output_file_contents.find("HSP" + kHotMethod), std::string::npos)
+      << output_file_contents;
+  EXPECT_NE(output_file_contents.find(kOtherMethod), std::string::npos)
+      << output_file_contents;
+  // Not inferred class, method is only in one profile.
+  EXPECT_EQ(output_file_contents.find("Ljava/util/HashMap;\n"), std::string::npos)
+      << output_file_contents;
+}
+
 TEST_F(ProfileAssistantTest, TestProfileCreationOneNotMatched) {
   // Class names put here need to be in sorted order.
   std::vector<std::string> class_names = {
diff --git a/profman/profman.cc b/profman/profman.cc
index f763b8e..14b0262 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 "boot_image_profile.h"
 #include "bytecode_utils.h"
 #include "dex_file.h"
 #include "jit/profile_compilation_info.h"
@@ -133,6 +134,15 @@
   UsageError("      search for dex files");
   UsageError("  --apk-=<filename>: an APK to search for dex files");
   UsageError("");
+  UsageError("  --generate-boot-image-profile: Generate a boot image profile based on input");
+  UsageError("      profiles. Requires passing in dex files to inspect properties of classes.");
+  UsageError("  --boot-image-class-threshold=<value>: specify minimum number of class occurrences");
+  UsageError("      to include a class in the boot image profile. Default is 10.");
+  UsageError("  --boot-image-clean-class-threshold=<value>: specify minimum number of clean class");
+  UsageError("      occurrences to include a class in the boot image profile. A clean class is a");
+  UsageError("      class that doesn't have any static fields or native methods and is likely to");
+  UsageError("      remain clean in the image. Default is 3.");
+  UsageError("");
 
   exit(EXIT_FAILURE);
 }
@@ -163,6 +173,7 @@
       reference_profile_file_fd_(kInvalidFd),
       dump_only_(false),
       dump_classes_and_methods_(false),
+      generate_boot_image_profile_(false),
       dump_output_to_fd_(kInvalidFd),
       test_profile_num_dex_(kDefaultTestProfileNumDex),
       test_profile_method_ratio_(kDefaultTestProfileMethodRatio),
@@ -202,6 +213,18 @@
         create_profile_from_file_ = option.substr(strlen("--create-profile-from=")).ToString();
       } else if (option.starts_with("--dump-output-to-fd=")) {
         ParseUintOption(option, "--dump-output-to-fd", &dump_output_to_fd_, Usage);
+      } else if (option == "--generate-boot-image-profile") {
+        generate_boot_image_profile_ = true;
+      } else if (option.starts_with("--boot-image-class-threshold=")) {
+        ParseUintOption(option,
+                        "--boot-image-class-threshold",
+                        &boot_image_options_.image_class_theshold,
+                        Usage);
+      } else if (option.starts_with("--boot-image-clean-class-threshold=")) {
+        ParseUintOption(option,
+                        "--boot-image-clean-class-threshold",
+                        &boot_image_options_.image_class_clean_theshold,
+                        Usage);
       } else if (option.starts_with("--profile-file=")) {
         profile_files_.push_back(option.substr(strlen("--profile-file=")).ToString());
       } else if (option.starts_with("--profile-file-fd=")) {
@@ -323,28 +346,33 @@
     }
   }
 
+  std::unique_ptr<const ProfileCompilationInfo> LoadProfile(const std::string& filename, int fd) {
+    if (!filename.empty()) {
+      fd = open(filename.c_str(), O_RDWR);
+      if (fd < 0) {
+        LOG(ERROR) << "Cannot open " << filename << strerror(errno);
+        return nullptr;
+      }
+    }
+    std::unique_ptr<ProfileCompilationInfo> info(new ProfileCompilationInfo);
+    if (!info->Load(fd)) {
+      LOG(ERROR) << "Cannot load profile info from fd=" << fd << "\n";
+      return nullptr;
+    }
+    return info;
+  }
+
   int DumpOneProfile(const std::string& banner,
                      const std::string& filename,
                      int fd,
                      const std::vector<std::unique_ptr<const DexFile>>* dex_files,
                      std::string* dump) {
-    if (!filename.empty()) {
-      fd = open(filename.c_str(), O_RDWR);
-      if (fd < 0) {
-        LOG(ERROR) << "Cannot open " << filename << strerror(errno);
-        return -1;
-      }
-    }
-    ProfileCompilationInfo info;
-    if (!info.Load(fd)) {
-      LOG(ERROR) << "Cannot load profile info from fd=" << fd << "\n";
+    std::unique_ptr<const ProfileCompilationInfo> info(LoadProfile(filename, fd));
+    if (info == nullptr) {
+      LOG(ERROR) << "Cannot load profile info from filename=" << filename << " fd=" << fd;
       return -1;
     }
-    std::string this_dump = banner + "\n" + info.DumpInfo(dex_files) + "\n";
-    *dump += this_dump;
-    if (close(fd) < 0) {
-      PLOG(WARNING) << "Failed to close descriptor";
-    }
+    *dump += banner + "\n" + info->DumpInfo(dex_files) + "\n";
     return 0;
   }
 
@@ -854,6 +882,19 @@
     return true;
   }
 
+  int OpenReferenceProfile() const {
+    int fd = reference_profile_file_fd_;
+    if (!FdIsValid(fd)) {
+      CHECK(!reference_profile_file_.empty());
+      fd = open(reference_profile_file_.c_str(), O_CREAT | O_TRUNC | O_WRONLY, 0644);
+      if (fd < 0) {
+        LOG(ERROR) << "Cannot open " << reference_profile_file_ << strerror(errno);
+        return kInvalidFd;
+      }
+    }
+    return fd;
+  }
+
   // Creates a profile from a human friendly textual representation.
   // The expected input format is:
   //   # Classes
@@ -881,14 +922,9 @@
     // for ZipArchive::OpenFromFd
     MemMap::Init();
     // Open the profile output file if needed.
-    int fd = reference_profile_file_fd_;
+    int fd = OpenReferenceProfile();
     if (!FdIsValid(fd)) {
-      CHECK(!reference_profile_file_.empty());
-      fd = open(reference_profile_file_.c_str(), O_CREAT | O_TRUNC | O_WRONLY, 0644);
-      if (fd < 0) {
-        LOG(ERROR) << "Cannot open " << reference_profile_file_ << strerror(errno);
         return -1;
-      }
     }
     // Read the user-specified list of classes and methods.
     std::unique_ptr<std::unordered_set<std::string>>
@@ -914,6 +950,57 @@
     return 0;
   }
 
+  bool ShouldCreateBootProfile() const {
+    return generate_boot_image_profile_;
+  }
+
+  int CreateBootProfile() {
+    // Initialize memmap since it's required to open dex files.
+    MemMap::Init();
+    // Open the profile output file.
+    const int reference_fd = OpenReferenceProfile();
+    if (!FdIsValid(reference_fd)) {
+      PLOG(ERROR) << "Error opening reference profile";
+      return -1;
+    }
+    // Open the dex files.
+    std::vector<std::unique_ptr<const DexFile>> dex_files;
+    OpenApkFilesFromLocations(&dex_files);
+    if (dex_files.empty()) {
+      PLOG(ERROR) << "Expected dex files for creating boot profile";
+      return -2;
+    }
+    // Open the input profiles.
+    std::vector<std::unique_ptr<const ProfileCompilationInfo>> profiles;
+    if (!profile_files_fd_.empty()) {
+      for (int profile_file_fd : profile_files_fd_) {
+        std::unique_ptr<const ProfileCompilationInfo> profile(LoadProfile("", profile_file_fd));
+        if (profile == nullptr) {
+          return -3;
+        }
+        profiles.emplace_back(std::move(profile));
+      }
+    }
+    if (!profile_files_.empty()) {
+      for (const std::string& profile_file : profile_files_) {
+        std::unique_ptr<const ProfileCompilationInfo> profile(LoadProfile(profile_file, kInvalidFd));
+        if (profile == nullptr) {
+          return -4;
+        }
+        profiles.emplace_back(std::move(profile));
+      }
+    }
+    ProfileCompilationInfo out_profile;
+    GenerateBootImageProfile(dex_files,
+                             profiles,
+                             boot_image_options_,
+                             VLOG_IS_ON(profiler),
+                             &out_profile);
+    out_profile.Save(reference_fd);
+    close(reference_fd);
+    return 0;
+  }
+
   bool ShouldCreateProfile() {
     return !create_profile_from_file_.empty();
   }
@@ -1001,7 +1088,9 @@
   int reference_profile_file_fd_;
   bool dump_only_;
   bool dump_classes_and_methods_;
+  bool generate_boot_image_profile_;
   int dump_output_to_fd_;
+  BootImageOptions boot_image_options_;
   std::string test_profile_;
   std::string create_profile_from_file_;
   uint16_t test_profile_num_dex_;
@@ -1030,6 +1119,10 @@
   if (profman.ShouldCreateProfile()) {
     return profman.CreateProfile();
   }
+
+  if (profman.ShouldCreateBootProfile()) {
+    return profman.CreateBootProfile();
+  }
   // Process profile information and assess if we need to do a profile guided compilation.
   // This operation involves I/O.
   return profman.ProcessProfiles();
diff --git a/runtime/jit/profile_compilation_info.cc b/runtime/jit/profile_compilation_info.cc
index 960030d..147173c 100644
--- a/runtime/jit/profile_compilation_info.cc
+++ b/runtime/jit/profile_compilation_info.cc
@@ -1148,7 +1148,8 @@
   return ret;
 }
 
-bool ProfileCompilationInfo::MergeWith(const ProfileCompilationInfo& other) {
+bool ProfileCompilationInfo::MergeWith(const ProfileCompilationInfo& other,
+                                       bool merge_classes) {
   // First verify that all checksums match. This will avoid adding garbage to
   // the current profile info.
   // Note that the number of elements should be very small, so this should not
@@ -1194,8 +1195,10 @@
     DCHECK(dex_data != nullptr);
 
     // Merge the classes.
-    dex_data->class_set.insert(other_dex_data->class_set.begin(),
-                               other_dex_data->class_set.end());
+    if (merge_classes) {
+      dex_data->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) {
@@ -1239,6 +1242,18 @@
       : MethodHotness();
 }
 
+bool ProfileCompilationInfo::AddMethodHotness(const MethodReference& method_ref,
+                                              const MethodHotness& hotness) {
+  DexFileData* dex_data = GetOrAddDexFileData(method_ref.dex_file);
+  if (dex_data != nullptr) {
+    // TODO: Add inline caches.
+    dex_data->AddMethod(static_cast<MethodHotness::Flag>(hotness.GetFlags()),
+                        method_ref.dex_method_index);
+    return true;
+  }
+  return false;
+}
+
 ProfileCompilationInfo::MethodHotness ProfileCompilationInfo::GetMethodHotness(
     const std::string& dex_location,
     uint32_t dex_checksum,
diff --git a/runtime/jit/profile_compilation_info.h b/runtime/jit/profile_compilation_info.h
index f1f2428..079ce8d 100644
--- a/runtime/jit/profile_compilation_info.h
+++ b/runtime/jit/profile_compilation_info.h
@@ -285,6 +285,9 @@
     return true;
   }
 
+  // Add hotness flags for a simple method.
+  bool AddMethodHotness(const MethodReference& method_ref, const MethodHotness& hotness);
+
   // Load profile information from the given file descriptor.
   // If the current profile is non-empty the load will fail.
   bool Load(int fd);
@@ -295,8 +298,10 @@
   // the file and returns true.
   bool Load(const std::string& filename, bool clear_if_invalid);
 
-  // Merge the data from another ProfileCompilationInfo into the current object.
-  bool MergeWith(const ProfileCompilationInfo& info);
+  // Merge the data from another ProfileCompilationInfo into the current object. Only merges
+  // classes if merge_classes is true. This is used for creating the boot profile since
+  // we don't want all of the classes to be image classes.
+  bool MergeWith(const ProfileCompilationInfo& info, bool merge_classes = true);
 
   // Save the profile data to the given file descriptor.
   bool Save(int fd);
diff --git a/runtime/type_reference.h b/runtime/type_reference.h
index b7e964b..c44019d 100644
--- a/runtime/type_reference.h
+++ b/runtime/type_reference.h
@@ -37,6 +37,15 @@
   dex::TypeIndex type_index;
 };
 
+struct TypeReferenceComparator {
+  bool operator()(TypeReference mr1, TypeReference mr2) const {
+    if (mr1.dex_file != mr2.dex_file) {
+      return mr1.dex_file < mr2.dex_file;
+    }
+    return mr1.type_index < mr2.type_index;
+  }
+};
+
 // Compare the actual referenced type names. Used for type reference deduplication.
 struct TypeReferenceValueComparator {
   bool operator()(TypeReference tr1, TypeReference tr2) const {