static_assert(sizeof(ProfileCompilationInfo::kProfileVersion) == 4, "Invalid profile version size");
static_assert(sizeof(ProfileCompilationInfo::kProfileVersionForBootImage) == 4, "Invalid profile version size");
// The name of the profile entry in the dex metadata file. // DO NOT CHANGE THIS! (it's similar to classes.dex in the apk files). constchar ProfileCompilationInfo::kDexMetadataProfileEntry[] = "primary.prof";
// A synthetic annotations that can be used to denote that no annotation should // be associated with the profile samples. We use the empty string for the package name // because that's an invalid package name and should never occur in practice. const ProfileCompilationInfo::ProfileSampleAnnotation
ProfileCompilationInfo::ProfileSampleAnnotation::kNone =
ProfileCompilationInfo::ProfileSampleAnnotation("");
// Note: This used to be PATH_MAX (usually 4096) but that seems excessive // and we do not want to rely on that external constant anyway. static constexpr uint16_t kMaxDexFileKeyLength = 1024;
// Extra descriptors are serialized with a `uint16_t` prefix. This defines the length limit. static constexpr size_t kMaxExtraDescriptorLength = std::numeric_limits<uint16_t>::max();
// According to dex file specification, there can be more than 2^16 valid method indexes // but bytecode uses only 16 bits, so higher method indexes are not very useful (though // such methods could be reached through virtual or interface dispatch). Consequently, // dex files with more than 2^16 method indexes are not really used and the profile file // format does not support higher method indexes. static constexpr uint32_t kMaxSupportedMethodIndex = 0xffffu;
// Debug flag to ignore checksums when testing if a method or a class is present in the profile. // Used to facilitate testing profile guided compilation across a large number of apps // using the same test profile. static constexpr bool kDebugIgnoreChecksum = false;
static_assert(sizeof(ProfileCompilationInfo::kIndividualInlineCacheSize) == sizeof(uint8_t), "InlineCache::kIndividualInlineCacheSize does not have the expect type size");
static_assert(ProfileCompilationInfo::kIndividualInlineCacheSize < kIsMegamorphicEncoding, "InlineCache::kIndividualInlineCacheSize is larger than expected");
static_assert(ProfileCompilationInfo::kIndividualInlineCacheSize < kIsMissingTypesEncoding, "InlineCache::kIndividualInlineCacheSize is larger than expected");
int end_ret = deflateEnd(&strm); if (end_ret != Z_OK) { return nullptr;
}
return compressed_buffer;
}
// Inflate the data from `in_buffer` into `out_buffer`. The `out_buffer.size()` // is the expected output size of the buffer. It returns Z_STREAM_END on success. // On error, it returns Z_STREAM_ERROR if the compressed data is inconsistent // and Z_DATA_ERROR if the stream ended prematurely or the stream has extra data. int InflateBuffer(ArrayRef<const uint8_t> in_buffer, /*out*/ ArrayRef<uint8_t> out_buffer) { /* allocate inflate state */
z_stream strm;
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
strm.avail_in = in_buffer.size();
strm.next_in = const_cast<uint8_t*>(in_buffer.data());
strm.avail_out = out_buffer.size();
strm.next_out = out_buffer.data();
int init_ret = inflateInit(&strm); if (init_ret != Z_OK) { return init_ret;
}
int ret = inflate(&strm, Z_NO_FLUSH); if (strm.avail_in != 0 || strm.avail_out != 0) { return Z_DATA_ERROR;
}
int end_ret = inflateEnd(&strm); if (end_ret != Z_OK) { return end_ret;
}
return ret;
}
} // anonymous namespace
enumclass ProfileCompilationInfo::ProfileLoadStatus : uint32_t {
kSuccess,
kIOError,
kBadMagic,
kVersionMismatch,
kBadData,
kMergeError, // Merging failed. There are too many extra descriptors // or classes without TypeId referenced by a dex file.
};
enumclass ProfileCompilationInfo::FileSectionType : uint32_t { // The values of section enumerators and data format for individual sections // must not be changed without changing the profile file version. New sections // can be added at the end and they shall be ignored by old versions of ART.
// The list of the dex files included in the profile. // There must be exactly one dex file section and it must be first.
kDexFiles = 0,
// Extra descriptors for referencing classes that do not have a `dex::TypeId` // in the referencing dex file, such as classes from a different dex file // (even outside of the dex files in the profile) or array classes that were // used from other dex files or created through reflection.
kExtraDescriptors = 1,
// Classes included in the profile.
kClasses = 2,
// Methods included in the profile, their hotness flags and inline caches.
kMethods = 3,
// The aggregation counts of the profile, classes and methods. This section is // an optional reserved section not implemented on client yet.
kAggregationCounts = 4,
// Classes included in the profile that should not be initialized by zygote or dex2oat // (usually due to some logic in the class initializer that should not be shared between // processes, e.g. initializing random seed).
kClassesNoPreload = 5,
// The number of known sections.
kNumberOfSections = 6
};
class ProfileCompilationInfo::FileSectionInfo { public: // Constructor for reading from a `ProfileSource`. Data shall be filled from the source.
FileSectionInfo() {}
// Constructor for writing to a file.
FileSectionInfo(FileSectionType type,
uint32_t file_offset,
uint32_t file_size,
uint32_t inflated_size)
: type_(type),
file_offset_(file_offset),
file_size_(file_size),
inflated_size_(inflated_size) {}
private:
FileSectionType type_;
uint32_t file_offset_;
uint32_t file_size_;
uint32_t inflated_size_; // If 0, do not inflate and use data from file directly.
};
// The file header. class ProfileCompilationInfo::FileHeader { public: // Constructor for reading from a `ProfileSource`. Data shall be filled from the source.
FileHeader() {
DCHECK(!IsValid());
}
private: // The upper bound for file section count is used to ensure that there // shall be no arithmetic overflow when calculating size of the header // with section information. staticconst uint32_t kMaxFileSectionCount;
int32_t fd_; // The fd is not owned by this class.
MemMap mem_map_;
size_t mem_map_cur_; // Current position in the map to read from.
};
// A helper structure to make sure we don't read past our buffers in the loops. // Also used for writing but the buffer should be pre-sized correctly for that, so we // DCHECK() we do not write beyond the end, rather than returning `false` on failure. class ProfileCompilationInfo::SafeBuffer { public:
SafeBuffer()
: storage_(nullptr),
ptr_current_(nullptr),
ptr_end_(nullptr) {}
// Reads an uint value and advances the current pointer. template <typename T> bool ReadUintAndAdvance(/*out*/ T* value) {
static_assert(std::is_unsigned<T>::value, "Type is not unsigned"); if (sizeof(T) > GetAvailableBytes()) { returnfalse;
}
*value = 0; for (size_t i = 0; i < sizeof(T); i++) {
*value += ptr_current_[i] << (i * kBitsPerByte);
}
ptr_current_ += sizeof(T); returntrue;
}
// Reads a length-prefixed string as `std::string_view` and advances the current pointer. // The length is `uint16_t`. bool ReadStringAndAdvance(/*out*/ std::string_view* value) {
uint16_t length; if (!ReadUintAndAdvance(&length)) { returnfalse;
} if (length > GetAvailableBytes()) { returnfalse;
} constvoid* null_char = memchr(GetCurrentPtr(), 0, length); if (null_char != nullptr) { // Embedded nulls are invalid. returnfalse;
}
*value = std::string_view(reinterpret_cast<constchar*>(GetCurrentPtr()), length);
Advance(length); returntrue;
}
// Compares the given data with the content at the 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) { if (data_size > GetAvailableBytes()) { returnfalse;
} if (memcmp(ptr_current_, data, data_size) == 0) {
ptr_current_ += data_size; returntrue;
} returnfalse;
}
// Perform an explicit lookup for the type instead of directly emplacing the // element. We do this because emplace() allocates the node before doing the // lookup and if it then finds an identical element, it shall deallocate the // node. For Arena allocations, that's essentially a leak. auto lb = classes.lower_bound(type_idx); if (lb != classes.end() && *lb == type_idx) { // The type index exists. return;
}
// Check if the adding the type will cause the cache to become megamorphic. if (classes.size() + 1 >= ProfileCompilationInfo::kIndividualInlineCacheSize) {
is_megamorphic = true;
classes.clear(); return;
}
// The type does not exist and the inline cache will not be megamorphic.
classes.emplace_hint(lb, type_idx);
}
// Transform the actual dex location into a key used to index the dex file in the profile. // See ProfileCompilationInfo#GetProfileDexFileBaseKey as well.
std::string ProfileCompilationInfo::GetProfileDexFileAugmentedKey( const DexFile* dex_file, const ProfileSampleAnnotation& annotation) {
DCHECK(dex_file != nullptr);
std::string base_key = GetProfileDexFileBaseKey(dex_file); return annotation == ProfileSampleAnnotation::kNone
? base_key
: base_key + kSampleMetadataSeparator + annotation.GetOriginPackageName();
}
// Returns the basename of the location (e.g. "base.apk" from "/dir/base.apk").
std::string_view ProfileCompilationInfo::GetLocationBasename(std::string_view base_location) {
DCHECK(!DexFileLoader::IsMultiDexLocation(base_location));
size_t last_sep_index = base_location.find_last_of('/'); return base_location.substr(last_sep_index == std::string_view::npos ? 0 : last_sep_index + 1);
}
// Transform the actual dex location into a base profile key (represented as 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 // collide with other entries.
std::string ProfileCompilationInfo::GetProfileDexFileBaseKey(std::string_view base_location,
std::string_view entry_name) {
DCHECK(!DexFileLoader::IsMultiDexLocation(base_location));
std::string_view filename = GetLocationBasename(base_location); if (entry_name.empty() || entry_name == "classes.dex") { return std::string(filename);
} return std::string(filename) + DexFileLoader::kMultiDexSeparator + std::string(entry_name);
}
std::string ProfileCompilationInfo::GetProfileDexFileBaseKey(const DexFile* dex_file) {
DCHECK(dex_file != nullptr); auto [base_location, index] = DexFileLoader::SplitMultiDexLocation(dex_file->GetLocation()); if (dex_file->HasDexContainer()) { // Use the new "!1" syntax for container dex files, since just the zip entry name isn't unique. return GetProfileDexFileBaseKey(base_location, std::to_string(index));
} else { // Use the old "!classes2.dex" syntax for backwards compatibility. return GetProfileDexFileBaseKey(base_location, DexFileLoader::GetMultiDexZipEntryName(index));
}
}
dex::TypeIndex ProfileCompilationInfo::FindOrCreateTypeIndex(const DexFile& dex_file,
TypeReference class_ref) {
DCHECK(class_ref.dex_file != nullptr);
DCHECK_LT(class_ref.TypeIndex().index_, class_ref.dex_file->NumTypeIds()); if (class_ref.dex_file == &dex_file) { // We can use the type index from the `class_ref` as it's a valid index in the `dex_file`. return class_ref.TypeIndex();
} // Try to find a `TypeId` in the method's dex file.
std::string_view descriptor = class_ref.dex_file->GetTypeDescriptorView(class_ref.TypeIndex()); return FindOrCreateTypeIndex(dex_file, descriptor);
}
dex::TypeIndex ProfileCompilationInfo::FindOrCreateTypeIndex(const DexFile& dex_file,
std::string_view descriptor) { const dex::TypeId* type_id = dex_file.FindTypeId(descriptor); if (type_id != nullptr) { return dex_file.GetIndexForTypeId(*type_id);
} // Try to find an existing extra descriptor.
uint32_t num_type_ids = dex_file.NumTypeIds();
uint32_t max_artificial_ids = DexFile::kDexNoIndex16 - num_type_ids; // Check descriptor length for "extra descriptor". We are using `uint16_t` as prefix. if (UNLIKELY(descriptor.size() > kMaxExtraDescriptorLength)) { return dex::TypeIndex(); // Invalid.
} auto it = extra_descriptors_indexes_.find(descriptor); if (it != extra_descriptors_indexes_.end()) { return (*it < max_artificial_ids) ? dex::TypeIndex(num_type_ids + *it) : dex::TypeIndex();
} // Check if inserting the extra descriptor yields a valid artificial type index. if (UNLIKELY(extra_descriptors_.size() >= max_artificial_ids)) { return dex::TypeIndex(); // Invalid.
} // Add the descriptor to extra descriptors and return the artificial type index.
ExtraDescriptorIndex new_extra_descriptor_index = AddExtraDescriptor(descriptor);
DCHECK_LT(new_extra_descriptor_index, max_artificial_ids); return dex::TypeIndex(num_type_ids + new_extra_descriptor_index);
}
#ifdef _WIN32 int flags = O_RDWR; #else int flags = O_RDWR | O_NOFOLLOW | O_CLOEXEC; #endif // There's no need to fsync profile data right away. We get many chances // to write it again in case something goes wrong. We can rely on a simple // close(), no sync, and let to the kernel decide when to write to disk.
ScopedFlock profile_file =
LockedFile::Open(filename.c_str(), flags, /*block=*/false, &error);
ProfileLoadStatus status = LoadInternal(fd, &error); if (status == ProfileLoadStatus::kSuccess) { returntrue;
}
if (clear_if_invalid &&
((status == ProfileLoadStatus::kBadMagic) ||
(status == ProfileLoadStatus::kVersionMismatch) ||
(status == ProfileLoadStatus::kBadData))) {
LOG(WARNING) << "Clearing bad or obsolete profile data from file "
<< filename << ": " << error; // When ART Service is enabled, this is the only place where we mutate a profile in place. // TODO(jiakaiz): Get rid of this. if (profile_file->ClearContent()) { returntrue;
} else {
PLOG(WARNING) << "Could not clear profile file: " << filename; returnfalse;
}
}
LOG(WARNING) << "Could not load profile data from file " << filename << ": " << error; returnfalse;
}
#ifndef ART_TARGET_ANDROID return SaveFallback(filename, bytes_written, flush); #else // Prior to U, SELinux policy doesn't allow apps to create profile files. // Additionally, when installd is being used for dexopt, it acquires a flock when working on a // profile. It's unclear to us whether the flock means that the file at the fd shouldn't change or // that the file at the path shouldn't change, especially when the installd code is modified by // partners. Therefore, we fall back to using a flock as well just to be safe. if (!android::modules::sdklevel::IsAtLeastU() ||
!android::base::GetBoolProperty("dalvik.vm.useartservice", /*default_value=*/false)) { return SaveFallback(filename, bytes_written, flush);
}
std::string tmp_filename = filename + ".XXXXXX.tmp"; // mkstemps creates the file with permissions 0600, which is the desired permissions, so there's // no need to chmod.
android::base::unique_fd fd(mkostemps(tmp_filename.data(), /*suffixlen=*/4, O_CLOEXEC)); if (fd.get() < 0) {
PLOG(WARNING) << "Failed to create temp profile file for " << filename; returnfalse;
}
// In case anything goes wrong. auto remove_tmp_file = android::base::make_scope_guard([&]() { if (unlink(tmp_filename.c_str()) != 0) {
PLOG(WARNING) << "Failed to remove temp profile file " << tmp_filename;
}
});
bool result = Save(fd.get(), flush); if (!result) {
VLOG(profiler) << "Failed to save profile info to temp profile file " << tmp_filename; returnfalse;
}
fd.reset();
// Move the temp profile file to the final location. if (rename(tmp_filename.c_str(), filename.c_str()) != 0) {
PLOG(WARNING) << "Failed to commit profile file " << filename; returnfalse;
}
remove_tmp_file.Disable();
if (flush) {
std::string dirname = android::base::Dirname(filename);
std::unique_ptr<File> dir(OS::OpenFileForReading(dirname.c_str())); if (dir == nullptr || dir->Flush(/*flush_metadata=*/true) != 0) {
PLOG(WARNING) << "Failed to flush directory " << dirname;
}
}
int64_t size = OS::GetFileSizeBytes(filename.c_str()); if (size != -1) {
VLOG(profiler) << "Successfully saved profile info to " << filename << " Size: " << size; if (bytes_written != nullptr) {
*bytes_written = static_cast<uint64_t>(size);
}
} else {
VLOG(profiler) << "Saved profile info to " << filename
<< " but failed to get size: " << strerror(errno);
}
returntrue; #endif
}
bool ProfileCompilationInfo::SaveFallback(const std::string& filename,
uint64_t* bytes_written, bool flush) {
std::string error; #ifdef _WIN32 int flags = O_WRONLY | O_CREAT; #else int flags = O_WRONLY | O_NOFOLLOW | O_CLOEXEC | O_CREAT; #endif // There's no need to fsync profile data right away. We get many chances // to write it again in case something goes wrong. We can rely on a simple // close(), no sync, and let to the kernel decide when to write to disk.
ScopedFlock profile_file =
LockedFile::Open(filename.c_str(), flags, /*block=*/false, &error); if (profile_file.get() == nullptr) {
LOG(WARNING) << "Couldn't lock the profile file " << filename << ": " << error; returnfalse;
}
int fd = profile_file->Fd();
// We need to clear the data because we don't support appending to the profiles yet. if (!profile_file->ClearContent()) {
PLOG(WARNING) << "Could not clear profile file: " << filename; returnfalse;
}
// This doesn't need locking because we are trying to lock the file for exclusive // access and fail immediately if we can't. bool result = Save(fd, flush);
if (flush) {
std::string dirname = android::base::Dirname(filename);
std::unique_ptr<File> dir(OS::OpenFileForReading(dirname.c_str())); if (dir == nullptr || dir->Flush(/*flush_metadata=*/true) != 0) {
PLOG(WARNING) << "Failed to flush directory " << dirname;
}
}
if (result) {
int64_t size = OS::GetFileSizeBytes(filename.c_str()); if (size != -1) {
VLOG(profiler)
<< "Successfully saved profile info to " << filename << " Size: "
<< size; if (bytes_written != nullptr) {
*bytes_written = static_cast<uint64_t>(size);
}
} else {
VLOG(profiler) << "Saved profile info to " << filename
<< " but failed to get size: " << strerror(errno);
}
} else {
VLOG(profiler) << "Failed to save profile info to " << filename;
} return result;
}
// Returns true if all the bytes were successfully written to the file descriptor. staticbool WriteBuffer(int fd, constvoid* buffer, size_t byte_count) { while (byte_count > 0) { int bytes_written = TEMP_FAILURE_RETRY(write(fd, buffer, byte_count)); if (bytes_written == -1) { returnfalse;
}
byte_count -= bytes_written; // Reduce the number of remaining bytes. reinterpret_cast<const uint8_t*&>(buffer) += bytes_written; // Move the buffer forward.
} returntrue;
}
/** *Serializationformat: * *Thefilestartswithaheaderandsectioninformation: *FileHeader *FileSectionInfo[] *ThefirstFileSectionInfomustbefortheDexFilessection. * *Therestofthefileisallowedtocontaindifferentsectionsinanyorder, *atarbitraryoffsets,withanygapsbetweeenthemandeachsectioncanbe *eitherplaintextorseparatelyzipped.However,we'rewritingsections *withoutanygapswiththefollowingorderandcompression: *DexFiles-mandatory,plaintext *ExtraDescriptors-optional,zipped *Classes-optional,zipped *ClassesNoPreload-optional,zipped *Methods-optional,zipped *AggregationCounts-optional,zipped,server-side * *DexFiles: *number_of_dex_files *(checksum,num_type_ids,num_method_ids,profile_key)[number_of_dex_files] *where`profile_key`isalength-prefixedstring,thelengthis`uint16_t`. * *ExtraDescriptors: *number_of_extra_descriptors *(extra_descriptor)[number_of_extra_descriptors] *where`extra_descriptor`isalength-prefixedstring,thelengthis`uint16_t`. * *Classescontainsrecordsforanynumberofdexfiles,eachconsistingof: *profile_index// Index of the dex file in DexFiles section. *number_of_classes *type_index_diff[number_of_classes] *whereinsteadofstoringplainsortedtypeindexes,westoretheirdifferences *assmallernumbersarelikelytocompressbetter. * *ClassesNoPreload:thesameformatas'Classes'section * *Methodscontainsrecordsforanynumberofdexfiles,eachconsistingof: *profile_index// Index of the dex file in DexFiles section. *following_data_size// For easy skipping of remaining data when dex file is filtered out. *method_flags *bitmap_data *method_encoding[]// Until the size indicated by `following_data_size`. *where`method_flags`isaunionofflagsrecordedformethodsinthereferenceddexfile, *`bitmap_data`contains`num_method_ids`bitsforeachbitsetin`method_flags`other *than"hot"(thesizeof`bitmap_data`isroundeduptowholebytes)and`method_encoding[]` *containsdataforhotmethods.The`method_encoding`is: *method_index_diff *number_of_inline_caches *inline_cache_encoding[number_of_inline_caches] *wheredifferencesinmethodindexesareusedforbettercompression, *andthe`inline_cache_encoding`is *dex_pc *(M|dex_map_size) *type_index_diff[dex_map_size] *where`M`standsforspecialencodingsindicatingmissingtypes(kIsMissingTypesEncoding) *ormemamorphiccall(kIsMegamorphicEncoding)whichbothimply`dex_map_size==0`.
**/ bool ProfileCompilationInfo::Save(int fd, bool flush) {
uint64_t start = NanoTime();
ScopedTrace trace(__PRETTY_FUNCTION__);
DCHECK_GE(fd, 0);
// Collect uncompressed section sizes. // Use `uint64_t` and assume this cannot overflow as we would have run out of memory.
uint64_t extra_descriptors_section_size = 0u; if (!extra_descriptors_.empty()) {
extra_descriptors_section_size += sizeof(uint16_t); // Number of descriptors. for (const std::string& descriptor : extra_descriptors_) { // Length-prefixed string, the length is `uint16_t`.
extra_descriptors_section_size += sizeof(uint16_t) + descriptor.size();
}
}
uint64_t dex_files_section_size = sizeof(ProfileIndexType); // Number of dex files.
uint64_t classes_section_size = 0u;
uint64_t classes_no_preload_section_size = 0u;
uint64_t methods_section_size = 0u;
DCHECK_LE(info_.size(), MaxProfileIndex()); for (const std::unique_ptr<DexFileData>& dex_data : info_) { if (dex_data->profile_key.size() > kMaxDexFileKeyLength) {
LOG(WARNING) << "DexFileKey exceeds allocated limit"; returnfalse;
}
dex_files_section_size += 3 * sizeof(uint32_t) + // Checksum, num_type_ids, num_method_ids. // Length-prefixed string, the length is `uint16_t`. sizeof(uint16_t) + dex_data->profile_key.size();
classes_section_size += dex_data->ClassesDataSize();
classes_no_preload_section_size += dex_data->ClassesNoPreloadDataSize();
methods_section_size += dex_data->MethodsDataSize();
}
// Check size limit. Allow large profiles for non target builds for the case // where we are merging many profiles to generate a boot image profile.
uint64_t total_uncompressed_size = header_and_infos_size + dex_files_section_size +
extra_descriptors_section_size + classes_section_size +
classes_no_preload_section_size + methods_section_size;
VLOG(profiler) << "Required capacity: " << total_uncompressed_size << " bytes."; if (total_uncompressed_size > GetSizeErrorThresholdBytes()) {
LOG(WARNING) << "Profile data size exceeds "
<< GetSizeErrorThresholdBytes()
<< " bytes. Profile will not be written to disk."
<< " It requires " << total_uncompressed_size << " bytes."; returnfalse;
}
if (flush) { // We do not flush for non-Linux because `flush` is only used by the runtime and the runtime // only supports Linux. #ifdef __linux__ if (fsync(fd) != 0) {
PLOG(WARNING) << "Failed to flush profile data";
} #endif
}
uint64_t total_time = NanoTime() - start;
VLOG(profiler) << "Compressed from "
<< std::to_string(total_uncompressed_size)
<< " to "
<< std::to_string(file_offset);
VLOG(profiler) << "Time to save profile: " << std::to_string(total_time); returntrue;
}
ProfileCompilationInfo::DexFileData* ProfileCompilationInfo::GetOrAddDexFileData( const std::string& profile_key,
uint32_t checksum,
uint32_t num_type_ids,
uint32_t num_method_ids) {
DCHECK_EQ(profile_key_map_.size(), info_.size()); auto profile_index_it = profile_key_map_.lower_bound(profile_key); if (profile_index_it == profile_key_map_.end() || profile_index_it->first != profile_key) { // We did not find the key. Create a new DexFileData if we did not reach the limit.
DCHECK_LE(profile_key_map_.size(), MaxProfileIndex()); if (profile_key_map_.size() == MaxProfileIndex()) { // Allow only a limited number dex files to be profiled. This allows us to save bytes // when encoding. For regular profiles this 2^8, and for boot profiles is 2^16 // (well above what we expect for normal applications).
LOG(ERROR) << "Exceeded the maximum number of dex file. Something went wrong"; return nullptr;
}
ProfileIndexType new_profile_index = dchecked_integral_cast<ProfileIndexType>(info_.size());
std::unique_ptr<DexFileData> dex_file_data(new (&allocator_) DexFileData(
&allocator_,
profile_key,
checksum,
new_profile_index,
num_type_ids,
num_method_ids,
IsForBootImage())); // Record the new data in `profile_key_map_` and `info_`.
std::string_view new_key(dex_file_data->profile_key);
profile_index_it = profile_key_map_.PutBefore(profile_index_it, new_key, new_profile_index);
info_.push_back(std::move(dex_file_data));
DCHECK_EQ(profile_key_map_.size(), info_.size());
}
ProfileIndexType profile_index = profile_index_it->second;
DexFileData* result = info_[profile_index].get();
// Check that the checksum matches. // This may different if for example the dex file was updated and we had a record of the old one. if (result->checksum != checksum) {
LOG(WARNING) << "Checksum mismatch for dex " << profile_key; return nullptr;
}
// DCHECK that profile info map key is consistent with the one stored in the dex file data. // This should always be the case since since the cache map is managed by ProfileCompilationInfo.
DCHECK_EQ(profile_key, result->profile_key);
DCHECK_EQ(profile_index, result->profile_index);
if (num_type_ids != result->num_type_ids || num_method_ids != result->num_method_ids) { // This should not happen... added to help investigating b/65812889.
LOG(ERROR) << "num_type_ids or num_method_ids mismatch for dex " << profile_key
<< ", types: expected=" << num_type_ids << " v. actual=" << result->num_type_ids
<< ", methods: expected=" << num_method_ids << " actual=" << result->num_method_ids; return nullptr;
}
ProfileCompilationInfo::ExtraDescriptorIndex ProfileCompilationInfo::AddExtraDescriptor(
std::string_view extra_descriptor) {
DCHECK_LE(extra_descriptor.size(), kMaxExtraDescriptorLength);
DCHECK(extra_descriptors_indexes_.find(extra_descriptor) == extra_descriptors_indexes_.end());
ExtraDescriptorIndex new_extra_descriptor_index = extra_descriptors_.size();
DCHECK_LE(new_extra_descriptor_index, kMaxExtraDescriptors); if (UNLIKELY(new_extra_descriptor_index == kMaxExtraDescriptors)) { return kMaxExtraDescriptors; // Cannot add another extra descriptor.
} // Add the extra descriptor and record the new index.
extra_descriptors_.emplace_back(extra_descriptor);
extra_descriptors_indexes_.insert(new_extra_descriptor_index); return new_extra_descriptor_index;
}
bool ProfileCompilationInfo::AddMethod(const ProfileMethodInfo& pmi,
MethodHotness::Flag flags, const ProfileSampleAnnotation& annotation, bool is_test) {
DexFileData* const data = GetOrAddDexFileData(pmi.ref.dex_file, annotation); if (data == nullptr) { // checksum mismatch returnfalse;
} if (!data->AddMethod(flags, pmi.ref.index)) { returnfalse;
} if ((flags & MethodHotness::kFlagHot) == 0) { // The method is not hot, do not add inline caches. returntrue;
}
const dex::MethodId& mid = pmi.ref.GetMethodId(); const DexFile& dex_file = *pmi.ref.dex_file; const dex::ClassDef* class_def = dex_file.FindClassDef(mid.class_idx_); // If `is_test` is true, we don't try to look at whether dex_pc fit in the // code item of that method.
uint32_t dex_pc_max = 0u; if (is_test) {
dex_pc_max = std::numeric_limits<uint32_t>::max();
} else { if (class_def == nullptr || dex_file.GetClassData(*class_def) == nullptr) { returntrue;
}
std::optional<uint32_t> offset = dex_file.GetCodeItemOffset(*class_def, pmi.ref.index); if (!offset.has_value()) { returntrue;
}
CodeItemInstructionAccessor accessor(dex_file, dex_file.GetCodeItem(offset.value()));
dex_pc_max = accessor.InsnsSizeInCodeUnits();
}
for (const ProfileMethodInfo::ProfileInlineCache& cache : pmi.inline_caches) { if (cache.dex_pc >= std::numeric_limits<uint16_t>::max()) { // Discard entries that don't fit the encoding. This should only apply to // inlined inline caches. See also `HInliner::GetInlineCacheAOT`. continue;
} if (cache.dex_pc >= dex_pc_max) { // Discard entries for inlined inline caches. We don't support them in // profiles yet. continue;
} if (cache.is_missing_types) {
FindOrAddDexPc(inline_cache, cache.dex_pc)->SetIsMissingTypes(); continue;
} if (cache.is_megamorphic) {
FindOrAddDexPc(inline_cache, cache.dex_pc)->SetIsMegamorphic(); continue;
} for (const TypeReference& class_ref : cache.classes) {
DexPcData* dex_pc_data = FindOrAddDexPc(inline_cache, cache.dex_pc); if (dex_pc_data->is_missing_types || dex_pc_data->is_megamorphic) { // Don't bother adding classes if we are missing types or already megamorphic. break;
}
dex::TypeIndex type_index = FindOrCreateTypeIndex(*pmi.ref.dex_file, class_ref); if (type_index.IsValid()) {
dex_pc_data->AddClass(type_index);
} else { // Could not create artificial type index.
dex_pc_data->SetIsMissingTypes();
}
}
} returntrue;
}
// TODO(calin): Fix this API. ProfileCompilationInfo::Load should be static and // return a unique pointer to a ProfileCompilationInfo upon success. bool ProfileCompilationInfo::Load( int fd, bool merge_classes, const ProfileLoadFilterFn& filter_fn) {
std::string error;
ProfileLoadStatus status = LoadInternal(fd, &error, merge_classes, filter_fn);
bool ProfileCompilationInfo::VerifyProfileData(const std::vector<const DexFile*>& dex_files) {
std::unordered_map<std::string, const DexFile*> key_to_dex_file; for (const DexFile* dex_file : dex_files) {
key_to_dex_file.emplace(GetProfileDexFileBaseKey(dex_file), dex_file);
} for (const std::unique_ptr<DexFileData>& dex_data : info_) { // We need to remove any annotation from the key during verification.
std::string base_key(GetBaseKeyViewFromAugmentedKey(dex_data->profile_key)); constauto it = key_to_dex_file.find(base_key); if (it == key_to_dex_file.end()) { // It is okay if profile contains data for additional dex files. continue;
} const DexFile* dex_file = it->second; const std::string& dex_location = dex_file->GetLocation(); if (!ChecksumMatch(dex_data->checksum, dex_file->GetLocationChecksum())) {
LOG(ERROR) << "Dex checksum mismatch while verifying profile "
<< "dex location " << dex_location << " (checksum="
<< dex_file->GetLocationChecksum() << ", profile checksum="
<< dex_data->checksum; returnfalse;
}
if (dex_data->num_method_ids != dex_file->NumMethodIds() ||
dex_data->num_type_ids != dex_file->NumTypeIds()) {
LOG(ERROR) << "Number of type or method ids in dex file and profile don't match."
<< "dex location " << dex_location
<< " dex_file.NumTypeIds=" << dex_file->NumTypeIds()
<< " .v dex_data.num_type_ids=" << dex_data->num_type_ids
<< ", dex_file.NumMethodIds=" << dex_file->NumMethodIds()
<< " v. dex_data.num_method_ids=" << dex_data->num_method_ids; returnfalse;
}
// Class and method data should be valid. Verify only in debug builds. if (kIsDebugBuild) { // Verify method_encoding. for (constauto& method_it : dex_data->method_map) {
CHECK_LT(method_it.first, dex_data->num_method_ids);
// Verify class indices of inline caches. const InlineCacheMap &inline_cache_map = method_it.second; for (constauto& inline_cache_it : inline_cache_map) { const DexPcData& dex_pc_data = inline_cache_it.second; if (dex_pc_data.is_missing_types || dex_pc_data.is_megamorphic) { // No class indices to verify.
CHECK(dex_pc_data.classes.empty()); continue;
}
ProfileCompilationInfo::ProfileLoadStatus ProfileCompilationInfo::OpenSource(
int32_t fd, /*out*/ std::unique_ptr<ProfileSource>* source, /*out*/ std::string* error) { if (IsProfileFile(fd)) {
source->reset(ProfileSource::Create(fd)); return ProfileLoadStatus::kSuccess;
} else {
std::unique_ptr<ZipArchive> zip_archive(
ZipArchive::OpenFromFd(DupCloexec(fd), "profile", error)); if (zip_archive.get() == nullptr) {
*error = "Could not open the profile zip archive"; return ProfileLoadStatus::kBadData;
}
std::unique_ptr<ZipEntry> zip_entry(zip_archive->Find(kDexMetadataProfileEntry, error)); if (zip_entry == nullptr) { // Allow archives without the profile entry. In this case, create an empty profile. // This gives more flexible when ure-using archives that may miss the entry. // (e.g. dex metadata files)
LOG(WARNING) << "Could not find entry " << kDexMetadataProfileEntry
<< " in the zip archive. Creating an empty profile.";
source->reset(ProfileSource::Create(MemMap::Invalid())); return ProfileLoadStatus::kSuccess;
} if (zip_entry->GetUncompressedLength() == 0) {
*error = "Empty profile entry in the zip archive."; return ProfileLoadStatus::kBadData;
}
// TODO(calin) pass along file names to assist with debugging.
MemMap map = zip_entry->MapDirectlyOrExtract(
kDexMetadataProfileEntry, "profile file", error, alignof(ProfileSource));
uint16_t num_extra_descriptors; if (!buffer.ReadUintAndAdvance(&num_extra_descriptors)) {
*error = "Error reading number of extra descriptors."; return ProfileLoadStatus::kBadData;
}
// Note: We allow multiple extra descriptors sections in a single profile file // but that can lead to `kMergeError` if there are too many extra descriptors. // Other sections can reference only extra descriptors from preceding sections.
extra_descriptors_remap->reserve(
std::min<size_t>(extra_descriptors_remap->size() + num_extra_descriptors,
std::numeric_limits<uint16_t>::max())); for (uint16_t i = 0; i != num_extra_descriptors; ++i) {
std::string_view extra_descriptor; if (!buffer.ReadStringAndAdvance(&extra_descriptor)) {
*error += "Error reading extra descriptor string."; return ProfileLoadStatus::kBadData;
} if (!IsValidDescriptor(std::string(extra_descriptor).c_str())) {
*error += "Invalid extra descriptor."; return ProfileLoadStatus::kBadData;
} // Try to match an existing extra descriptor. auto it = extra_descriptors_indexes_.find(extra_descriptor); if (it != extra_descriptors_indexes_.end()) {
extra_descriptors_remap->push_back(*it); continue;
} // Try to insert a new extra descriptor.
ExtraDescriptorIndex extra_descriptor_index = AddExtraDescriptor(extra_descriptor); if (extra_descriptor_index == kMaxExtraDescriptors) {
*error = "Too many extra descriptors."; return ProfileLoadStatus::kMergeError;
}
extra_descriptors_remap->push_back(extra_descriptor_index);
} return ProfileLoadStatus::kSuccess;
}
while (buffer.GetAvailableBytes() != 0u) {
ProfileIndexType profile_index; if (!buffer.ReadUintAndAdvance(&profile_index)) {
*error = "Error profile index in methods section."; return ProfileLoadStatus::kBadData;
} if (profile_index >= dex_profile_index_remap.size()) {
*error = "Invalid profile index in methods section."; return ProfileLoadStatus::kBadData;
}
profile_index = dex_profile_index_remap[profile_index]; if (profile_index == MaxProfileIndex()) {
status = DexFileData::SkipMethods(buffer, error);
} else {
status = info_[profile_index]->ReadMethods(buffer, extra_descriptors_remap, error);
} if (status != ProfileLoadStatus::kSuccess) { return status;
}
} return ProfileLoadStatus::kSuccess;
}
// TODO(calin): fail fast if the dex checksums don't match.
ProfileCompilationInfo::ProfileLoadStatus ProfileCompilationInfo::LoadInternal(
int32_t fd,
std::string* error, bool merge_classes, const ProfileLoadFilterFn& filter_fn) {
ScopedTrace trace(__PRETTY_FUNCTION__);
DCHECK_GE(fd, 0);
std::unique_ptr<ProfileSource> source;
ProfileLoadStatus status = OpenSource(fd, &source, error); if (status != ProfileLoadStatus::kSuccess) { return status;
}
// We allow empty profile files. // Profiles may be created by ActivityManager or installd before we manage to // process them in the runtime or profman. if (source->HasEmptyContent()) { return ProfileLoadStatus::kSuccess;
}
// Allow large profiles for non target builds for the case where we are merging many profiles // to generate a boot image profile. if (uncompressed_data_size > GetSizeErrorThresholdBytes()) {
LOG(WARNING) << "Profile data size exceeds "
<< GetSizeErrorThresholdBytes()
<< " bytes. It has " << uncompressed_data_size << " bytes."; return ProfileLoadStatus::kBadData;
} if (uncompressed_data_size > GetSizeWarningThresholdBytes()) {
LOG(WARNING) << "Profile data size exceeds "
<< GetSizeWarningThresholdBytes()
<< " bytes. It has " << uncompressed_data_size << " bytes.";
}
// Process the mandatory dex files section.
DCHECK_NE(section_count, 0u); // Checked by `header.IsValid()` above. const FileSectionInfo& dex_files_section_info = section_infos[0]; if (dex_files_section_info.GetType() != FileSectionType::kDexFiles) {
*error = "First section is not dex files section."; return ProfileLoadStatus::kBadData;
}
dchecked_vector<ProfileIndexType> dex_profile_index_remap;
status = ReadDexFilesSection(
*source, dex_files_section_info, filter_fn, &dex_profile_index_remap, error); if (status != ProfileLoadStatus::kSuccess) {
DCHECK(!error->empty()); return status;
}
// Process all other sections.
dchecked_vector<ExtraDescriptorIndex> extra_descriptors_remap; for (uint32_t i = 1u; i != section_count; ++i) { const FileSectionInfo& section_info = section_infos[i];
DCHECK(status == ProfileLoadStatus::kSuccess); switch (section_info.GetType()) { case FileSectionType::kDexFiles:
*error = "Unsupported additional dex files section.";
status = ProfileLoadStatus::kBadData; break; case FileSectionType::kExtraDescriptors:
status = ReadExtraDescriptorsSection(
*source, section_info, &extra_descriptors_remap, error); break; case FileSectionType::kClassesNoPreload:
has_no_preload_section = true;
FALLTHROUGH_INTENDED; case FileSectionType::kClasses: // Skip if all dex files were filtered out. if (!info_.empty() && merge_classes) {
status = ReadClassesSection(
*source, section_info, dex_profile_index_remap, extra_descriptors_remap, error);
} break; case FileSectionType::kMethods: // Skip if all dex files were filtered out. if (!info_.empty()) {
status = ReadMethodsSection(
*source, section_info, dex_profile_index_remap, extra_descriptors_remap, error);
} break; case FileSectionType::kAggregationCounts: // This section is only used on server side. break; default: // Unknown section. Skip it. New versions of ART are allowed // to add sections that shall be ignored by old versions. break;
} if (status != ProfileLoadStatus::kSuccess) {
DCHECK(!error->empty()); return status;
}
}
return ProfileLoadStatus::kSuccess;
}
bool ProfileCompilationInfo::MergeWith(const ProfileCompilationInfo& other, bool merge_classes) { if (!SameVersion(other)) {
LOG(WARNING) << "Cannot merge different profile versions"; returnfalse;
}
// 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 // be a performance issue. for (const std::unique_ptr<DexFileData>& other_dex_data : other.info_) { // verify_checksum is false because we want to differentiate between a missing dex data and // a mismatched checksum. const DexFileData* dex_data = FindDexData(other_dex_data->profile_key, /* checksum= */ 0u, /* verify_checksum= */ false); if ((dex_data != nullptr) && (dex_data->checksum != other_dex_data->checksum)) {
LOG(WARNING) << "Checksum mismatch for dex " << other_dex_data->profile_key; returnfalse;
}
} // 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.
dchecked_vector<ProfileIndexType> dex_profile_index_remap;
dex_profile_index_remap.reserve(other.info_.size()); for (const std::unique_ptr<DexFileData>& other_dex_data : other.info_) { const DexFileData* dex_data = GetOrAddDexFileData(other_dex_data->profile_key,
other_dex_data->checksum,
other_dex_data->num_type_ids,
other_dex_data->num_method_ids); if (dex_data == nullptr) { // Could happen if we exceed the number of allowed dex files or there is // a mismatch in `num_type_ids` or `num_method_ids`. returnfalse;
}
DCHECK_EQ(other_dex_data->profile_index, dex_profile_index_remap.size());
dex_profile_index_remap.push_back(dex_data->profile_index);
}
// Then merge extra descriptors.
dchecked_vector<ExtraDescriptorIndex> extra_descriptors_remap;
extra_descriptors_remap.reserve(other.extra_descriptors_.size()); for (const std::string& other_extra_descriptor : other.extra_descriptors_) { auto it = extra_descriptors_indexes_.find(std::string_view(other_extra_descriptor)); if (it != extra_descriptors_indexes_.end()) {
extra_descriptors_remap.push_back(*it);
} else {
ExtraDescriptorIndex extra_descriptor_index = AddExtraDescriptor(other_extra_descriptor); if (extra_descriptor_index == kMaxExtraDescriptors) { // Too many extra descriptors. returnfalse;
}
extra_descriptors_remap.push_back(extra_descriptor_index);
}
}
// Merge the actual profile data. for (const std::unique_ptr<DexFileData>& other_dex_data : other.info_) {
DexFileData* dex_data = info_[dex_profile_index_remap[other_dex_data->profile_index]].get();
DCHECK_EQ(dex_data, FindDexData(other_dex_data->profile_key, other_dex_data->checksum));
// Merge the classes.
uint32_t num_type_ids = dex_data->num_type_ids;
DCHECK_EQ(num_type_ids, other_dex_data->num_type_ids); if (merge_classes) { // Classes are ordered by the `TypeIndex`, so we have the classes with a `TypeId` // in the dex file first, followed by classes using extra descriptors. auto it = other_dex_data->class_set.lower_bound(dex::TypeIndex(num_type_ids));
dex_data->class_set.insert(other_dex_data->class_set.begin(), it); for (auto end = other_dex_data->class_set.end(); it != end; ++it) {
ExtraDescriptorIndex new_extra_descriptor_index =
extra_descriptors_remap[it->index_ - num_type_ids]; if (new_extra_descriptor_index >= DexFile::kDexNoIndex16 - num_type_ids) { // Cannot represent the type with new extra descriptor index. returnfalse;
}
dex_data->class_set.insert(dex::TypeIndex(num_type_ids + new_extra_descriptor_index));
}
}
// Merge the methods and the inline caches. for (constauto& other_method_it : other_dex_data->method_map) {
uint16_t other_method_index = other_method_it.first;
InlineCacheMap* inline_cache = dex_data->FindOrAddHotMethod(other_method_index); if (inline_cache == nullptr) { returnfalse;
} constauto& other_inline_cache = other_method_it.second; for (constauto& other_ic_it : other_inline_cache) {
uint16_t other_dex_pc = other_ic_it.first; const ArenaSet<dex::TypeIndex>& other_class_set = other_ic_it.second.classes;
DexPcData* dex_pc_data = FindOrAddDexPc(inline_cache, other_dex_pc); if (other_ic_it.second.is_missing_types) {
dex_pc_data->SetIsMissingTypes();
} elseif (other_ic_it.second.is_megamorphic) {
dex_pc_data->SetIsMegamorphic();
} else { for (dex::TypeIndex type_index : other_class_set) { if (type_index.index_ >= num_type_ids) {
ExtraDescriptorIndex new_extra_descriptor_index =
extra_descriptors_remap[type_index.index_ - num_type_ids]; if (new_extra_descriptor_index >= DexFile::kDexNoIndex16 - num_type_ids) { // Cannot represent the type with new extra descriptor index. returnfalse;
}
type_index = dex::TypeIndex(num_type_ids + new_extra_descriptor_index);
}
dex_pc_data->AddClass(type_index);
}
}
}
}
// Merge the method bitmaps.
dex_data->MergeBitmap(*other_dex_data);
}
for (size_t k = 0; k < kProfileVersionSize - 1; k++) { // Iterate to 'kProfileVersionSize - 1' because the version_ ends with '\0' // which we don't want to print.
os << static_cast<char>(version_[k]);
}
os << "]\n";
if (info_.empty()) {
os << "-empty-"; return os.str();
}
if (!extra_descriptors_.empty()) {
os << "\nextra descriptors:"; for (const std::string& str : extra_descriptors_) {
os << "\n\t" << str;
}
os << "\n";
}
bool ProfileCompilationInfo::Equals(const ProfileCompilationInfo& other) { // No need to compare profile_key_map_. That's only a cache for fast search. // All the information is already in the info_ vector. if (!SameVersion(other)) { returnfalse;
} if (info_.size() != other.info_.size()) { returnfalse;
} for (size_t i = 0; i < info_.size(); i++) { const DexFileData& dex_data = *info_[i]; const DexFileData& other_dex_data = *other.info_[i]; if (!(dex_data == other_dex_data)) { returnfalse;
}
}
returntrue;
}
// Naive implementation to generate a random profile file suitable for testing. bool ProfileCompilationInfo::GenerateTestProfile(int fd,
uint16_t number_of_dex_files,
uint16_t method_percentage,
uint16_t class_percentage,
uint32_t random_seed) { const std::string base_dex_location = "base.apk";
ProfileCompilationInfo info; // The limits are defined by the dex specification. const uint16_t max_methods = std::numeric_limits<uint16_t>::max(); const uint16_t max_classes = std::numeric_limits<uint16_t>::max();
uint16_t number_of_methods = max_methods * method_percentage / 100;
uint16_t number_of_classes = max_classes * class_percentage / 100;
std::srand(random_seed);
// Make sure we generate more samples with a low index value. // This makes it more likely to hit valid method/class indices in small apps. const uint16_t kFavorFirstN = 10000; const uint16_t kFavorSplit = 2;
for (uint16_t i = 0; i < number_of_dex_files; i++) {
std::string entry_name = DexFileLoader::GetMultiDexZipEntryName(i);
std::string profile_key = info.GetProfileDexFileBaseKey(base_dex_location, entry_name);
DexFileData* const data =
info.GetOrAddDexFileData(profile_key, /*checksum=*/ 0, max_classes, max_methods); for (uint16_t m = 0; m < number_of_methods; m++) {
uint16_t method_idx = rand() % max_methods; if (m < (number_of_methods / kFavorSplit)) {
method_idx %= kFavorFirstN;
} // Alternate between startup and post startup.
uint32_t flags = MethodHotness::kFlagHot;
flags |= ((m & 1) != 0) ? MethodHotness::kFlagPostStartup : MethodHotness::kFlagStartup;
data->AddMethod(static_cast<MethodHotness::Flag>(flags), method_idx);
}
for (uint16_t c = 0; c < number_of_classes; c++) {
uint16_t type_idx = rand() % max_classes; if (c < (number_of_classes / kFavorSplit)) {
type_idx %= kFavorFirstN;
}
data->class_set.insert(dex::TypeIndex(type_idx));
}
} return info.Save(fd);
}
// Naive implementation to generate a random profile file suitable for testing. // Description of random selection: // * Select a random starting point S. // * For every index i, add (S+i) % (N - total number of methods/classes) to profile with the // probably of 1/(N - i - number of methods/classes needed to add in profile). bool ProfileCompilationInfo::GenerateTestProfile( int fd,
std::vector<std::unique_ptr<const DexFile>>& dex_files,
uint16_t method_percentage,
uint16_t class_percentage,
uint32_t random_seed) {
ProfileCompilationInfo info;
std::default_random_engine rng(random_seed); auto create_shuffled_range = [&rng](uint32_t take, uint32_t out_of) {
CHECK_LE(take, out_of);
std::vector<uint32_t> vec(out_of);
std::iota(vec.begin(), vec.end(), 0u);
std::shuffle(vec.begin(), vec.end(), rng);
vec.erase(vec.begin() + take, vec.end());
std::sort(vec.begin(), vec.end()); return vec;
}; for (std::unique_ptr<const DexFile>& dex_file : dex_files) {
std::string profile_key = info.GetProfileDexFileBaseKey(dex_file.get());
uint32_t checksum = dex_file->GetLocationChecksum();
bool ProfileCompilationInfo::IsEmpty() const {
DCHECK_EQ(info_.size(), profile_key_map_.size()); // Note that this doesn't look at the bitmap region, so we will return true // when the profile contains only non-hot methods. This is generally ok // as for speed-profile to be useful we do need hot methods and resolved classes. return GetNumberOfMethods() == 0 && GetNumberOfResolvedClasses() == 0;
}
// Mark a method as executed at least once. bool ProfileCompilationInfo::DexFileData::AddMethod(MethodHotness::Flag flags, size_t index) { if (index >= num_method_ids || index > kMaxSupportedMethodIndex) {
LOG(ERROR) << "Invalid method index " << index << ". num_method_ids=" << num_method_ids
<< ", max: " << kMaxSupportedMethodIndex; returnfalse;
}
SetMethodHotness(index, flags);
if ((flags & MethodHotness::kFlagHot) != 0) {
ProfileCompilationInfo::InlineCacheMap* result = FindOrAddHotMethod(index);
DCHECK(result != nullptr);
} returntrue;
}
bool ProfileCompilationInfo::IsProfileFile(int fd) { // First check if it's an empty file as we allow empty profile files. // Profiles may be created by ActivityManager or installd before we manage to // process them in the runtime or profman. struct stat stat_buffer; if (fstat(fd, &stat_buffer) != 0) { returnfalse;
}
if (stat_buffer.st_size == 0) { returntrue;
}
// The files is not empty. Check if it contains the profile magic.
size_t byte_count = sizeof(kProfileMagic);
uint8_t buffer[sizeof(kProfileMagic)]; if (!android::base::ReadFullyAtOffset(fd, buffer, byte_count, /*offset=*/ 0)) { returnfalse;
}
// Reset the offset to prepare the file for reading.
off_t rc = TEMP_FAILURE_RETRY(lseek(fd, 0, SEEK_SET)); if (rc == static_cast<off_t>(-1)) {
PLOG(ERROR) << "Failed to reset the offset"; returnfalse;
}
bool ProfileCompilationInfo::UpdateProfileKeys( const std::vector<std::unique_ptr<const DexFile>>& dex_files, /*out*/ bool* matched) { // This check aligns with when dex2oat falls back from "speed-profile" to "verify". // // ART Service relies on the exit code of profman, which is determined by the value of `matched`, // to judge whether it should re-dexopt for "speed-profile". Therefore, a misalignment will cause // repeated dexopt. if (IsEmpty()) {
*matched = false; returntrue;
}
DCHECK(!info_.empty());
*matched = true;
// A map from the old base key to the new base key.
std::unordered_map<std::string, std::string> old_key_to_new_key;
// A map from the new base key to all matching old base keys (an invert of the map above), for // detecting duplicate keys.
std::unordered_map<std::string, std::unordered_set<std::string>> new_key_to_old_keys;
for (const std::unique_ptr<DexFileData>& dex_data : info_) {
std::string old_base_key = GetBaseKeyFromAugmentedKey(dex_data->profile_key); bool found = false; for (const std::unique_ptr<const DexFile>& dex_file : dex_files) { if (dex_data->checksum == dex_file->GetLocationChecksum() &&
dex_data->num_type_ids == dex_file->NumTypeIds() &&
dex_data->num_method_ids == dex_file->NumMethodIds()) {
std::string new_base_key = GetProfileDexFileBaseKey(dex_file.get()); if (dex_file->GetHeader().HasDexContainer()) { // DEX v41 introduces dex containers, which store multipe dex files per zip entry. // This means the ZIP CRC32 alone isn't unique, so also check the location suffix. if (DexFileLoader::SplitMultiDexLocation(old_base_key).second !=
DexFileLoader::SplitMultiDexLocation(new_base_key).second) { continue;
}
}
old_key_to_new_key[old_base_key] = new_base_key;
new_key_to_old_keys[new_base_key].insert(old_base_key);
found = true; break;
}
} if (!found) {
*matched = false; // Keep the old key.
old_key_to_new_key[old_base_key] = old_base_key;
new_key_to_old_keys[old_base_key].insert(old_base_key);
}
}
for (constauto& [new_key, old_keys] : new_key_to_old_keys) { if (old_keys.size() > 1) {
LOG(ERROR) << "Cannot update multiple profile keys [" << android::base::Join(old_keys, ", ")
<< "] to the same new key '" << new_key << "'"; returnfalse;
}
}
// Check passed. Now perform the actual mutation.
profile_key_map_.clear();
for (const std::unique_ptr<DexFileData>& dex_data : info_) {
std::string old_base_key = GetBaseKeyFromAugmentedKey(dex_data->profile_key); const std::string& new_base_key = old_key_to_new_key[old_base_key];
DCHECK(!new_base_key.empty()); // Retain the annotation (if any) during the renaming by re-attaching the info from the old key.
dex_data->profile_key = MigrateAnnotationInfo(new_base_key, dex_data->profile_key);
profile_key_map_.Put(dex_data->profile_key, dex_data->profile_index);
}
constexpr size_t kPerHotMethodSize = sizeof(uint16_t) + // Method index diff. sizeof(uint16_t); // Inline cache size.
constexpr size_t kPerDexPcEntrySize = sizeof(uint16_t) + // Dex PC. sizeof(uint8_t); // Number of inline cache classes.
constexpr size_t kPerClassEntrySize = sizeof(uint16_t); // Type index diff.
size_t saved_bitmap_byte_size = BitsToBytesRoundUp(local_saved_bitmap_bit_size);
size = sizeof(ProfileIndexType) + // Which dex file. sizeof(uint32_t) + // Total size of following data. sizeof(uint16_t) + // Method flags.
saved_bitmap_byte_size + // Bitmap data.
num_hot_methods * kPerHotMethodSize + // Data for hot methods.
num_dex_pc_entries * kPerDexPcEntrySize + // Data for dex pc entries.
num_class_entries * kPerClassEntrySize; // Data for inline cache class entries.
} if (method_flags != nullptr) {
*method_flags = local_method_flags;
} if (saved_bitmap_bit_size != nullptr) {
*saved_bitmap_bit_size = local_saved_bitmap_bit_size;
} return size;
}
void ProfileCompilationInfo::DexFileData::WriteMethods(SafeBuffer& buffer) const {
uint16_t method_flags;
size_t saved_bitmap_bit_size;
uint32_t methods_data_size = MethodsDataSize(&method_flags, &saved_bitmap_bit_size); if (methods_data_size == 0u) { return; // No data to write.
}
DCHECK_GE(buffer.GetAvailableBytes(), methods_data_size);
uint32_t expected_available_bytes_at_end = buffer.GetAvailableBytes() - methods_data_size;
// Write the profile index.
buffer.WriteUintAndAdvance(profile_index); // Write the total size of the following methods data (without the profile index // and the total size itself) for easy skipping when the dex file is filtered out.
uint32_t following_data_size = methods_data_size - sizeof(ProfileIndexType) - sizeof(uint32_t);
buffer.WriteUintAndAdvance(following_data_size); // Write the used method flags.
buffer.WriteUintAndAdvance(method_flags);
// Store the difference between the method indices for better compression. // The SafeMap is ordered by method_id, so the difference will always be non negative.
DCHECK_GE(method_index, last_method_index);
uint16_t diff_with_last_method_index = method_index - last_method_index;
last_method_index = method_index;
buffer.WriteUintAndAdvance(diff_with_last_method_index);
// Add the dex pc.
buffer.WriteUintAndAdvance(dex_pc);
// Add the megamorphic/missing_types encoding if needed and continue. // In either cases we don't add any classes to the profiles and so there's // no point to continue. // TODO: in case we miss types there is still value to add the rest of the // classes. (This requires changing profile version or using a new section type.) if (dex_pc_data.is_missing_types) { // At this point the megamorphic flag should not be set.
DCHECK(!dex_pc_data.is_megamorphic);
DCHECK_EQ(classes.size(), 0u);
buffer.WriteUintAndAdvance(kIsMissingTypesEncoding); continue;
} elseif (dex_pc_data.is_megamorphic) {
DCHECK_EQ(classes.size(), 0u);
buffer.WriteUintAndAdvance(kIsMegamorphicEncoding); continue;
}
DCHECK_LT(classes.size(), ProfileCompilationInfo::kIndividualInlineCacheSize);
DCHECK_NE(classes.size(), 0u) << "InlineCache contains a dex_pc with 0 classes";
// Add the number of classes for the dex PC.
buffer.WriteUintAndAdvance(dchecked_integral_cast<uint8_t>(classes.size())); // Store the class set.
WriteClassSet(buffer, classes);
}
}
// Check if we've written the right number of bytes.
DCHECK_EQ(buffer.GetAvailableBytes(), expected_available_bytes_at_end);
}
ProfileCompilationInfo::ProfileLoadStatus ProfileCompilationInfo::DexFileData::ReadMethods(
SafeBuffer& buffer, const dchecked_vector<ExtraDescriptorIndex>& extra_descriptors_remap,
std::string* error) {
uint32_t following_data_size; if (!buffer.ReadUintAndAdvance(&following_data_size)) {
*error = "Error reading methods data size."; return ProfileLoadStatus::kBadData;
} if (following_data_size > buffer.GetAvailableBytes()) {
*error = "Methods data size exceeds available data size."; return ProfileLoadStatus::kBadData;
}
uint32_t expected_available_bytes_at_end = buffer.GetAvailableBytes() - following_data_size;
// Read method flags.
uint16_t method_flags; if (!buffer.ReadUintAndAdvance(&method_flags)) {
*error = "Error reading method flags."; return ProfileLoadStatus::kBadData;
} if (!is_for_boot_image && method_flags >= (MethodHotness::kFlagLastRegular << 1)) { // The profile we're loading contains data for boot image.
*error = "Method flags contain boot image profile flags for non-boot image profile."; return ProfileLoadStatus::kBadData;
}
// Load inline cache classes.
uint8_t inline_cache_classes_size; if (!buffer.ReadUintAndAdvance(&inline_cache_classes_size)) {
*error = "Error reading inline cache classes size."; return ProfileLoadStatus::kBadData;
} if (inline_cache_classes_size == kIsMissingTypesEncoding) {
dex_pc_data->SetIsMissingTypes();
} elseif (inline_cache_classes_size == kIsMegamorphicEncoding) {
dex_pc_data->SetIsMegamorphic();
} elseif (inline_cache_classes_size >= kIndividualInlineCacheSize) {
*error = "Inline cache size too large."; return ProfileLoadStatus::kBadData;
} else {
uint16_t type_index = 0u; for (size_t i = 0; i != inline_cache_classes_size; ++i) {
uint16_t type_index_diff; if (!buffer.ReadUintAndAdvance(&type_index_diff)) {
*error = "Error reading inline cache type index diff."; return ProfileLoadStatus::kBadData;
} if (type_index_diff == 0u && i != 0u) {
*error = "Duplicate inline cache type index."; return ProfileLoadStatus::kBadData;
} if (type_index_diff >= num_valid_type_indexes - type_index) {
*error = "Invalid inline cache type index."; return ProfileLoadStatus::kBadData;
}
type_index += type_index_diff; if (type_index >= num_type_ids) {
ExtraDescriptorIndex new_extra_descriptor_index =
extra_descriptors_remap[type_index - num_type_ids]; if (new_extra_descriptor_index >= DexFile::kDexNoIndex16 - num_type_ids) {
*error = "Remapped inline cache type index out of range."; return ProfileLoadStatus::kMergeError;
}
dex_pc_data->AddClass(dex::TypeIndex(num_type_ids + new_extra_descriptor_index));
} else {
dex_pc_data->AddClass(dex::TypeIndex(type_index));
}
}
}
}
}
}
if (buffer.GetAvailableBytes() != expected_available_bytes_at_end) {
*error = "Methods data did not end at expected position."; return ProfileLoadStatus::kBadData;
}
return ProfileLoadStatus::kSuccess;
}
ProfileCompilationInfo::ProfileLoadStatus ProfileCompilationInfo::DexFileData::SkipMethods(
SafeBuffer& buffer,
std::string* error) {
uint32_t following_data_size; if (!buffer.ReadUintAndAdvance(&following_data_size)) {
*error = "Error reading methods data size to skip."; return ProfileLoadStatus::kBadData;
} if (following_data_size > buffer.GetAvailableBytes()) {
*error = "Methods data size to skip exceeds remaining data."; return ProfileLoadStatus::kBadData;
}
buffer.Advance(following_data_size); return ProfileLoadStatus::kSuccess;
}
auto create_metadata_fn = []() { return FlattenProfileData::ItemMetadata(); };
// Iterate through all the dex files, find the methods/classes associated with each of them, // and add them to the flatten result. for (const std::unique_ptr<const DexFile>& dex_file : dex_files) { // Find all the dex data for the given dex file. // We may have multiple dex data if the methods or classes were added using // different annotations.
std::vector<const DexFileData*> all_dex_data;
FindAllDexData(dex_file.get(), &all_dex_data); for (const DexFileData* dex_data : all_dex_data) { // Extract the annotation from the key as we want to store it in the flatten result.
ProfileSampleAnnotation annotation = GetAnnotationFromKey(dex_data->profile_key);
// Check which methods from the current dex files are in the profile. for (uint32_t method_idx = 0; method_idx < dex_data->num_method_ids; ++method_idx) {
MethodHotness hotness = dex_data->GetHotnessInfo(method_idx); if (!hotness.IsInProfile()) { // Not in the profile, continue. continue;
} // The method is in the profile, create metadata item for it and added to the result.
MethodReference ref(dex_file.get(), method_idx);
FlattenProfileData::ItemMetadata& metadata =
result->method_metadata_.GetOrCreate(ref, create_metadata_fn);
metadata.flags_ |= hotness.flags_;
metadata.annotations_.push_back(annotation);
metadata.ExtractInlineCacheInfo(*this, dex_file.get(), method_idx);
// Update the max aggregation counter for methods. // This is essentially a cache, to avoid traversing all the methods just to find out // this value.
result->max_aggregation_for_methods_ = std::max(
result->max_aggregation_for_methods_, static_cast<uint32_t>(metadata.annotations_.size()));
}
// Check which classes from the current dex files are in the profile. for (const dex::TypeIndex& type_index : dex_data->class_set) { if (type_index.index_ >= dex_file->NumTypeIds()) { // Not a valid `dex::TypeIndex` for `TypeReference`. // TODO: Rewrite the API to use descriptors or the `ProfileCompilationInfo` directly // instead of the `FlattenProfileData` helper class. continue;
}
TypeReference ref(dex_file.get(), type_index);
FlattenProfileData::ItemMetadata& metadata =
result->class_metadata_.GetOrCreate(ref, create_metadata_fn);
metadata.annotations_.push_back(annotation); // Update the max aggregation counter for classes.
result->max_aggregation_for_classes_ = std::max(
result->max_aggregation_for_classes_, static_cast<uint32_t>(metadata.annotations_.size()));
}
}
}
return result;
}
void FlattenProfileData::ItemMetadata::MergeInlineCacheInfo( const SafeMap<TypeReference, InlineCacheInfo, TypeReferenceValueComparator>& other) { for (constauto& [target, inline_cache_data] : other) { if (!inline_cache_data.is_megamorphic_ && !inline_cache_data.is_missing_types_ &&
inline_cache_data.classes_.empty()) { continue;
}
InlineCacheInfo& val = inline_cache_.FindOrAdd(target)->second; if (inline_cache_data.is_megamorphic_) {
val.is_megamorphic_ = true;
} if (inline_cache_data.is_missing_types_) {
val.is_missing_types_ = true;
} for (const std::string& cls : inline_cache_data.classes_) {
val.classes_.insert(cls);
}
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.