Add a new boot image profile format

The new format adds additional method flags which will capture the
following states:
  - am start / post startup
  - boot / post boot
  - foreground / background
  - startup classification bin (see code comments)

The extension is currently only available for boot image profile because
it expands the profile memory and disk footprint. A stress test shows that
the new format requires:
  - profile content: 10 dex files, 2^16 methods, all in, no inline caches
    (that's way more than we should see in practice)
  - 1.2MB extra ram (3.9MB compared to 2.6MB)
  - 1.1MB extra disk space (1.5MB compared o 379K)

The ram scales down with less data (e.g. for 5 dexes the RAM is only 6KB
more). The disk space is drastically reduced if the zip algorithm is able
to compress the data efficiently (e.g in the idea case, when all bits are
of the same value, it only adds 5K of extra space).

The threshold for size warnings/errors were adjusted based on the new
values.

Bug: 139884006
Test: m test-art-host-gtest
Change-Id: I1543309d769c7d263a3e5f06cd8a2cf476999253
diff --git a/libprofile/profile/profile_compilation_info.cc b/libprofile/profile/profile_compilation_info.cc
index 3f18d0b..097c30c 100644
--- a/libprofile/profile/profile_compilation_info.cc
+++ b/libprofile/profile/profile_compilation_info.cc
@@ -85,6 +85,12 @@
 static_assert(ProfileCompilationInfo::kIndividualInlineCacheSize < kIsMissingTypesEncoding,
               "InlineCache::kIndividualInlineCacheSize is larger than expected");
 
+static constexpr uint32_t kSizeWarningThresholdBytes = 500000U;
+static constexpr uint32_t kSizeErrorThresholdBytes = 1500000U;
+
+static constexpr uint32_t kSizeWarningThresholdBootBytes = 3000000U;
+static constexpr uint32_t kSizeErrorThresholdBootBytes = 6000000U;
+
 static bool ChecksumMatch(uint32_t dex_file_checksum, uint32_t checksum) {
   return kDebugIgnoreChecksum || dex_file_checksum == checksum;
 }
@@ -154,6 +160,10 @@
     DCHECK(last_sep_index < dex_location.size());
     return dex_location.substr(last_sep_index + 1);
   }
+
+  // TODO(calin): append the RuntimeInstructionSet to the key so we can capture arch-dependent data.
+  // This requires a bit of work as the ProfileSaver and various tests rely on this being a
+  // static public method.
 }
 
 bool ProfileCompilationInfo::AddMethodIndex(MethodHotness::Flag flags, const MethodReference& ref) {
@@ -395,10 +405,12 @@
   }
   // Allow large profiles for non target builds for the case where we are merging many profiles
   // to generate a boot image profile.
-  if (kIsTargetBuild && required_capacity > kProfileSizeErrorThresholdInBytes) {
+  VLOG(profiler) << "Required capacity: " << required_capacity << " bytes.";
+  if (required_capacity > GetSizeErrorThresholdBytes()) {
     LOG(ERROR) << "Profile data size exceeds "
-               << std::to_string(kProfileSizeErrorThresholdInBytes)
-               << " bytes. Profile will not be written to disk.";
+               << GetSizeErrorThresholdBytes()
+               << " bytes. Profile will not be written to disk."
+               << " It requires " << required_capacity << " bytes.";
     return false;
   }
   AddUintToBuffer(&buffer, required_capacity);
@@ -472,9 +484,10 @@
                                                                required_capacity,
                                                                &output_size);
 
-  if (output_size > kProfileSizeWarningThresholdInBytes) {
+  if (output_size > GetSizeWarningThresholdBytes()) {
     LOG(WARNING) << "Profile data size exceeds "
-                 << std::to_string(kProfileSizeWarningThresholdInBytes);
+        << GetSizeWarningThresholdBytes()
+        << " It has " << output_size << " bytes";
   }
 
   buffer.clear();
@@ -605,7 +618,8 @@
         profile_key,
         checksum,
         profile_index,
-        num_method_ids);
+        num_method_ids,
+        IsForBootImage());
     info_.push_back(dex_file_data);
   }
   DexFileData* result = info_[profile_index];
@@ -1325,16 +1339,16 @@
   }
   // Allow large profiles for non target builds for the case where we are merging many profiles
   // to generate a boot image profile.
