diff options
| -rw-r--r-- | profman/Android.bp | 1 | ||||
| -rw-r--r-- | profman/boot_image_profile.cc | 138 | ||||
| -rw-r--r-- | profman/boot_image_profile.h | 55 | ||||
| -rw-r--r-- | profman/profile_assistant_test.cc | 94 | ||||
| -rw-r--r-- | profman/profman.cc | 131 | ||||
| -rw-r--r-- | runtime/jit/profile_compilation_info.cc | 21 | ||||
| -rw-r--r-- | runtime/jit/profile_compilation_info.h | 9 | ||||
| -rw-r--r-- | runtime/type_reference.h | 9 |
8 files changed, 434 insertions, 24 deletions
diff --git a/profman/Android.bp b/profman/Android.bp index a327ef2c16..2a45c462b0 100644 --- a/profman/Android.bp +++ b/profman/Android.bp @@ -19,6 +19,7 @@ cc_defaults { 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 0000000000..21de0831b8 --- /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 0000000000..d02e408cd5 --- /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 c6b06afb7b..75f8ec9e27 100644 --- a/profman/profile_assistant_test.cc +++ b/profman/profile_assistant_test.cc @@ -621,6 +621,100 @@ TEST_F(ProfileAssistantTest, TestProfileCreationGenerateMethods) { 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 f763b8ea05..14b026277f 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 @@ NO_RETURN static void Usage(const char *fmt, ...) { 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 @@ class ProfMan FINAL { 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 @@ class ProfMan FINAL { 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 @@ class ProfMan FINAL { } } - 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) { + 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 -1; + return nullptr; } } - ProfileCompilationInfo info; - if (!info.Load(fd)) { + std::unique_ptr<ProfileCompilationInfo> info(new ProfileCompilationInfo); + if (!info->Load(fd)) { LOG(ERROR) << "Cannot load profile info from fd=" << fd << "\n"; - return -1; + return nullptr; } - std::string this_dump = banner + "\n" + info.DumpInfo(dex_files) + "\n"; - *dump += this_dump; - if (close(fd) < 0) { - PLOG(WARNING) << "Failed to close descriptor"; + 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) { + 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; } + *dump += banner + "\n" + info->DumpInfo(dex_files) + "\n"; return 0; } @@ -854,6 +882,19 @@ class ProfMan FINAL { 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 @@ class ProfMan FINAL { // 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 @@ class ProfMan FINAL { 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 @@ class ProfMan FINAL { 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 @@ static int profman(int argc, char** argv) { 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 960030d577..147173c5a3 100644 --- a/runtime/jit/profile_compilation_info.cc +++ b/runtime/jit/profile_compilation_info.cc @@ -1148,7 +1148,8 @@ int ProfileCompilationInfo::InflateBuffer(const uint8_t* in_buffer, 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 @@ bool ProfileCompilationInfo::MergeWith(const ProfileCompilationInfo& other) { 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 @@ ProfileCompilationInfo::MethodHotness ProfileCompilationInfo::GetMethodHotness( : 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 f1f2428b18..079ce8d117 100644 --- a/runtime/jit/profile_compilation_info.h +++ b/runtime/jit/profile_compilation_info.h @@ -285,6 +285,9 @@ class ProfileCompilationInfo { 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 @@ class ProfileCompilationInfo { // 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 b7e964b3ad..c44019dde3 100644 --- a/runtime/type_reference.h +++ b/runtime/type_reference.h @@ -37,6 +37,15 @@ struct TypeReference { 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 { |