-  if (kIsTargetBuild && uncompressed_data_size > kProfileSizeErrorThresholdInBytes) {
+  if (uncompressed_data_size > GetSizeErrorThresholdBytes()) {
     LOG(ERROR) << "Profile data size exceeds "
-               << std::to_string(kProfileSizeErrorThresholdInBytes)
-               << " bytes";
+               << GetSizeErrorThresholdBytes()
+               << " bytes. It has " << uncompressed_data_size << " bytes.";
     return kProfileLoadBadData;
   }
-  if (uncompressed_data_size > kProfileSizeWarningThresholdInBytes) {
+  if (uncompressed_data_size > GetSizeWarningThresholdBytes()) {
     LOG(WARNING) << "Profile data size exceeds "
-                 << std::to_string(kProfileSizeWarningThresholdInBytes)
-                 << " bytes";
+                 << GetSizeWarningThresholdBytes()
+                 << " bytes. It has " << uncompressed_data_size << " bytes.";
   }
 
   std::unique_ptr<uint8_t[]> compressed_data(new uint8_t[compressed_data_size]);
@@ -1385,7 +1399,8 @@
       size_t profile_line_size =
            profile_line_headers[k].class_set_size * sizeof(uint16_t) +
            profile_line_headers[k].method_region_size_bytes +
-           DexFileData::ComputeBitmapStorage(profile_line_headers[k].num_method_ids);
+           DexFileData::ComputeBitmapStorage(IsForBootImage(),
+              profile_line_headers[k].num_method_ids);
       uncompressed_data.Advance(profile_line_size);
     } else {
       // Now read the actual profile line.
@@ -2016,22 +2031,30 @@
 void ProfileCompilationInfo::DexFileData::SetMethodHotness(size_t index,
                                                            MethodHotness::Flag flags) {
   DCHECK_LT(index, num_method_ids);
-  if ((flags & MethodHotness::kFlagStartup) != 0) {
-    method_bitmap.StoreBit(MethodBitIndex(/*startup=*/ true, index), /*value=*/ true);
-  }
-  if ((flags & MethodHotness::kFlagPostStartup) != 0) {
-    method_bitmap.StoreBit(MethodBitIndex(/*startup=*/ false, index), /*value=*/ true);
+  uint32_t lastFlag = is_for_boot_image ? MethodHotness::kFlagLastBoot : MethodHotness::kFlagLastRegular;
+  for (uint32_t flag = MethodHotness::kFlagFirst; flag <= lastFlag; flag = flag << 1) {
+    if (flag == MethodHotness::kFlagHot) {
+      // There's no bit for hotness in the bitmap.
+      // We store the hotness by recording the method in the method list.
+      continue;
+    }
+    if ((flags & flag) != 0) {
+      method_bitmap.StoreBit(MethodFlagBitmapIndex(static_cast<MethodHotness::Flag>(flag), index), /*value=*/ true);
+    }
   }
 }
 
 ProfileCompilationInfo::MethodHotness ProfileCompilationInfo::DexFileData::GetHotnessInfo(
     uint32_t dex_method_index) const {
   MethodHotness ret;
-  if (method_bitmap.LoadBit(MethodBitIndex(/*startup=*/ true, dex_method_index))) {
-    ret.AddFlag(MethodHotness::kFlagStartup);
-  }
-  if (method_bitmap.LoadBit(MethodBitIndex(/*startup=*/ false, dex_method_index))) {
-    ret.AddFlag(MethodHotness::kFlagPostStartup);
+  uint32_t lastFlag = is_for_boot_image ? MethodHotness::kFlagLastBoot : MethodHotness::kFlagLastRegular;
+  for (uint32_t flag = MethodHotness::kFlagFirst; flag <= lastFlag; flag = flag << 1) {
+    if (flag == MethodHotness::kFlagHot) {
+      continue;
+    }
+    if (method_bitmap.LoadBit(MethodFlagBitmapIndex(static_cast<MethodHotness::Flag>(flag), dex_method_index))) {
+      ret.AddFlag(static_cast<MethodHotness::Flag>(flag));
+    }
   }
   auto it = method_map.find(dex_method_index);
   if (it != method_map.end()) {
@@ -2041,6 +2064,41 @@
   return ret;
 }
 
+// To simplify the implementation we use the MethodHotness flag values as indexes into the internal
+// bitmap representation. As such, they should never change unless the profile version is updated
+// and the implementation changed accordingly.
+static_assert(ProfileCompilationInfo::MethodHotness::kFlagFirst == 1 << 0);
+static_assert(ProfileCompilationInfo::MethodHotness::kFlagHot == 1 << 0);
+static_assert(ProfileCompilationInfo::MethodHotness::kFlagStartup == 1 << 1);
+static_assert(ProfileCompilationInfo::MethodHotness::kFlagPostStartup == 1 << 2);
+static_assert(ProfileCompilationInfo::MethodHotness::kFlagLastRegular == 1 << 2);
+static_assert(ProfileCompilationInfo::MethodHotness::kFlagAmStartup == 1 << 3);
+static_assert(ProfileCompilationInfo::MethodHotness::kFlagAmPostStartup == 1 << 4);
+static_assert(ProfileCompilationInfo::MethodHotness::kFlagBoot == 1 << 5);
+static_assert(ProfileCompilationInfo::MethodHotness::kFlagPostBoot == 1 << 6);
+static_assert(ProfileCompilationInfo::MethodHotness::kFlagForeground == 1 << 7);
+static_assert(ProfileCompilationInfo::MethodHotness::kFlagBackground == 1 << 8);
+static_assert(ProfileCompilationInfo::MethodHotness::kFlagStartupBin == 1 << 9);
+static_assert(ProfileCompilationInfo::MethodHotness::kFlagStartupMaxBin == 1 << 16);
+static_assert(ProfileCompilationInfo::MethodHotness::kFlagLastBoot == 1 << 16);
+
+size_t ProfileCompilationInfo::DexFileData::MethodFlagBitmapIndex(MethodHotness::Flag flag, size_t method_index) const {
+  DCHECK_LT(method_index, num_method_ids);
+  // The format is [startup bitmap][post startup bitmap][AmStartup][...]
+  // This compresses better than ([startup bit][post startup bit])*
+  return method_index + FlagBitmapIndex(flag) * num_method_ids;
+}
+
+size_t ProfileCompilationInfo::DexFileData::FlagBitmapIndex(MethodHotness::Flag flag) {
+  DCHECK(flag != MethodHotness::kFlagHot);
+  DCHECK(IsPowerOfTwo(static_cast<uint32_t>(flag)));
+  // We arrange the method flags in order, starting with the startup flag.
+  // The kFlagHot is not encoded in the bitmap and thus not expected as an
+  // argument here. Since all the other flags start at 1 we have to subtract
+  // one for the power of 2.
+  return WhichPowerOf2(static_cast<uint32_t>(flag)) - 1;
+}
+
 ProfileCompilationInfo::DexPcData*
 ProfileCompilationInfo::FindOrAddDexPc(InlineCacheMap* inline_cache, uint32_t dex_pc) {
   return &(inline_cache->FindOrAdd(dex_pc, DexPcData(&allocator_))->second);
@@ -2148,4 +2206,13 @@
 bool ProfileCompilationInfo::DexFileData::ContainsClass(const dex::TypeIndex type_index) const {
   return class_set.find(type_index) != class_set.end();
 }
+
+size_t ProfileCompilationInfo::GetSizeWarningThresholdBytes() const {
+  return IsForBootImage() ?  kSizeWarningThresholdBootBytes : kSizeWarningThresholdBytes;
+}
+
+size_t ProfileCompilationInfo::GetSizeErrorThresholdBytes() const {
+  return IsForBootImage() ?  kSizeErrorThresholdBootBytes : kSizeErrorThresholdBytes;
+}
+
 }  // namespace art
diff --git a/libprofile/profile/profile_compilation_info.h b/libprofile/profile/profile_compilation_info.h
index bfc8e0a..a6013e1 100644
--- a/libprofile/profile/profile_compilation_info.h
+++ b/libprofile/profile/profile_compilation_info.h
@@ -181,9 +181,26 @@
   class MethodHotness {
    public:
     enum Flag {
-      kFlagHot = 0x1,
-      kFlagStartup = 0x2,
-      kFlagPostStartup = 0x4,
+      kFlagFirst = 1 << 0,
+      kFlagHot = 1 << 0,
+      kFlagStartup = 1 << 1,
+      kFlagPostStartup = 1 << 2,
+      kFlagLastRegular = 1 << 2,
+      kFlagAmStartup = 1 << 3,
+      kFlagAmPostStartup = 1 << 4,
+      kFlagBoot = 1 << 5,
+      kFlagPostBoot = 1 << 6,
+      kFlagForeground = 1 << 7,
+      kFlagBackground = 1 << 8,
+      // The startup bins captured the relative order of when a method become hot. There are 8
+      // total bins supported and each hot method will have at least one bit set. If the profile was
+      // merged multiple times more than one bit may be set as a given method may become hot at
+      // various times during subsequent executions.
+      // The granularity of the bins is unspecified (i.e. the runtime is free to change the
+      // values it uses - this may be 100ms, 200ms etc...).
+      kFlagStartupBin = 1 << 9,
+      kFlagStartupMaxBin = 1 << 16,
+      kFlagLastBoot = 1 << 16,
     };
 
     bool IsHot() const {
@@ -202,17 +219,21 @@
       flags_ |= flag;
     }
 
-    uint8_t GetFlags() const {
+    uint32_t GetFlags() const {
       return flags_;
     }
 
+    bool HasFlagSet(MethodHotness::Flag flag) {
+      return (flags_ & flag ) != 0;
+    }
+
     bool IsInProfile() const {
       return flags_ != 0;
     }
 
    private:
     const InlineCacheMap* inline_cache_map_ = nullptr;
-    uint8_t flags_ = 0;
+    uint32_t flags_ = 0;
 
     const InlineCacheMap* GetInlineCacheMap() const {
       return inline_cache_map_;
@@ -474,9 +495,6 @@
     kProfileLoadSuccess
   };
 
-  const uint32_t kProfileSizeWarningThresholdInBytes = 500000U;
-  const uint32_t kProfileSizeErrorThresholdInBytes = 1000000U;
-
   // Internal representation of the profile information belonging to a dex file.
   // Note that we could do without profile_key (the key used to encode the dex
   // file in the profile) and profile_index (the index of the dex file in the
@@ -488,7 +506,8 @@
                 const std::string& key,
                 uint32_t location_checksum,
                 uint16_t index,
-                uint32_t num_methods)
+                uint32_t num_methods,
+                bool for_boot_image)
         : allocator_(allocator),
           profile_key(key),
           profile_index(index),
@@ -496,20 +515,28 @@
           method_map(std::less<uint16_t>(), allocator->Adapter(kArenaAllocProfile)),
           class_set(std::less<dex::TypeIndex>(), allocator->Adapter(kArenaAllocProfile)),
           num_method_ids(num_methods),
-          bitmap_storage(allocator->Adapter(kArenaAllocProfile)) {
-      bitmap_storage.resize(ComputeBitmapStorage(num_method_ids));
+          bitmap_storage(allocator->Adapter(kArenaAllocProfile)),
+          is_for_boot_image(for_boot_image) {
+      bitmap_storage.resize(ComputeBitmapStorage(is_for_boot_image, num_method_ids));
       if (!bitmap_storage.empty()) {
         method_bitmap =
             BitMemoryRegion(MemoryRegion(
-                &bitmap_storage[0], bitmap_storage.size()), 0, ComputeBitmapBits(num_method_ids));
+                &bitmap_storage[0],
+                bitmap_storage.size()),
+                0,
+                ComputeBitmapBits(is_for_boot_image, num_method_ids));
       }
     }
 
-    static size_t ComputeBitmapBits(uint32_t num_method_ids) {
-      return num_method_ids * kBitmapIndexCount;
+    static size_t ComputeBitmapBits(bool is_for_boot_image, uint32_t num_method_ids) {
+      size_t flag_bitmap_index = FlagBitmapIndex(is_for_boot_image
+          ? MethodHotness::kFlagLastBoot
+          : MethodHotness::kFlagLastRegular);
+      return num_method_ids * (flag_bitmap_index + 1);
     }
-    static size_t ComputeBitmapStorage(uint32_t num_method_ids) {
-      return RoundUp(ComputeBitmapBits(num_method_ids), kBitsPerByte) / kBitsPerByte;
+    static size_t ComputeBitmapStorage(bool is_for_boot_image, uint32_t num_method_ids) {
+      return RoundUp(ComputeBitmapBits(is_for_boot_image, num_method_ids), kBitsPerByte) /
+          kBitsPerByte;
     }
 
     bool operator==(const DexFileData& other) const {
@@ -555,23 +582,11 @@
     uint32_t num_method_ids;
     ArenaVector<uint8_t> bitmap_storage;
     BitMemoryRegion method_bitmap;
+    bool is_for_boot_image;
 
    private:
-    enum BitmapIndex {
-      kBitmapIndexStartup,
-      kBitmapIndexPostStartup,
-      kBitmapIndexCount,
-    };
-
-    size_t MethodBitIndex(bool startup, size_t index) const {
-      DCHECK_LT(index, num_method_ids);
-      // The format is [startup bitmap][post startup bitmap]
-      // This compresses better than ([startup bit][post statup bit])*
-
-      return index + (startup
-          ? kBitmapIndexStartup * num_method_ids
-          : kBitmapIndexPostStartup * num_method_ids);
-    }
+    size_t MethodFlagBitmapIndex(MethodHotness::Flag flag, size_t method_index) const;
+    static size_t FlagBitmapIndex(MethodHotness::Flag flag);
   };
 
   // Return the profile data for the given profile key or null if the dex location
@@ -819,6 +834,11 @@
   // Initializes the profile version to the desired one.
   void InitProfileVersionInternal(const uint8_t version[]);
 
+  // Returns the threshold size (in bytes) which will triggers save/load warnings.
+  size_t GetSizeWarningThresholdBytes() const;
+  // Returns the threshold size (in bytes) which will cause save/load failures.
+  size_t GetSizeErrorThresholdBytes() const;
+
   friend class ProfileCompilationInfoTest;
   friend class CompilerDriverProfileTest;
   friend class ProfileAssistantTest;
diff --git a/libprofile/profile/profile_compilation_info_test.cc b/libprofile/profile/profile_compilation_info_test.cc
index 3881cab..bae1903 100644
--- a/libprofile/profile/profile_compilation_info_test.cc
+++ b/libprofile/profile/profile_compilation_info_test.cc
@@ -32,6 +32,10 @@
 using Hotness = ProfileCompilationInfo::MethodHotness;
 
 static constexpr size_t kMaxMethodIds = 65535;
+static uint32_t kMaxHotnessFlagBootIndex =
+    WhichPowerOf2(static_cast<uint32_t>(Hotness::kFlagLastBoot));
+static uint32_t kMaxHotnessFlagRegularIndex =
+    WhichPowerOf2(static_cast<uint32_t>(Hotness::kFlagLastRegular));
 
 class ProfileCompilationInfoTest : public CommonArtTest {
  public:
@@ -187,6 +191,56 @@
     return info.IsEmpty();
   }
 
+  void SizeStressTest(bool random) {
+    ProfileCompilationInfo boot_profile(/*for_boot_image*/ true);
+    ProfileCompilationInfo reg_profile(/*for_boot_image*/ false);
+
+    static constexpr size_t kNumDexFiles = 5;
+    static constexpr size_t kNumMethods = 1 << 16;
+    static constexpr size_t kChecksum = 1234;
+    static const std::string kDex = "dex";
+
+    std::srand(0);
+    // Set a few flags on a 2 different methods in each of the profile.
+    for (uint32_t dex_index = 0; dex_index < kNumDexFiles; dex_index++) {
+      for (uint32_t method_idx = 0; method_idx < kNumMethods; method_idx++) {
+        for (uint32_t flag_index = 0; flag_index <= kMaxHotnessFlagBootIndex; flag_index++) {
+          if (!random || rand() % 2 == 0) {
+            ASSERT_TRUE(boot_profile.AddMethodIndex(
+                static_cast<Hotness::Flag>(1 << flag_index),
+                kDex + std::to_string(dex_index),
+                kChecksum + dex_index,
+                method_idx,
+                kNumMethods));
+          }
+        }
+        for (uint32_t flag_index = 0; flag_index <= kMaxHotnessFlagRegularIndex; flag_index++) {
+          if (!random || rand() % 2 == 0) {
+            ASSERT_TRUE(reg_profile.AddMethodIndex(
+                static_cast<Hotness::Flag>(1 << flag_index),
+                kDex + std::to_string(dex_index),
+                kChecksum + dex_index,
+                method_idx,
+                kNumMethods));
+          }
+        }
+      }
+    }
+
+    ScratchFile boot_file;
+    ScratchFile reg_file;
+
+    ASSERT_TRUE(boot_profile.Save(GetFd(boot_file)));
+    ASSERT_TRUE(reg_profile.Save(GetFd(reg_file)));
+    ASSERT_TRUE(boot_file.GetFile()->ResetOffset());
+    ASSERT_TRUE(reg_file.GetFile()->ResetOffset());
+
+    ProfileCompilationInfo loaded_boot;
+    ProfileCompilationInfo loaded_reg;
+    ASSERT_TRUE(loaded_boot.Load(GetFd(boot_file)));
+    ASSERT_TRUE(loaded_reg.Load(GetFd(reg_file)));
+  }
+
   // Cannot sizeof the actual arrays so hard code the values here.
   // They should not change anyway.
   static constexpr int kProfileMagicSize = 4;
@@ -1166,4 +1220,135 @@
   ASSERT_FALSE(info.Equals(info1));
 }
 
+TEST_F(ProfileCompilationInfoTest, AllMethodFlags) {
+  ProfileCompilationInfo info(/*for_boot_image*/ true);
+
+  static constexpr size_t kNumMethods = 1000;
+  static constexpr size_t kChecksum1 = 1234;
+  static const std::string kDex1 = "dex1";
+
+  for (uint32_t index = 0; index <= kMaxHotnessFlagBootIndex; index++) {
+    info.AddMethodIndex(static_cast<Hotness::Flag>(1 << index), kDex1, kChecksum1, index, kNumMethods);
+  }
+
+  auto run_test = [](const ProfileCompilationInfo& info) {
+    for (uint32_t index = 0; index <= kMaxHotnessFlagBootIndex; index++) {
+      EXPECT_TRUE(info.GetMethodHotness(kDex1, kChecksum1, index).IsInProfile());
+      EXPECT_TRUE(info.GetMethodHotness(kDex1, kChecksum1, index)
+          .HasFlagSet(static_cast<Hotness::Flag>(1 << index))) << index << " "
+            << info.GetMethodHotness(kDex1, kChecksum1, index).GetFlags();
+    }
+  };
+  run_test(info);
+
+  // Save the profile.
+  ScratchFile profile;
+  ASSERT_TRUE(info.Save(GetFd(profile)));
+  ASSERT_EQ(0, profile.GetFile()->Flush());
+  ASSERT_TRUE(profile.GetFile()->ResetOffset());
+
+  // Load the profile and make sure we can read the data and it matches what we expect.
+  ProfileCompilationInfo loaded_info;
+  ASSERT_TRUE(loaded_info.Load(GetFd(profile)));
+  run_test(loaded_info);
+}
+
+TEST_F(ProfileCompilationInfoTest, AllMethodFlagsOnOneMethod) {
+  ProfileCompilationInfo info(/*for_boot_image*/ true);
+
+  static constexpr size_t kNumMethods = 1000;
+  static constexpr size_t kChecksum1 = 1234;
+  static const std::string kDex1 = "dex1";
+
+  // Set all flags on a single method.
+  for (uint32_t index = 0; index <= kMaxHotnessFlagBootIndex; index++) {
+    info.AddMethodIndex(static_cast<Hotness::Flag>(1 << index), kDex1, kChecksum1, 0, kNumMethods);
+  }
+
+  auto run_test = [](const ProfileCompilationInfo& info) {
+    for (uint32_t index = 0; index <= kMaxHotnessFlagBootIndex; index++) {
+      EXPECT_TRUE(info.GetMethodHotness(kDex1, kChecksum1, 0).IsInProfile());
+      EXPECT_TRUE(info.GetMethodHotness(kDex1, kChecksum1, 0)
+          .HasFlagSet(static_cast<Hotness::Flag>(1 << 0)));
+    }
+  };
+  run_test(info);
+
+  // Save the profile.
+  ScratchFile profile;
+  ASSERT_TRUE(info.Save(GetFd(profile)));
+  ASSERT_EQ(0, profile.GetFile()->Flush());
+  ASSERT_TRUE(profile.GetFile()->ResetOffset());
+
+  // Load the profile and make sure we can read the data and it matches what we expect.
+  ProfileCompilationInfo loaded_info;
+  ASSERT_TRUE(loaded_info.Load(GetFd(profile)));
+  run_test(loaded_info);
+}
+
+
+TEST_F(ProfileCompilationInfoTest, MethodFlagsMerge) {
+  ProfileCompilationInfo info1(/*for_boot_image*/ true);
+  ProfileCompilationInfo info2(/*for_boot_image*/ true);
+
+  static constexpr size_t kNumMethods = 1000;
+  static constexpr size_t kChecksum1 = 1234;
+  static const std::string kDex1 = "dex1";
+
+  // Set a few flags on a 2 different methods in each of the profile.
+  for (uint32_t index = 0; index <= kMaxHotnessFlagBootIndex / 4; index++) {
+    info1.AddMethodIndex(static_cast<Hotness::Flag>(1 << index), kDex1, kChecksum1, 0, kNumMethods);
+    info2.AddMethodIndex(static_cast<Hotness::Flag>(1 << index), kDex1, kChecksum1, 1, kNumMethods);
+  }
+
+  // Set a few more flags on the same methods but reverse the profiles.
+  for (uint32_t index = kMaxHotnessFlagBootIndex / 4 + 1; index <= kMaxHotnessFlagBootIndex / 2; index++) {
+    info2.AddMethodIndex(static_cast<Hotness::Flag>(1 << index), kDex1, kChecksum1, 0, kNumMethods);
+    info1.AddMethodIndex(static_cast<Hotness::Flag>(1 << index), kDex1, kChecksum1, 1, kNumMethods);
+  }
+
+  ASSERT_TRUE(info1.MergeWith(info2));
+
+  auto run_test = [](const ProfileCompilationInfo& info) {
+    // Assert that the flags were merged correctly for both methods.
+    for (uint32_t index = 0; index <= kMaxHotnessFlagBootIndex / 2; index++) {
+      EXPECT_TRUE(info.GetMethodHotness(kDex1, kChecksum1, 0).IsInProfile());
+      EXPECT_TRUE(info.GetMethodHotness(kDex1, kChecksum1, 0)
+          .HasFlagSet(static_cast<Hotness::Flag>(1 << 0)));
+
+      EXPECT_TRUE(info.GetMethodHotness(kDex1, kChecksum1, 1).IsInProfile());
+      EXPECT_TRUE(info.GetMethodHotness(kDex1, kChecksum1, 1)
+          .HasFlagSet(static_cast<Hotness::Flag>(1 << index)));
+    }
+
+    // Assert that no extra flags were added.
+    for (uint32_t index = kMaxHotnessFlagBootIndex / 2 + 1; index <= kMaxHotnessFlagBootIndex; index++) {
+      EXPECT_FALSE(info.GetMethodHotness(kDex1, kChecksum1, 0)
+          .HasFlagSet(static_cast<Hotness::Flag>(1 << index)));
+      EXPECT_FALSE(info.GetMethodHotness(kDex1, kChecksum1, 1)
+          .HasFlagSet(static_cast<Hotness::Flag>(1 << index)));
+    }
+  };
+
+  run_test(info1);
+
+  // Save the profile.
+  ScratchFile profile;
+  ASSERT_TRUE(info1.Save(GetFd(profile)));
+  ASSERT_EQ(0, profile.GetFile()->Flush());
+  ASSERT_TRUE(profile.GetFile()->ResetOffset());
+
+  // Load the profile and make sure we can read the data and it matches what we expect.
+  ProfileCompilationInfo loaded_info;
+  ASSERT_TRUE(loaded_info.Load(GetFd(profile)));
+  run_test(loaded_info);
+}
+
+TEST_F(ProfileCompilationInfoTest, SizeStressTestAllIn) {
+  SizeStressTest(/*random=*/ false);
+}
+
+TEST_F(ProfileCompilationInfoTest, SizeStressTestAllInRandom) {
+  SizeStressTest(/*random=*/ true);
+}
 }  // namespace art