using ::art::mirror::Class; using ::art::mirror::DexCache; using ::art::mirror::Object; using ::art::mirror::ObjectArray; using ::art::mirror::String;
namespace art { namespace linker {
// The actual value of `kImageClassTableMinLoadFactor` is irrelevant because image class tables // are never resized, but we still need to pass a reasonable value to the constructor.
constexpr double kImageClassTableMinLoadFactor = 0.5; // We use `kImageClassTableMaxLoadFactor` to determine the buffer size for image class tables // to make them full. We never insert additional elements to them, so we do not want to waste // extra memory. And unlike runtime class tables, we do not want this to depend on runtime // properties (see `Runtime::GetHashTableMaxLoadFactor()` checking for low memory mode).
constexpr double kImageClassTableMaxLoadFactor = 0.6;
// The actual value of `kImageInternTableMinLoadFactor` is irrelevant because image intern tables // are never resized, but we still need to pass a reasonable value to the constructor.
constexpr double kImageInternTableMinLoadFactor = 0.5; // We use `kImageInternTableMaxLoadFactor` to determine the buffer size for image intern tables // to make them full. We never insert additional elements to them, so we do not want to waste // extra memory. And unlike runtime intern tables, we do not want this to depend on runtime // properties (see `Runtime::GetHashTableMaxLoadFactor()` checking for low memory mode).
constexpr double kImageInternTableMaxLoadFactor = 0.6;
// Separate objects into multiple bins to optimize dirty memory use. static constexpr bool kBinObjects = true;
namespace {
// Dirty object data from dirty-image-objects. struct DirtyEntry { // Reference field name and type. struct RefInfo {
std::string_view name;
std::string_view type;
};
std::string_view class_descriptor; // A "path" from class object to the dirty object. If empty -- the class itself is dirty.
std::vector<RefInfo> reference_path;
uint32_t sort_key = std::numeric_limits<uint32_t>::max();
};
// Parse dirty-image-object line of the format: // <class_descriptor>[.<reference_field_name>:<reference_field_type>]* [<sort_key>]
std::optional<DirtyEntry> ParseDirtyEntry(std::string_view entry_str) {
DirtyEntry entry;
std::vector<std::string_view> tokens;
Split(entry_str, ' ', &tokens); if (tokens.empty()) { // entry_str is empty. return std::nullopt;
}
std::string_view path_to_root = tokens[0]; // Parse sort_key if present, otherwise it will be uint32::max by default. if (tokens.size() > 1) {
std::from_chars_result res =
std::from_chars(tokens[1].data(), tokens[1].data() + tokens[1].size(), entry.sort_key); if (res.ec != std::errc()) {
LOG(WARNING) << "Failed to parse dirty object sort key: \"" << entry_str << "\""; return std::nullopt;
}
}
std::vector<std::string_view> path_components;
Split(path_to_root, '.', &path_components); if (path_components.empty()) { return std::nullopt;
}
entry.class_descriptor = path_components[0]; for (size_t i = 1; i < path_components.size(); ++i) {
std::string_view name_and_type = path_components[i];
std::vector<std::string_view> ref_data;
Split(name_and_type, ':', &ref_data); if (ref_data.size() != 2) {
LOG(WARNING) << "Failed to parse dirty object reference field: \"" << entry_str << "\""; return std::nullopt;
}
// Calls VisitFunc for each non-null (reference)Object/ArtField pair. // Doesn't work with ObjectArray instances, because array elements don't have ArtField. class ReferenceFieldVisitor { public: using VisitFunc = std::function<void(mirror::Object&, ArtField&)>;
// Finds Class objects for descriptors of dirty entries. // Map keys are string_views, that point to strings from `dirty_image_objects`. // If there is no Class for a descriptor, the result map will have an entry with nullptr value. static HashMap<std::string_view, mirror::Object*> FindClassesByDescriptor( const std::vector<std::string>& dirty_image_objects) REQUIRES_SHARED(Locks::mutator_lock_) {
HashMap<std::string_view, mirror::Object*> descriptor_to_class; // Collect class descriptors that are used in dirty-image-objects. for (const std::string& entry : dirty_image_objects) { auto it = std::find_if(entry.begin(), entry.end(), [](char c) { return c == '.' || c == ' '; });
size_t descriptor_len = std::distance(entry.begin(), it);
static ObjPtr<mirror::ObjectArray<mirror::Object>> AllocateBootImageLiveObjects(
Thread* self, Runtime* runtime) REQUIRES_SHARED(Locks::mutator_lock_) {
ClassLinker* class_linker = runtime->GetClassLinker(); // The objects used for intrinsics must remain live even if references // to them are removed using reflection. Image roots are not accessible through reflection, // so the array we construct here shall keep them alive.
StackHandleScope<1> hs(self);
size_t live_objects_size =
enum_cast<size_t>(ImageHeader::kIntrinsicObjectsStart) +
IntrinsicObjects::GetNumberOfIntrinsicObjects();
ObjPtr<mirror::ObjectArray<mirror::Object>> live_objects =
mirror::ObjectArray<mirror::Object>::Alloc(
self, GetClassRoot<mirror::ObjectArray<mirror::Object>>(class_linker), live_objects_size); if (live_objects == nullptr) { return nullptr;
}
int32_t index = 0u; auto set_entry = [&](ImageHeader::BootImageLiveObjects entry,
ObjPtr<mirror::Object> value) REQUIRES_SHARED(Locks::mutator_lock_) {
DCHECK_EQ(index, enum_cast<int32_t>(entry));
live_objects->Set</*kTransacrionActive=*/ false>(index, value);
++index;
};
set_entry(ImageHeader::kOomeWhenThrowingException,
runtime->GetPreAllocatedOutOfMemoryErrorWhenThrowingException());
set_entry(ImageHeader::kOomeWhenThrowingOome,
runtime->GetPreAllocatedOutOfMemoryErrorWhenThrowingOOME());
set_entry(ImageHeader::kOomeWhenHandlingStackOverflow,
runtime->GetPreAllocatedOutOfMemoryErrorWhenHandlingStackOverflow());
set_entry(ImageHeader::kNoClassDefFoundError, runtime->GetPreAllocatedNoClassDefFoundError());
set_entry(ImageHeader::kClearedJniWeakSentinel, runtime->GetSentinel().Read());
bool ImageWriter::IsImageDexCache(ObjPtr<mirror::DexCache> dex_cache) const { // For boot image, we keep all dex caches. if (compiler_options_.IsBootImage()) { returntrue;
} // Dex caches already in the boot image do not belong to the image being written. if (IsInBootImage(dex_cache.Ptr())) { returnfalse;
} // Dex caches for the boot class path components that are not part of the boot image // cannot be garbage collected in PrepareImageAddressSpace() but we do not want to // include them in the app image. if (!ContainsElement(compiler_options_.GetDexFilesForOatFile(), dex_cache->GetDexFile())) { returnfalse;
} returntrue;
}
staticvoid ClearDexFileCookies() REQUIRES_SHARED(Locks::mutator_lock_) { auto visitor = [](Object* obj) REQUIRES_SHARED(Locks::mutator_lock_) {
DCHECK(obj != nullptr); Class* klass = obj->GetClass(); if (klass == WellKnownClasses::dalvik_system_DexFile) {
ArtField* field = WellKnownClasses::dalvik_system_DexFile_cookie; // Null out the cookie to enable determinism. b/34090128
field->SetObject</*kTransactionActive*/false>(obj, nullptr);
}
};
Runtime::Current()->GetHeap()->VisitObjects(visitor);
}
if (UNLIKELY(!CreateImageRoots())) {
self->AssertPendingOOMException();
self->ClearException(); returnfalse;
}
if (compiler_options_.IsAppImage()) {
TimingLogger::ScopedTiming t("ClearDexFileCookies", timings); // Clear dex file cookies for app images to enable app image determinism. This is required // since the cookie field contains long pointers to DexFiles which are not deterministic. // b/34090128
ClearDexFileCookies();
}
}
if (kIsDebugBuild) {
ScopedObjectAccess soa(self);
CheckNonImageClassesRemoved();
}
// From this point on, there should be no GC, so we should not use unnecessary read barriers.
ScopedDebugDisallowReadBarriers sddrb(self);
{ // All remaining weak interns are referenced. Promote them to strong interns. Whether a // string was strongly or weakly interned, we shall make it strongly interned in the image.
TimingLogger::ScopedTiming t("PromoteInterns", timings);
ScopedObjectAccess soa(self);
PromoteWeakInternsToStrong(self);
}
// This needs to happen after CalculateNewObjectOffsets since it relies on intern_table_bytes_ and // bin size sums being calculated.
TimingLogger::ScopedTiming t("AllocMemory", timings); return AllocMemory();
}
bool ImageWriter::Write(int image_fd, const std::vector<std::string>& image_filenames,
size_t component_count) { // If image_fd or oat_fd are not File::kInvalidFd then we may have empty strings in // image_filenames or oat_filenames.
CHECK(!image_filenames.empty()); if (image_fd != File::kInvalidFd) {
CHECK_EQ(image_filenames.size(), 1u);
}
DCHECK(!oat_filenames_.empty());
CHECK_EQ(image_filenames.size(), oat_filenames_.size());
Thread* const self = Thread::Current();
ScopedDebugDisallowReadBarriers sddrb(self);
{
ScopedObjectAccess soa(self); for (size_t i = 0; i < oat_filenames_.size(); ++i) {
CreateHeader(i, component_count);
CopyAndFixupNativeData(i);
CopyAndFixupJniStubMethods(i);
}
}
{ // TODO: heap validation can't handle these fix up passes.
ScopedObjectAccess soa(self);
Runtime::Current()->GetHeap()->DisableObjectValidation();
CopyAndFixupObjects();
}
if (compiler_options_.IsAppImage()) {
CopyMetadata();
}
// Primary image header shall be written last for two reasons. First, this ensures // that we shall not end up with a valid primary image and invalid secondary image. // Second, its checksum shall include the checksums of the secondary images (XORed). // This way only the primary image checksum needs to be checked to determine whether // any of the images or oat files are out of date. (Oat file checksums are included // in the image checksum calculation.)
ImageHeader* primary_header = reinterpret_cast<ImageHeader*>(image_infos_[0].image_.Begin());
ImageFileGuard primary_image_file; for (size_t i = 0; i < image_filenames.size(); ++i) { const std::string& image_filename = image_filenames[i];
ImageInfo& image_info = GetImageInfo(i);
ImageFileGuard image_file; if (image_fd != File::kInvalidFd) { // Ignore image_filename, it is supplied only for better diagnostic.
image_file.reset(new File(image_fd, unix_file::kCheckSafeUsage)); // Empty the file in case it already exists. if (image_file != nullptr) {
TEMP_FAILURE_RETRY(image_file->SetLength(0));
TEMP_FAILURE_RETRY(image_file->Flush());
}
} else {
image_file.reset(OS::CreateEmptyFile(image_filename.c_str()));
}
if (image_file == nullptr) {
LOG(ERROR) << "Failed to open image file " << image_filename; returnfalse;
}
// Make file world readable if we have created it, i.e. when not passed as file descriptor. if (image_fd == -1 && !compiler_options_.IsAppImage() && fchmod(image_file->Fd(), 0644) != 0) {
PLOG(ERROR) << "Failed to make image file world readable: " << image_filename; returnfalse;
}
// Image data size excludes the bitmap and the header.
ImageHeader* const image_header = reinterpret_cast<ImageHeader*>(image_info.image_.Begin());
std::string error_msg; if (!image_header->WriteData(image_file,
image_info.image_.Begin(), reinterpret_cast<const uint8_t*>(image_info.image_bitmap_.Begin()),
image_storage_mode_,
compiler_options_.MaxImageBlockSize(), /* update_checksum= */ true,
&error_msg)) {
LOG(ERROR) << error_msg; returnfalse;
}
// Write header last in case the compiler gets killed in the middle of image writing. // We do not want to have a corrupted image with a valid header. // Delay the writing of the primary image header until after writing secondary images. if (i == 0u) {
primary_image_file = std::move(image_file);
} else { if (!image_file.WriteHeaderAndClose(image_filename, image_header, &error_msg)) {
LOG(ERROR) << error_msg; returnfalse;
} // Update the primary image checksum with the secondary image checksum.
primary_header->SetImageChecksum(
primary_header->GetImageChecksum() ^ image_header->GetImageChecksum());
}
}
DCHECK(primary_image_file != nullptr);
std::string error_msg; if (!primary_image_file.WriteHeaderAndClose(image_filenames[0], primary_header, &error_msg)) {
LOG(ERROR) << error_msg; returnfalse;
}
// The magic happens here. We segregate objects into different bins based // on how likely they are to get dirty at runtime. // // Likely-to-dirty objects get packed together into the same bin so that // at runtime their page dirtiness ratio (how many dirty objects a page has) is // maximized. // // This means more pages will stay either clean or shared dirty (with zygote) and // the app will use less of its own (private) memory.
Bin bin = Bin::kRegular;
if (kBinObjects) { // // Changing the bin of an object is purely a memory-use tuning. // It has no change on runtime correctness. // // Memory analysis has determined that the following types of objects get dirtied // the most: // // * Class'es which are verified [their clinit runs only at runtime] // - classes in general [because their static fields get overwritten] // - initialized classes with all-final statics are unlikely to be ever dirty, // so bin them separately // * Art Methods that are: // - native [their native entry point is not looked up until runtime] // - have declaring classes that aren't initialized // [their interpreter/quick entry points are trampolines until the class // becomes initialized] // // We also assume the following objects get dirtied either never or extremely rarely: // * Strings (they are immutable) // * Art methods that aren't native and have initialized declared classes // // We assume that "regular" bin objects are highly unlikely to become dirtied, // so packing them together will not result in a noticeably tighter dirty-to-clean ratio. //
ObjPtr<mirror::Class> klass = object->GetClass<kVerifyNone, kWithoutReadBarrier>(); if (klass->IsStringClass<kVerifyNone>()) { // Assign strings to their bin before checking dirty objects, because // string intern processing expects strings to be in Bin::kString.
bin = Bin::kString; // Strings are almost always immutable (except for object header).
} elseif (dirty_objects_.find(object) != dirty_objects_.end()) {
bin = Bin::kKnownDirty;
} elseif (klass->IsClassClass()) {
bin = Bin::kClassVerified;
ObjPtr<mirror::Class> as_klass = object->AsClass<kVerifyNone>(); if (as_klass->IsVisiblyInitialized<kVerifyNone>()) {
bin = Bin::kClassInitialized;
// If the class's static fields are all final, put it into a separate bin // since it's very likely it will stay clean. auto fields = as_klass->GetFields(); bool all_final = std::all_of(fields.begin(),
fields.end(),
[](ArtField& f) { return !f.IsStatic() || f.IsFinal(); }); if (all_final) {
bin = Bin::kClassInitializedFinalStatics;
}
}
} elseif (!klass->HasSuperClass()) { // Only `j.l.Object` and primitive classes lack the superclass and // there are no instances of primitive classes.
DCHECK(klass->IsObjectClass()); // Instance of java lang object, probably a lock object. This means it will be dirty when we // synchronize on it.
bin = Bin::kMiscDirty;
} elseif (klass->IsDexCacheClass<kVerifyNone>()) { // Dex file field becomes dirty when the image is loaded.
bin = Bin::kMiscDirty;
} // else bin = kBinRegular
}
// Assign the oat index too. if (IsMultiImage()) {
DCHECK(oat_index_map_.find(object) == oat_index_map_.end());
oat_index_map_.insert(std::make_pair(object, oat_index));
} else {
DCHECK(oat_index_map_.empty());
}
ImageInfo& image_info = GetImageInfo(oat_index);
size_t offset_delta = RoundUp(object_size, kObjectAlignment); // 64-bit alignment // How many bytes the current bin is at (aligned).
size_t current_offset = image_info.GetBinSlotSize(bin); // Move the current bin size up to accommodate the object we just assigned a bin slot.
image_info.IncrementBinSlotSize(bin, offset_delta);
// We always stash the bin slot into a lockword, in the 'forwarding address' state. // If it's in some other state, then we haven't yet assigned an image bin slot. if (object->GetLockWord(false).GetState() != LockWord::kForwardingAddress) { returnfalse;
} elseif (kIsDebugBuild) {
LockWord lock_word = object->GetLockWord(false);
size_t offset = lock_word.ForwardingAddress();
BinSlot bin_slot(offset);
size_t oat_index = GetOatIndex(object); const ImageInfo& image_info = GetImageInfo(oat_index);
DCHECK_LT(bin_slot.GetOffset(), image_info.GetBinSlotSize(bin_slot.GetBin()))
<< "bin slot offset should not exceed the size of that bin";
} returntrue;
}
// Create the image bitmap, only needs to cover mirror object section which is up to image_end_. // The covered size is rounded up to kCardSize to match the bitmap size expected by Loader::Init // at art::gc::space::ImageSpace.
CHECK_LE(image_info.image_end_, length);
image_info.image_bitmap_ = gc::accounting::ContinuousSpaceBitmap::Create("image bitmap",
image_info.image_.Begin(),
RoundUp(image_info.image_end_, gc::accounting::CardTable::kCardSize)); if (!image_info.image_bitmap_.IsValid()) {
LOG(ERROR) << "Failed to allocate memory for image bitmap"; returnfalse;
}
} returntrue;
}
// This visitor follows the references of an instance, recursively then prune this class // if a type of any field is pruned. class ImageWriter::PruneObjectReferenceVisitor { public:
PruneObjectReferenceVisitor(ImageWriter* image_writer, bool* early_exit,
HashSet<mirror::Object*>* visited, bool* result)
: image_writer_(image_writer), early_exit_(early_exit), visited_(visited), result_(result) {}
ObjPtr<mirror::ObjectArray<mirror::Class>> class_roots =
Runtime::Current()->GetClassLinker()->GetClassRoots();
ObjPtr<mirror::Class> klass = ref->IsClass() ? ref->AsClass() : ref->GetClass(); if (klass == GetClassRoot<mirror::Method>(class_roots) ||
klass == GetClassRoot<mirror::Constructor>(class_roots)) { // Prune all classes using reflection because the content they held will not be fixup.
*result_ = true;
}
if (ref->IsClass()) {
*result_ = *result_ ||
image_writer_->PruneImageClassInternal(ref->AsClass(), early_exit_, visited_);
} else { // Record the object visited in case of circular reference.
visited_->insert(ref);
*result_ = *result_ ||
image_writer_->PruneImageClassInternal(klass, early_exit_, visited_);
ref->VisitReferences(*this, *this); // Clean up before exit for next call of this function. auto it = visited_->find(ref);
DCHECK(it != visited_->end());
visited_->erase(it);
}
}
bool ImageWriter::PruneImageClassInternal(
ObjPtr<mirror::Class> klass, bool* early_exit,
HashSet<mirror::Object*>* visited) {
DCHECK(early_exit != nullptr);
DCHECK(visited != nullptr);
DCHECK(compiler_options_.IsAppImage() || compiler_options_.IsBootImageExtension()); if (klass == nullptr || IsInBootImage(klass.Ptr())) { returnfalse;
} auto found = prune_class_memo_.find(klass.Ptr()); if (found != prune_class_memo_.end()) { // Already computed, return the found value. return found->second;
} // Circular dependencies, return false but do not store the result in the memoization table. if (visited->find(klass.Ptr()) != visited->end()) {
*early_exit = true; returnfalse;
}
visited->insert(klass.Ptr()); bool result = klass->IsBootStrapClassLoaded(); // Prune if not an image class, this handles any broken sets of image classes such as having a // class in the set but not it's superclass.
result = result || !IsImageClass(compiler_options_, klass); bool my_early_exit = false; // Only for ourselves, ignore caller. // Remove classes that failed to verify since we don't want to have java.lang.VerifyError in the // app image. if (klass->IsErroneous()) {
result = true;
} else {
ObjPtr<mirror::ClassExt> ext(klass->GetExtData());
CHECK(ext.IsNull() || ext->GetErroneousStateError() == nullptr) << klass->PrettyClass();
} if (!result) { // Check interfaces since these wont be visited through VisitReferences.)
ObjPtr<mirror::IfTable> if_table = klass->GetIfTable(); for (size_t i = 0, num_interfaces = klass->GetIfTableCount(); i < num_interfaces; ++i) {
result = result || PruneImageClassInternal(if_table->GetInterface(i),
&my_early_exit,
visited);
}
} if (klass->IsObjectArrayClass()) {
result = result || PruneImageClassInternal(klass->GetComponentType(),
&my_early_exit,
visited);
} // Check static fields and their classes. if (klass->IsResolved() && klass->NumReferenceStaticFields() != 0) {
size_t num_static_fields = klass->NumReferenceStaticFields(); // Presumably GC can happen when we are cross compiling, it should not cause performance // problems to do pointer size logic.
MemberOffset field_offset = klass->GetFirstReferenceStaticFieldOffset(
Runtime::Current()->GetClassLinker()->GetImagePointerSize()); for (size_t i = 0u; i < num_static_fields; ++i) {
mirror::Object* ref = klass->GetFieldObject<mirror::Object>(field_offset); if (ref != nullptr) { if (ref->IsClass()) {
result = result || PruneImageClassInternal(ref->AsClass(), &my_early_exit, visited);
} else {
mirror::Class* type = ref->GetClass();
result = result || PruneImageClassInternal(type, &my_early_exit, visited); if (!result) { // For non-class case, also go through all the types mentioned by it's fields' // references recursively to decide whether to keep this class. bool tmp = false;
PruneObjectReferenceVisitor visitor(this, &my_early_exit, visited, &tmp);
ref->VisitReferences(visitor, visitor);
result = result || tmp;
}
}
}
field_offset = MemberOffset(field_offset.Uint32Value() + sizeof(mirror::HeapReference<mirror::Object>));
}
}
result = result || PruneImageClassInternal(klass->GetSuperClass(), &my_early_exit, visited); // Remove the class if the dex file is not in the set of dex files. This happens for classes that // are from uses-library if there is no profile. b/30688277
ObjPtr<mirror::DexCache> dex_cache = klass->GetDexCache(); if (dex_cache != nullptr) {
result = result ||
dex_file_oat_index_map_.find(dex_cache->GetDexFile()) == dex_file_oat_index_map_.end();
} // Erase the element we stored earlier since we are exiting the function. auto it = visited->find(klass.Ptr());
DCHECK(it != visited->end());
visited->erase(it); // Only store result if it is true or none of the calls early exited due to circular // dependencies. If visited is empty then we are the root caller, in this case the cycle was in // a child call and we can remember the result. if (result == true || !my_early_exit || visited->empty()) {
prune_class_memo_.Overwrite(klass.Ptr(), result);
}
*early_exit |= my_early_exit; return result;
}
bool ImageWriter::KeepClass(ObjPtr<mirror::Class> klass) { if (klass == nullptr) { returnfalse;
} if (IsInBootImage(klass.Ptr())) { // Already in boot image, return true.
DCHECK(!compiler_options_.IsBootImage()); returntrue;
} if (!IsImageClass(compiler_options_, klass)) { returnfalse;
} if (compiler_options_.IsAppImage()) { // For app images, we need to prune classes that // are defined by the boot class path we're compiling against but not in // the boot image spaces since these may have already been loaded at // run time when this image is loaded. Keep classes in the boot image // spaces we're compiling against since we don't want to re-resolve these. // FIXME: Update image classes in the `CompilerOptions` after initializing classes // with `--initialize-app-image-classes=true`. This experimental flag can currently // cause an inconsistency between `CompilerOptions::IsImageClass()` and what actually // ends up in the app image as seen in the run-test `660-clinit` where the class // `ObjectRef` is considered an app image class during compilation but in the end // it's pruned here. This inconsistency should be fixed if we want to properly // initialize app image classes. b/38313278 bool keep = !PruneImageClass(klass);
CHECK_IMPLIES(!compiler_options_.InitializeAppImageClasses(), keep)
<< klass->PrettyDescriptor(); return keep;
} returntrue;
}
class ImageWriter::PruneClassesVisitor : public ClassVisitor { public:
PruneClassesVisitor(ImageWriter* image_writer, ObjPtr<mirror::ClassLoader> class_loader)
: image_writer_(image_writer),
class_loader_(class_loader),
classes_to_prune_(),
defined_class_count_(0u) { }
booloperator()(ObjPtr<mirror::Class> klass) override REQUIRES_SHARED(Locks::mutator_lock_) { if (!image_writer_->KeepClass(klass.Ptr())) {
classes_to_prune_.insert(klass.Ptr()); if (klass->GetClassLoader() == class_loader_) {
++defined_class_count_;
}
} returntrue;
}
size_t Prune() REQUIRES_SHARED(Locks::mutator_lock_) {
ClassTable* class_table =
Runtime::Current()->GetClassLinker()->ClassTableForClassLoader(class_loader_);
WriterMutexLock mu(Thread::Current(), class_table->lock_); // App class loader class tables contain only one internal set. The boot class path class // table also contains class sets from boot images we're compiling against but we are not // pruning these boot image classes, so all classes to remove are in the last set.
DCHECK(!class_table->classes_.empty());
ClassTable::ClassSet& last_class_set = class_table->classes_.back(); for (mirror::Class* klass : classes_to_prune_) {
uint32_t hash = klass->DescriptorHash(); auto it = last_class_set.FindWithHash(ClassTable::TableSlot(klass, hash), hash);
DCHECK(it != last_class_set.end());
last_class_set.erase(it);
DCHECK(std::none_of(class_table->classes_.begin(),
class_table->classes_.end(),
[klass, hash](ClassTable::ClassSet& class_set)
REQUIRES_SHARED(Locks::mutator_lock_) {
ClassTable::TableSlot slot(klass, hash); return class_set.FindWithHash(slot, hash) != class_set.end();
}));
} return defined_class_count_;
}
// Prune uses-library dex caches. Only prune the uses-library dex caches since we want to make // sure the other ones don't get unloaded before the OatWriter runs.
class_linker->VisitClassTables(
[&](ClassTable* table) REQUIRES_SHARED(Locks::mutator_lock_) {
table->RemoveStrongRoots(
[&](GcRoot<mirror::Object> root) REQUIRES_SHARED(Locks::mutator_lock_) {
ObjPtr<mirror::Object> obj = root.Read(); if (obj->IsDexCache()) { // Return true if the dex file is not one of the ones in the map. return dex_file_oat_index_map_.find(obj->AsDexCache()->GetDexFile()) ==
dex_file_oat_index_map_.end();
} // Return false to avoid removing. returnfalse;
});
});
// Remove the undesired classes from the class roots.
{
PruneClassLoaderClassesVisitor class_loader_visitor(this);
VisitClassLoaders(&class_loader_visitor);
VLOG(compiler) << "Pruned " << class_loader_visitor.GetRemovedClassCount() << " classes";
}
// Work list of <object, oat_index> for objects. Everything in the queue must already be // assigned a bin slot.
WorkQueue work_queue_;
// Objects for individual bins. Indexed by `oat_index` and `bin`. // Cannot use ObjPtr<> because of invalidation in Heap::VisitObjects().
dchecked_vector<dchecked_vector<dchecked_vector<mirror::Object*>>> bin_objects_;
// Interns that do not have a corresponding StringId in any of the input dex files. // These shall be assigned to individual images based on the `oat_index` that we // see as we visit them during the work queue processing.
dchecked_vector<mirror::String*> non_dex_file_interns_;
};
// We do not visit native roots. These are handled with other logic. void VisitRootIfNonNull(
[[maybe_unused]] mirror::CompressedReference<mirror::Object>* root) const {
LOG(FATAL) << "UNREACHABLE";
} void VisitRoot([[maybe_unused]] mirror::CompressedReference<mirror::Object>* root) const {
LOG(FATAL) << "UNREACHABLE";
}
private: void VisitReference(mirror::Object* ref) const REQUIRES_SHARED(Locks::mutator_lock_) { if (helper_->TryAssignBinSlot(ref, oat_index_)) { // Remember how many objects we're adding at the front of the queue as we want // to reverse that range to process these references in the order of addition.
helper_->work_queue_.emplace_front(ref, oat_index_);
} if (ClassLinker::kAppImageMayContainStrings &&
helper_->image_writer_->compiler_options_.IsAppImage() &&
helper_->image_writer_->IsInternedAppImageStringReference(ref)) {
helper_->image_writer_->image_infos_[oat_index_].num_string_references_ += 1u;
}
}
// To ensure deterministic output, populate the work queue with objects in a pre-defined order. // Note: If we decide to implement a profile-guided layout, this is the place to do so.
// Get initial work queue with the image classes and assign their bin slots.
CollectClassesVisitor visitor(image_writer_);
{
WriterMutexLock mu(self, *Locks::classlinker_classes_lock_); if (compiler_options.IsBootImage() || compiler_options.IsBootImageExtension()) { // No need to filter based on class loader, boot class table contains only // classes defined by the boot class loader.
ClassTable* class_table = class_linker->boot_class_table_.get();
class_table->Visit<kWithoutReadBarrier>(visitor);
} else { // No need to visit boot class table as there are no classes there for the app image. for (const ClassLinker::ClassLoaderData& data : class_linker->class_loaders_) { auto class_loader =
DecodeWeakGlobalWithoutRB<mirror::ClassLoader>(vm, self, data.weak_root); if (class_loader != nullptr) {
ClassTable* class_table = class_loader->GetClassTable(); if (class_table != nullptr) { // Visit only classes defined in this class loader (avoid visiting multiple times). auto filtering_visitor = [&visitor, class_loader](ObjPtr<mirror::Class> klass)
REQUIRES_SHARED(Locks::mutator_lock_) { if (klass->GetClassLoader<kVerifyNone, kWithoutReadBarrier>() == class_loader) {
visitor(klass);
} returntrue;
};
class_table->Visit<kWithoutReadBarrier>(filtering_visitor);
}
}
}
}
}
DCHECK(work_queue_.empty());
work_queue_ = visitor.ProcessCollectedClasses(self); for (const std::pair<ObjPtr<mirror::Object>, size_t>& entry : work_queue_) {
DCHECK(entry.first != nullptr);
ObjPtr<mirror::Class> klass = entry.first->AsClass();
size_t oat_index = entry.second;
image_writer_->RecordNativeRelocations(klass, oat_index);
AssignImageBinSlot(klass.Ptr(), oat_index);
auto method_pointer_array_visitor =
[&](ObjPtr<mirror::PointerArray> pointer_array) REQUIRES_SHARED(Locks::mutator_lock_) {
constexpr Bin bin = kBinObjects ? Bin::kInternalClean : Bin::kRegular;
AssignImageBinSlot(pointer_array.Ptr(), oat_index, bin); // No need to add to the work queue. The class reference, if not in the boot image // (that is, when compiling the primary boot image), is already in the work queue.
};
VisitNewMethodPointerArrays(klass, method_pointer_array_visitor);
}
// Assign bin slots to dex caches.
{
ReaderMutexLock mu(self, *Locks::dex_lock_); for (const DexFile* dex_file : compiler_options.GetDexFilesForOatFile()) { auto it = image_writer_->dex_file_oat_index_map_.find(dex_file);
DCHECK(it != image_writer_->dex_file_oat_index_map_.end()) << dex_file->GetLocation(); const size_t oat_index = it->second; // Assign bin slot to this file's dex cache and add it to the end of the work queue. const ClassLinker::DexCacheData* data = class_linker->FindDexCacheDataLocked(*dex_file);
DCHECK(data != nullptr) << dex_file->GetLocation(); auto dex_cache = DecodeWeakGlobalWithoutRB<mirror::DexCache>(vm, self, data->weak_root);
DCHECK(dex_cache != nullptr); bool assigned = TryAssignBinSlot(dex_cache, oat_index);
DCHECK(assigned);
work_queue_.emplace_back(dex_cache, oat_index);
}
}
// Assign interns to images depending on the first dex file they appear in. // Record those that do not have a StringId in any dex file.
ProcessInterns(self);
// Since classes and dex caches have been assigned to their bins, when we process a class // we do not follow through the class references or dex caches, so we correctly process // only objects actually belonging to that class before taking a new class from the queue. // If multiple class statics reference the same object (directly or indirectly), the object // is treated as belonging to the first encountered referencing class.
ProcessWorkQueue();
}
void ImageWriter::LayoutHelper::ProcessRoots(Thread* self) { // Assign bin slots to the image roots and boot image live objects, add them to the work queue // and process the work queue. These objects reference other objects needed for the image, for // example the array of dex cache references, or the pre-allocated exceptions for the boot image.
DCHECK(work_queue_.empty());
constexpr Bin clean_bin = kBinObjects ? Bin::kInternalClean : Bin::kRegular;
size_t num_oat_files = image_writer_->oat_filenames_.size();
JavaVMExt* vm = down_cast<JNIEnvExt*>(self->GetJniEnv())->GetVm(); for (size_t oat_index = 0; oat_index != num_oat_files; ++oat_index) { // Put image roots and dex caches into `clean_bin`. auto image_roots = DecodeGlobalWithoutRB<mirror::ObjectArray<mirror::Object>>(
vm, image_writer_->image_roots_[oat_index]);
AssignImageBinSlot(image_roots, oat_index, clean_bin);
work_queue_.emplace_back(image_roots, oat_index); // Do not rely on the `work_queue_` for dex cache arrays, it would assign a different bin.
ObjPtr<ObjectArray<Object>> dex_caches = ObjPtr<ObjectArray<Object>>::DownCast(
image_roots->GetWithoutChecks<kVerifyNone, kWithoutReadBarrier>(ImageHeader::kDexCaches));
AssignImageBinSlot(dex_caches, oat_index, clean_bin);
work_queue_.emplace_back(dex_caches, oat_index);
} // Do not rely on the `work_queue_` for boot image live objects, it would assign a different bin. if (image_writer_->compiler_options_.IsBootImage()) {
ObjPtr<mirror::ObjectArray<mirror::Object>> boot_image_live_objects =
image_writer_->boot_image_live_objects_;
AssignImageBinSlot(boot_image_live_objects, GetDefaultOatIndex(), clean_bin);
work_queue_.emplace_back(boot_image_live_objects, GetDefaultOatIndex());
}
ProcessWorkQueue();
}
void ImageWriter::LayoutHelper::ProcessInterns(Thread* self) { // String bins are empty at this point.
DCHECK(std::all_of(bin_objects_.begin(),
bin_objects_.end(),
[](constauto& bins) { return bins[enum_cast<size_t>(Bin::kString)].empty();
}));
// There is only one non-boot image intern table and it's the last one.
InternTable* const intern_table = Runtime::Current()->GetInternTable();
MutexLock mu(self, *Locks::intern_table_lock_);
DCHECK_EQ(std::count_if(intern_table->strong_interns_.tables_.begin(),
intern_table->strong_interns_.tables_.end(),
[](const InternTable::Table::InternalTable& table) { return !table.IsBootImage();
}), 1);
DCHECK(!intern_table->strong_interns_.tables_.back().IsBootImage()); const InternTable::UnorderedSet& intern_set = intern_table->strong_interns_.tables_.back().set_;
// Assign bin slots to all interns with a corresponding `StringId` in one of the input dex files.
ImageWriter* image_writer = image_writer_; for (const DexFile* dex_file : image_writer->compiler_options_.GetDexFilesForOatFile()) { auto it = image_writer->dex_file_oat_index_map_.find(dex_file);
DCHECK(it != image_writer->dex_file_oat_index_map_.end()) << dex_file->GetLocation(); const size_t oat_index = it->second; // Assign bin slots for strings defined in this dex file in StringId (lexicographical) order. for (size_t i = 0, count = dex_file->NumStringIds(); i != count; ++i) {
uint32_t utf16_length; constchar* utf8_data = dex_file->GetStringDataAndUtf16Length(dex::StringIndex(i),
&utf16_length);
uint32_t hash = InternTable::Utf8String::Hash(utf16_length, utf8_data); auto intern_it =
intern_set.FindWithHash(InternTable::Utf8String(utf16_length, utf8_data), hash); if (intern_it != intern_set.end()) {
mirror::String* string = intern_it->Read<kWithoutReadBarrier>();
DCHECK(string != nullptr);
DCHECK(!image_writer->IsInBootImage(string)); if (!image_writer->IsImageBinSlotAssigned(string)) {
Bin bin = AssignImageBinSlot(string, oat_index);
DCHECK_EQ(bin, kBinObjects ? Bin::kString : Bin::kRegular);
} else { // We have already seen this string in a previous dex file.
DCHECK(dex_file != image_writer->compiler_options_.GetDexFilesForOatFile().front());
}
}
}
}
if (com::android::art::flags::weak_const_string()) { // Collect interns without a `StringId` in any of the input dex files and // assign them to bin slots in the first image in a deterministic order. struct StringLess { booloperator()(mirror::String* lhs, mirror::String* rhs) const
REQUIRES_SHARED(Locks::mutator_lock_) { return lhs->CompareTo(rhs) < 0;
}
};
std::set<mirror::String*, StringLess> remaining_strings; for (const GcRoot<mirror::String>& root : intern_set) {
mirror::String* string = root.Read<kWithoutReadBarrier>();
DCHECK(string != nullptr);
DCHECK(!image_writer->IsInBootImage(string)); if (!image_writer->IsImageBinSlotAssigned(string)) {
DCHECK(remaining_strings.find(string) == remaining_strings.end());
remaining_strings.insert(string);
}
} for (mirror::String* string : remaining_strings) {
Bin bin = AssignImageBinSlot(string, /*oat_index=*/ 0u);
DCHECK_EQ(bin, kBinObjects ? Bin::kString : Bin::kRegular);
}
}
// String bins have been filled with dex file interns. Record their numbers in image infos.
DCHECK_EQ(bin_objects_.size(), image_writer_->image_infos_.size());
size_t total_dex_file_interns = 0u; for (size_t oat_index = 0, size = bin_objects_.size(); oat_index != size; ++oat_index) {
size_t num_dex_file_interns = bin_objects_[oat_index][enum_cast<size_t>(Bin::kString)].size();
ImageInfo& image_info = image_writer_->GetImageInfo(oat_index);
DCHECK_EQ(image_info.intern_table_size_, 0u);
image_info.intern_table_size_ = num_dex_file_interns;
total_dex_file_interns += num_dex_file_interns;
}
// Collect interns that do not have a corresponding StringId in any of the input dex files.
non_dex_file_interns_.reserve(intern_set.size() - total_dex_file_interns); for (const GcRoot<mirror::String>& root : intern_set) {
mirror::String* string = root.Read<kWithoutReadBarrier>(); if (!image_writer->IsImageBinSlotAssigned(string)) {
non_dex_file_interns_.push_back(string);
}
}
DCHECK_EQ(intern_set.size(), total_dex_file_interns + non_dex_file_interns_.size());
}
void ImageWriter::LayoutHelper::FinalizeInternTables() { // Remove interns that do not have a bin slot assigned. These correspond // to the DexCache locations excluded in VerifyImageBinSlotsAssigned().
ImageWriter* image_writer = image_writer_; auto retained_end = std::remove_if(
non_dex_file_interns_.begin(),
non_dex_file_interns_.end(),
[=](mirror::String* string) REQUIRES_SHARED(Locks::mutator_lock_) { return !image_writer->IsImageBinSlotAssigned(string);
});
non_dex_file_interns_.resize(std::distance(non_dex_file_interns_.begin(), retained_end));
// Sort `non_dex_file_interns_` based on oat index and bin offset.
ArrayRef<mirror::String*> non_dex_file_interns(non_dex_file_interns_);
std::sort(non_dex_file_interns.begin(),
non_dex_file_interns.end(),
[=](mirror::String* lhs, mirror::String* rhs) REQUIRES_SHARED(Locks::mutator_lock_) {
size_t lhs_oat_index = image_writer->GetOatIndex(lhs);
size_t rhs_oat_index = image_writer->GetOatIndex(rhs); if (lhs_oat_index != rhs_oat_index) { return lhs_oat_index < rhs_oat_index;
}
BinSlot lhs_bin_slot = image_writer->GetImageBinSlot(lhs, lhs_oat_index);
BinSlot rhs_bin_slot = image_writer->GetImageBinSlot(rhs, rhs_oat_index); return lhs_bin_slot < rhs_bin_slot;
});
// Allocate and fill intern tables.
size_t ndfi_index = 0u;
DCHECK_EQ(bin_objects_.size(), image_writer->image_infos_.size()); for (size_t oat_index = 0, size = bin_objects_.size(); oat_index != size; ++oat_index) { // Find the end of `non_dex_file_interns` for this oat file.
size_t ndfi_end = ndfi_index; while (ndfi_end != non_dex_file_interns.size() &&
image_writer->GetOatIndex(non_dex_file_interns[ndfi_end]) == oat_index) {
++ndfi_end;
}
// Calculate final intern table size.
ImageInfo& image_info = image_writer->GetImageInfo(oat_index);
DCHECK_EQ(image_info.intern_table_bytes_, 0u);
size_t num_dex_file_interns = image_info.intern_table_size_;
size_t num_non_dex_file_interns = ndfi_end - ndfi_index;
image_info.intern_table_size_ = num_dex_file_interns + num_non_dex_file_interns; if (image_info.intern_table_size_ != 0u) { // Make sure the intern table shall be full by allocating a buffer of the right size.
size_t buffer_size = static_cast<size_t>(
ceil(image_info.intern_table_size_ / kImageInternTableMaxLoadFactor));
image_info.intern_table_buffer_.reset(new GcRoot<mirror::String>[buffer_size]);
DCHECK(image_info.intern_table_buffer_ != nullptr);
image_info.intern_table_.emplace(kImageInternTableMinLoadFactor,
kImageInternTableMaxLoadFactor,
image_info.intern_table_buffer_.get(),
buffer_size);
// Fill the intern table. Dex file interns are at the start of the bin_objects[.][kString].
InternTable::UnorderedSet& table = *image_info.intern_table_; constauto& oat_file_strings = bin_objects_[oat_index][enum_cast<size_t>(Bin::kString)];
DCHECK_LE(num_dex_file_interns, oat_file_strings.size());
ArrayRef<mirror::Object* const> dex_file_interns(
oat_file_strings.data(), num_dex_file_interns); for (mirror::Object* string : dex_file_interns) { bool inserted = table.insert(GcRoot<mirror::String>(string->AsString())).second;
DCHECK(inserted) << "String already inserted: " << string->AsString()->ToModifiedUtf8();
}
ArrayRef<mirror::String*> current_non_dex_file_interns =
non_dex_file_interns.SubArray(ndfi_index, num_non_dex_file_interns); for (mirror::String* string : current_non_dex_file_interns) { bool inserted = table.insert(GcRoot<mirror::String>(string)).second;
DCHECK(inserted) << "String already inserted: " << string->ToModifiedUtf8();
}
// Record the intern table size in bytes.
image_info.intern_table_bytes_ = table.WriteToMemory(nullptr);
}
void ImageWriter::LayoutHelper::VerifyImageBinSlotsAssigned() {
dchecked_vector<mirror::Object*> carveout;
JavaVMExt* vm = nullptr; if (image_writer_->compiler_options_.IsAppImage()) { // Exclude boot class path dex caches that are not part of the boot image. // Also exclude their locations if they have not been visited through another path.
ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
Thread* self = Thread::Current();
vm = down_cast<JNIEnvExt*>(self->GetJniEnv())->GetVm();
ReaderMutexLock mu(self, *Locks::dex_lock_); for (constauto& entry : class_linker->GetDexCachesData()) { const ClassLinker::DexCacheData& data = entry.second; auto dex_cache = DecodeWeakGlobalWithoutRB<mirror::DexCache>(vm, self, data.weak_root); if (dex_cache == nullptr ||
image_writer_->IsInBootImage(dex_cache.Ptr()) ||
ContainsElement(image_writer_->compiler_options_.GetDexFilesForOatFile(),
dex_cache->GetDexFile())) { continue;
}
CHECK(!image_writer_->IsImageBinSlotAssigned(dex_cache.Ptr()));
carveout.push_back(dex_cache.Ptr());
ObjPtr<mirror::String> location = dex_cache->GetLocation<kVerifyNone, kWithoutReadBarrier>(); if (!image_writer_->IsImageBinSlotAssigned(location.Ptr())) {
carveout.push_back(location.Ptr());
}
}
}
dchecked_vector<mirror::Object*> missed_objects; auto ensure_bin_slots_assigned = [&](mirror::Object* obj)
REQUIRES_SHARED(Locks::mutator_lock_) { if (!image_writer_->IsInBootImage(obj)) { if (!UNLIKELY(image_writer_->IsImageBinSlotAssigned(obj))) { // Ignore the `carveout` objects. if (ContainsElement(carveout, obj)) { return;
} // Ignore finalizer references for the dalvik.system.DexFile objects referenced by // the app class loader.
ObjPtr<mirror::Class> klass = obj->GetClass<kVerifyNone, kWithoutReadBarrier>(); if (klass->IsFinalizerReferenceClass<kVerifyNone>()) {
ObjPtr<mirror::Class> reference_class =
klass->GetSuperClass<kVerifyNone, kWithoutReadBarrier>();
DCHECK(reference_class->DescriptorEquals("Ljava/lang/ref/Reference;"));
ArtField* ref_field = reference_class->FindDeclaredInstanceField( "referent", "Ljava/lang/Object;");
CHECK(ref_field != nullptr);
ObjPtr<mirror::Object> ref = ref_field->GetObject<kWithoutReadBarrier>(obj);
CHECK(ref != nullptr);
CHECK(image_writer_->IsImageBinSlotAssigned(ref.Ptr()));
ObjPtr<mirror::Class> ref_klass = ref->GetClass<kVerifyNone, kWithoutReadBarrier>();
CHECK(ref_klass == WellKnownClasses::dalvik_system_DexFile.Get<kWithoutReadBarrier>()); // Note: The app class loader is used only for checking against the runtime // class loader, the dex file cookie is cleared and therefore we do not need // to run the finalizer even if we implement app image objects collection.
ArtField* field = WellKnownClasses::dalvik_system_DexFile_cookie;
CHECK(field->GetObject<kWithoutReadBarrier>(ref) == nullptr); return;
} if (klass->IsStringClass()) { // Ignore interned strings. These may come from reflection interning method names. // TODO: Make dex file strings weak interns and GC them before writing the image. if (IsStronglyInternedString(obj->AsString())) { return;
}
}
missed_objects.push_back(obj);
}
}
};
Runtime::Current()->GetHeap()->VisitObjects(ensure_bin_slots_assigned); if (!missed_objects.empty()) { const gc::Verification* v = Runtime::Current()->GetHeap()->GetVerification();
size_t num_missed_objects = missed_objects.size();
size_t num_paths = std::min<size_t>(num_missed_objects, 5u); // Do not flood the output.
ArrayRef<mirror::Object*> missed_objects_head =
ArrayRef<mirror::Object*>(missed_objects).SubArray(/*pos=*/ 0u, /*length=*/ num_paths); for (mirror::Object* obj : missed_objects_head) {
LOG(ERROR) << "Image object without assigned bin slot: "
<< mirror::Object::PrettyTypeOf(obj) << " " << obj
<< " " << v->FirstPathFromRootSet(obj);
}
LOG(FATAL) << "Found " << num_missed_objects << " objects without assigned bin slots.";
}
}
void ImageWriter::LayoutHelper::FinalizeBinSlotOffsets() { // Calculate bin slot offsets and adjust for region padding if needed. const size_t region_size = image_writer_->region_size_; const size_t num_image_infos = image_writer_->image_infos_.size(); for (size_t oat_index = 0; oat_index != num_image_infos; ++oat_index) {
ImageInfo& image_info = image_writer_->image_infos_[oat_index];
size_t bin_offset = image_writer_->image_objects_offset_begin_;
for (size_t i = 0; i != kNumberOfBins; ++i) {
Bin bin = enum_cast<Bin>(i); switch (bin) { case Bin::kArtMethodClean: case Bin::kArtMethodDirty: {
bin_offset = RoundUp(bin_offset, ArtMethod::Alignment(image_writer_->target_ptr_size_)); break;
} case Bin::kImTable: case Bin::kIMTConflictTable: {
bin_offset = RoundUp(bin_offset, static_cast<size_t>(image_writer_->target_ptr_size_)); break;
} default: { // Normal alignment.
}
}
image_info.bin_slot_offsets_[i] = bin_offset;
// If the bin is for mirror objects, we may need to add region padding and update offsets. if (i < enum_cast<size_t>(Bin::kMirrorCount) && region_size != 0u) { const size_t offset_after_header = bin_offset - sizeof(ImageHeader);
size_t remaining_space =
RoundUp(offset_after_header + 1u, region_size) - offset_after_header; // Exercise the loop below in debug builds to get coverage. if (kIsDebugBuild || remaining_space < image_info.bin_slot_sizes_[i]) { // The bin crosses a region boundary. Add padding if needed.
size_t object_offset = 0u;
size_t padding = 0u; for (mirror::Object* object : bin_objects_[oat_index][i]) {
BinSlot bin_slot = image_writer_->GetImageBinSlot(object, oat_index);
DCHECK_EQ(enum_cast<size_t>(bin_slot.GetBin()), i);
DCHECK_EQ(bin_slot.GetOffset() + padding, object_offset);
size_t object_size = RoundUp(object->SizeOf<kVerifyNone>(), kObjectAlignment);
auto add_padding = [&](bool tail_region) {
DCHECK_NE(remaining_space, 0u);
DCHECK_LT(remaining_space, region_size);
DCHECK_ALIGNED(remaining_space, kObjectAlignment); // TODO When copying to heap regions, leave the tail region padding zero-filled. if (!tail_region || true) {
image_info.padding_offsets_.push_back(bin_offset + object_offset);
}
image_info.bin_slot_sizes_[i] += remaining_space;
padding += remaining_space;
object_offset += remaining_space;
remaining_space = region_size;
}; if (object_size > remaining_space) { // Padding needed if we're not at region boundary (with a multi-region object). if (remaining_space != region_size) { // TODO: Instead of adding padding, we should consider reordering the bins // or objects to reduce wasted space.
add_padding(/*tail_region=*/ false);
}
DCHECK_EQ(remaining_space, region_size); // For huge objects, adjust the remaining space to hold the object and some more. if (object_size > region_size) {
remaining_space = RoundUp(object_size + 1u, region_size);
}
} elseif (remaining_space == object_size) { // Move to the next region, no padding needed.
remaining_space += region_size;
}
DCHECK_GT(remaining_space, object_size);
remaining_space -= object_size;
image_writer_->UpdateImageBinSlotOffset(object, oat_index, object_offset);
object_offset += object_size; // Add padding to the tail region of huge objects if not region-aligned. if (object_size > region_size && remaining_space != region_size) {
DCHECK(!IsAlignedParam(object_size, region_size));
add_padding(/*tail_region=*/ true);
}
}
image_writer_->region_alignment_wasted_ += padding;
image_info.image_end_ += padding;
}
}
bin_offset += image_info.bin_slot_sizes_[i];
} // NOTE: There may be additional padding between the bin slots and the intern table.
DCHECK_EQ(
image_info.image_end_,
image_info.GetBinSizeSum(Bin::kMirrorCount) + image_writer_->image_objects_offset_begin_);
}
VLOG(image) << "Space wasted for region alignment " << image_writer_->region_alignment_wasted_;
}
// Check that we collected the same number of string references as we saw in the previous pass.
CHECK_EQ(image_info.string_reference_offsets_.size(), image_info.num_string_references_);
}
void ImageWriter::LayoutHelper::VisitReferences(ObjPtr<mirror::Object> obj, size_t oat_index) {
size_t old_work_queue_size = work_queue_.size();
VisitReferencesVisitor visitor(this, oat_index); // Walk references and assign bin slots for them.
obj->VisitReferences</*kVisitNativeRoots=*/ false, kVerifyNone, kWithoutReadBarrier>(
visitor,
visitor); // Put the added references in the queue in the order in which they were added. // The visitor just pushes them to the front as it visits them.
DCHECK_LE(old_work_queue_size, work_queue_.size());
size_t num_added = work_queue_.size() - old_work_queue_size;
std::reverse(work_queue_.begin(), work_queue_.begin() + num_added);
}
bool ImageWriter::LayoutHelper::TryAssignBinSlot(ObjPtr<mirror::Object> obj, size_t oat_index) { if (obj == nullptr || image_writer_->IsInBootImage(obj.Ptr())) { // Object is null or already in the image, there is no work to do. returnfalse;
} bool assigned = false; if (!image_writer_->IsImageBinSlotAssigned(obj.Ptr())) {
AssignImageBinSlot(obj.Ptr(), oat_index);
assigned = true;
} return assigned;
}
ImageWriter::Bin ImageWriter::LayoutHelper::AssignImageBinSlot(ObjPtr<mirror::Object> object,
size_t oat_index) {
DCHECK(object != nullptr);
Bin bin = image_writer_->GetImageBin(object.Ptr());
AssignImageBinSlot(object.Ptr(), oat_index, bin); return bin;
}
AssertOnly1Thread(); // Leave space for the header, but do not write it yet, we need to // know where image_roots is going to end up
image_objects_offset_begin_ = RoundUp(sizeof(ImageHeader), kObjectAlignment); // 64-bit-alignment
// Write the image runtime methods.
image_methods_[ImageHeader::kResolutionMethod] = runtime->GetResolutionMethod();
image_methods_[ImageHeader::kImtConflictMethod] = runtime->GetImtConflictMethod();
image_methods_[ImageHeader::kImtUnimplementedMethod] = runtime->GetImtUnimplementedMethod();
image_methods_[ImageHeader::kSaveAllCalleeSavesMethod] =
runtime->GetCalleeSaveMethod(CalleeSaveType::kSaveAllCalleeSaves);
image_methods_[ImageHeader::kSaveRefsOnlyMethod] =
runtime->GetCalleeSaveMethod(CalleeSaveType::kSaveRefsOnly);
image_methods_[ImageHeader::kSaveRefsAndArgsMethod] =
runtime->GetCalleeSaveMethod(CalleeSaveType::kSaveRefsAndArgs);
image_methods_[ImageHeader::kSaveEverythingMethod] =
runtime->GetCalleeSaveMethod(CalleeSaveType::kSaveEverything);
image_methods_[ImageHeader::kSaveEverythingMethodForClinit] =
runtime->GetCalleeSaveMethod(CalleeSaveType::kSaveEverythingForClinit);
image_methods_[ImageHeader::kSaveEverythingMethodForSuspendCheck] =
runtime->GetCalleeSaveMethod(CalleeSaveType::kSaveEverythingForSuspendCheck); // Visit image methods first to have the main runtime methods in the first image. for (auto* m : image_methods_) {
CHECK(m != nullptr);
CHECK(m->IsRuntimeMethod());
DCHECK_EQ(!compiler_options_.IsBootImage(), IsInBootImage(m))
<< "Trampolines should be in boot image"; if (!IsInBootImage(m)) {
AssignMethodOffset(m, NativeObjectRelocationType::kRuntimeMethod, GetDefaultOatIndex());
}
}
// Deflate monitors before we visit roots since deflating acquires the monitor lock. Acquiring // this lock while holding other locks may cause lock order violations.
{ auto deflate_monitor = // NO_THREAD_SAFETY_ANALYSIS: We don't really hold mutator_lock_ exclusively.
[](mirror::Object* obj) REQUIRES_SHARED(Locks::mutator_lock_)
NO_THREAD_SAFETY_ANALYSIS { Monitor::Deflate(Thread::Current(), obj); };
heap->VisitObjects(deflate_monitor); // This does not update the MonitorList, which is thus rendered invalid, and is no longer used.
}
// From this point on, there shall be no GC anymore and no objects shall be allocated. // We can now assign a BitSlot to each object and store it in its lockword.
JavaVMExt* vm = down_cast<JNIEnvExt*>(self->GetJniEnv())->GetVm(); if (compiler_options_.IsBootImage() || compiler_options_.IsBootImageExtension()) { // Record the address of boot image live objects. auto image_roots = DecodeGlobalWithoutRB<mirror::ObjectArray<mirror::Object>>(
vm, image_roots_[0]);
boot_image_live_objects_ = ObjPtr<ObjectArray<Object>>::DownCast(
image_roots->GetWithoutChecks<kVerifyNone, kWithoutReadBarrier>(
ImageHeader::kBootImageLiveObjects)).Ptr();
}
// If dirty_image_objects_ is present - try optimizing object layout. // Parse dirty-image-objects entries and put them in dirty_objects_ map, which is then used in // `AssignImageBinSlot` method to put the objects in dirty bin. if (compiler_options_.IsBootImage() && dirty_image_objects_ != nullptr) {
dirty_objects_ = MatchDirtyObjectPaths(*dirty_image_objects_);
LOG(INFO) << ART_FORMAT("Matched {} out of {} dirty-image-objects",
dirty_objects_.size(),
dirty_image_objects_->size());
}
// Sort objects in dirty bin. if (!dirty_objects_.empty()) { for (size_t oat_index = 0; oat_index < image_infos_.size(); ++oat_index) {
layout_helper.SortDirtyObjects(dirty_objects_, oat_index);
}
}
// Verify that all objects have assigned image bin slots.
layout_helper.VerifyImageBinSlotsAssigned();
// Finalize bin slot offsets. This may add padding for regions.
layout_helper.FinalizeBinSlotOffsets();
// Collect string reference info for app images. if (ClassLinker::kAppImageMayContainStrings && compiler_options_.IsAppImage()) {
layout_helper.CollectStringReferenceInfo();
}
// Calculate image offsets.
size_t image_offset = 0; for (ImageInfo& image_info : image_infos_) {
image_info.image_begin_ = global_image_begin_ + image_offset;
image_info.image_offset_ = image_offset;
image_info.image_size_ = RoundUp(image_info.CreateImageSections().first, kElfSegmentAlignment); // There should be no gaps until the next image.
image_offset += image_info.image_size_;
}
// Round up to the alignment the string table expects. See HashSet::WriteToMemory.
size_t cur_pos = RoundUp(sections[ImageHeader::kSectionJniStubMethods].End(), sizeof(uint64_t));
// Round up to the alignment of the offsets we are going to store.
cur_pos = RoundUp(class_table_section.End(), sizeof(uint32_t));
// The size of string_reference_offsets_ can't be used here because it hasn't // been filled with AppImageReferenceOffsetInfo objects yet. The // num_string_references_ value is calculated separately, before we can // compute the actual offsets. const ImageSection& string_reference_offsets =
sections[ImageHeader::kSectionStringReferenceOffsets] =
ImageSection(cur_pos, sizeof(string_reference_offsets_[0]) * num_string_references_);
/* *DexCachearrayssection
*/
// Round up to the alignment dex caches arrays expects.
cur_pos = RoundUp(sections[ImageHeader::kSectionStringReferenceOffsets].End(), sizeof(uint32_t)); // We don't generate dex cache arrays in an image generated by dex2oat.
sections[ImageHeader::kSectionDexCacheArrays] = ImageSection(cur_pos, 0u);
/* *Metadatasection.
*/
// Round up to the alignment of the offsets we are going to store.
cur_pos = RoundUp(string_reference_offsets.End(), sizeof(uint32_t));
// Return the number of bytes described by these sections, and the sections // themselves. return make_pair(metadata_section.End(), std::move(sections));
}
// Compute boot image checksums for the primary component, leave as 0 otherwise.
uint32_t boot_image_components = 0u;
uint32_t boot_image_checksums = 0u; if (oat_index == 0u) { const std::vector<gc::space::ImageSpace*>& image_spaces =
Runtime::Current()->GetHeap()->GetBootImageSpaces();
DCHECK_EQ(image_spaces.empty(), compiler_options_.IsBootImage()); for (size_t i = 0u, size = image_spaces.size(); i != size; ) { const ImageHeader& header = image_spaces[i]->GetImageHeader();
boot_image_components += header.GetComponentCount();
boot_image_checksums ^= header.GetImageChecksum();
DCHECK_LE(header.GetImageSpaceCount(), size - i);
i += header.GetImageSpaceCount();
}
}
// Create the image sections. auto section_info_pair = image_info.CreateImageSections(); const size_t image_end = section_info_pair.first;
dchecked_vector<ImageSection>& sections = section_info_pair.second;
// Finally bitmap section. const size_t bitmap_bytes = image_info.image_bitmap_.Size(); auto* bitmap_section = §ions[ImageHeader::kSectionImageBitmap]; // The offset of the bitmap section should be aligned to kElfSegmentAlignment to enable mapping // the section from file to memory. However the section size doesn't have to be rounded up as it // is located at the end of the file. When mapping file contents to memory, if the last page of // the mapping is only partially filled with data, the rest will be zero-filled.
*bitmap_section = ImageSection(RoundUp(image_end, kElfSegmentAlignment), bitmap_bytes); if (VLOG_IS_ON(compiler)) {
LOG(INFO) << "Creating header for " << oat_filenames_[oat_index];
size_t idx = 0; for (const ImageSection& section : sections) {
LOG(INFO) << static_cast<ImageHeader::ImageSections>(idx) << " " << section;
++idx;
}
LOG(INFO) << "Methods: clean=" << clean_methods_ << " dirty=" << dirty_methods_;
LOG(INFO) << "Image roots address=" << std::hex << image_info.image_roots_address_ << std::dec;
LOG(INFO) << "Image begin=" << std::hex << reinterpret_cast<uintptr_t>(global_image_begin_)
<< " Image offset=" << image_info.image_offset_ << std::dec;
LOG(INFO) << "Oat file begin=" << std::hex << reinterpret_cast<uintptr_t>(oat_file_begin)
<< " Oat data begin=" << reinterpret_cast<uintptr_t>(image_info.oat_data_begin_)
<< " Oat data end=" << reinterpret_cast<uintptr_t>(oat_data_end)
<< " Oat file end=" << reinterpret_cast<uintptr_t>(oat_file_end);
}
// Create the header, leave 0 for data size since we will fill this in as we are writing the // image. new (image_info.image_.Begin()) ImageHeader(
image_reservation_size,
current_component_count,
PointerToLowMemUInt32(image_info.image_begin_),
image_end,
sections.data(),
image_info.image_roots_address_,
image_info.oat_checksum_,
PointerToLowMemUInt32(oat_file_begin),
PointerToLowMemUInt32(image_info.oat_data_begin_),
PointerToLowMemUInt32(oat_data_end),
PointerToLowMemUInt32(oat_file_end),
boot_image_begin_,
boot_image_size_,
boot_image_components,
boot_image_checksums,
target_ptr_size_);
}
ArtMethod* ImageWriter::GetImageMethodAddress(ArtMethod* method) const {
NativeObjectRelocation relocation = GetNativeRelocation(method); const ImageInfo& image_info = GetImageInfo(relocation.oat_index);
CHECK_GE(relocation.offset, image_info.image_end_) << "ArtMethods should be after Objects"; returnreinterpret_cast<ArtMethod*>(image_info.image_begin_ + relocation.offset);
}
void VisitRoots(mirror::CompressedReference<mirror::Object>** roots,
size_t count,
[[maybe_unused]] const RootInfo& info) override
REQUIRES_SHARED(Locks::mutator_lock_) { for (size_t i = 0; i < count; ++i) { // Copy the reference. Since we do not have the address for recording the relocation, // it needs to be recorded explicitly by the user of FixupRootVisitor.
ObjPtr<mirror::Object> old_ptr = roots[i]->AsMirrorPtr();
roots[i]->Assign(image_writer_->GetImageAddress(old_ptr.Ptr()));
}
}
void ImageWriter::CopyAndFixupNativeData(size_t oat_index) { const ImageInfo& image_info = GetImageInfo(oat_index); // Copy ArtFields and methods to their locations and update the array for convenience. for (auto& pair : native_object_relocations_) {
NativeObjectRelocation& relocation = pair.second; // Only work with fields and methods that are in the current oat file. if (relocation.oat_index != oat_index) { continue;
} auto* dest = image_info.image_.Begin() + relocation.offset;
DCHECK_GE(dest, image_info.image_.Begin() + image_info.image_end_);
DCHECK(!IsInBootImage(pair.first)); switch (relocation.type) { case NativeObjectRelocationType::kRuntimeMethod: case NativeObjectRelocationType::kArtMethodClean: case NativeObjectRelocationType::kArtMethodDirty: {
CopyAndFixupMethod(reinterpret_cast<ArtMethod*>(pair.first), reinterpret_cast<ArtMethod*>(dest),
oat_index); break;
} case NativeObjectRelocationType::kArtFieldArray: { // Copy and fix up the entire field array. auto* src_array = reinterpret_cast<LengthPrefixedArray<ArtField>*>(pair.first); auto* dest_array = reinterpret_cast<LengthPrefixedArray<ArtField>*>(dest);
size_t size = src_array->size();
memcpy(dest_array, src_array, LengthPrefixedArray<ArtField>::ComputeSize(size)); for (size_t i = 0; i != size; ++i) {
CopyAndFixupReference(
dest_array->At(i).GetDeclaringClassAddressWithoutBarrier(),
src_array->At(i).GetDeclaringClass<kWithoutReadBarrier>());
} break;
} case NativeObjectRelocationType::kArtMethodArrayClean: case NativeObjectRelocationType::kArtMethodArrayDirty: { // For method arrays, copy just the header since the elements will // get copied by their corresponding relocations.
size_t size = ArtMethod::Size(target_ptr_size_);
size_t alignment = ArtMethod::Alignment(target_ptr_size_);
memcpy(dest, pair.first, LengthPrefixedArray<ArtMethod>::ComputeSize(0, size, alignment)); // Clear padding to avoid non-deterministic data in the image. // Historical note: We also did that to placate Valgrind. reinterpret_cast<LengthPrefixedArray<ArtMethod>*>(dest)->ClearPadding(size, alignment); break;
} case NativeObjectRelocationType::kIMTable: {
ImTable* orig_imt = reinterpret_cast<ImTable*>(pair.first);
ImTable* dest_imt = reinterpret_cast<ImTable*>(dest);
CopyAndFixupImTable(orig_imt, dest_imt); break;
} case NativeObjectRelocationType::kIMTConflictTable: { auto* orig_table = reinterpret_cast<ImtConflictTable*>(pair.first);
CopyAndFixupImtConflictTable(
orig_table, new(dest)ImtConflictTable(orig_table->NumEntries(target_ptr_size_), target_ptr_size_)); break;
} case NativeObjectRelocationType::kGcRootPointer: { auto* orig_pointer = reinterpret_cast<GcRoot<mirror::Object>*>(pair.first); auto* dest_pointer = reinterpret_cast<GcRoot<mirror::Object>*>(dest);
CopyAndFixupReference(dest_pointer->AddressWithoutBarrier(), orig_pointer->Read()); break;
}
}
} // Fixup the image method roots. auto* image_header = reinterpret_cast<ImageHeader*>(image_info.image_.Begin()); for (size_t i = 0; i < ImageHeader::kImageMethodsCount; ++i) {
ArtMethod* method = image_methods_[i];
CHECK(method != nullptr);
CopyAndFixupPointer( reinterpret_cast<void**>(&image_header->image_methods_[i]), method, PointerSize::k32);
}
FixupRootVisitor root_visitor(this);
// Write the intern table into the image. if (image_info.intern_table_bytes_ > 0) { const ImageSection& intern_table_section = image_header->GetInternedStringsSection();
DCHECK(image_info.intern_table_.has_value()); const InternTable::UnorderedSet& intern_table = *image_info.intern_table_;
uint8_t* const intern_table_memory_ptr =
image_info.image_.Begin() + intern_table_section.Offset(); const size_t intern_table_bytes = intern_table.WriteToMemory(intern_table_memory_ptr);
CHECK_EQ(intern_table_bytes, image_info.intern_table_bytes_); // Fixup the pointers in the newly written intern table to contain image addresses.
InternTable temp_intern_table; // Note that we require that ReadFromMemory does not make an internal copy of the elements so // that the VisitRoots() will update the memory directly rather than the copies. // This also relies on visit roots not doing any verification which could fail after we update // the roots to be the image addresses.
temp_intern_table.AddTableFromMemory(intern_table_memory_ptr,
VoidFunctor(), /*is_boot_image=*/ false);
CHECK_EQ(temp_intern_table.Size(), intern_table.size());
temp_intern_table.VisitRoots(&root_visitor, kVisitRootFlagAllRoots);
if (kIsDebugBuild) {
MutexLock lock(Thread::Current(), *Locks::intern_table_lock_);
CHECK(!temp_intern_table.strong_interns_.tables_.empty()); // The UnorderedSet was inserted at the beginning.
CHECK_EQ(temp_intern_table.strong_interns_.tables_[0].Size(), intern_table.size());
}
}
// Write the class table(s) into the image. class_table_bytes_ may be 0 if there are multiple // class loaders. Writing multiple class tables into the image is currently unsupported. if (image_info.class_table_bytes_ > 0u) { const ImageSection& class_table_section = image_header->GetClassTableSection();
uint8_t* const class_table_memory_ptr =
image_info.image_.Begin() + class_table_section.Offset();
// Fixup the pointers in the newly written class table to contain image addresses. See // above comment for intern tables.
ClassTable temp_class_table;
temp_class_table.ReadFromMemory(class_table_memory_ptr);
CHECK_EQ(temp_class_table.NumReferencedZygoteClasses(), table.size());
UnbufferedRootVisitor visitor(&root_visitor, RootInfo(kRootUnknown));
temp_class_table.VisitRoots(visitor);
if (kIsDebugBuild) {
ReaderMutexLock lock(Thread::Current(), temp_class_table.lock_);
CHECK(!temp_class_table.classes_.empty()); // The ClassSet was inserted at the beginning.
CHECK_EQ(temp_class_table.classes_[0].size(), table.size());
}
}
}
void ImageWriter::CopyAndFixupJniStubMethods(size_t oat_index) { const ImageInfo& image_info = GetImageInfo(oat_index); // Copy method's address to JniStubMethods section. for (auto& pair : jni_stub_map_) {
JniStubMethodRelocation& relocation = pair.second.second; // Only work with JNI stubs that are in the current oat file. if (relocation.oat_index != oat_index) { continue;
} void** address = reinterpret_cast<void**>(image_info.image_.Begin() + relocation.offset);
ArtMethod* method = pair.second.first;
CopyAndFixupPointer(address, method);
}
}
void ImageWriter::CopyAndFixupMethodPointerArray(mirror::PointerArray* arr) { // Pointer arrays are processed early and each is visited just once. // Therefore we know that this array has not been copied yet.
mirror::Object* dst = CopyObject</*kCheckIfDone=*/ false>(arr);
DCHECK(dst != nullptr);
DCHECK(arr->IsIntArray() || arr->IsLongArray())
<< arr->GetClass<kVerifyNone, kWithoutReadBarrier>()->PrettyClass() << " " << arr; // Fixup int and long pointers for the ArtMethod or ArtField arrays. const size_t num_elements = arr->GetLength();
CopyAndFixupReference(dst->GetFieldObjectReferenceAddr<kVerifyNone>(Class::ClassOffset()),
arr->GetClass<kVerifyNone, kWithoutReadBarrier>()); auto* dest_array = down_cast<mirror::PointerArray*>(dst); for (size_t i = 0, count = num_elements; i < count; ++i) { void* elem = arr->GetElementPtrSize<void*>(i, target_ptr_size_); if (kIsDebugBuild && elem != nullptr && !IsInBootImage(elem)) { auto it = native_object_relocations_.find(elem); if (UNLIKELY(it == native_object_relocations_.end())) { auto* method = reinterpret_cast<ArtMethod*>(elem);
LOG(FATAL) << "No relocation entry for ArtMethod " << method->PrettyMethod() << " @ "
<< method << " idx=" << i << "/" << num_elements << " with declaring class "
<< Class::PrettyClass(method->GetDeclaringClass<kWithoutReadBarrier>());
UNREACHABLE();
}
}
CopyAndFixupPointer(dest_array->ElementAddress(i, target_ptr_size_), elem);
}
}
void ImageWriter::CopyAndFixupObject(Object* obj) { if (!IsImageBinSlotAssigned(obj)) { return;
} // Some objects (such as method pointer arrays) may have been processed before.
mirror::Object* dst = CopyObject</*kCheckIfDone=*/ true>(obj); if (dst != nullptr) {
FixupObject(obj, dst);
}
}
bool done = image_info.image_bitmap_.Set(dst); // Mark the obj as live. // Check if the object was already copied, unless the caller indicated that it was not. if (kCheckIfDone && done) { return nullptr;
}
DCHECK(!done);
const size_t n = obj->SizeOf();
if (kIsDebugBuild && region_size_ != 0u) { const size_t offset_after_header = offset - sizeof(ImageHeader); const size_t next_region = RoundUp(offset_after_header, region_size_); if (offset_after_header != next_region) { // If the object is not on a region bondary, it must not be cross region.
CHECK_LT(offset_after_header, next_region)
<< "offset_after_header=" << offset_after_header << " size=" << n;
CHECK_LE(offset_after_header + n, next_region)
<< "offset_after_header=" << offset_after_header << " size=" << n;
}
}
DCHECK_LE(offset + n, image_info.image_.Size());
memcpy(dst, src, n);
// Write in a hash code of objects which have inflated monitors or a hash code in their monitor // word. constauto it = saved_hashcode_map_.find(obj);
dst->SetLockWord(it != saved_hashcode_map_.end() ?
LockWord::FromHashCode(it->second, 0u) : LockWord::Default(), false); if (kUseBakerReadBarrier && gc::collector::ConcurrentCopying::kGrayDirtyImmuneObjects) { // Treat all of the objects in the image as marked to avoid unnecessary dirty pages. This is // safe since we mark all of the objects that may reference non immune objects as gray.
CHECK(dst->AtomicSetMarkBit(0, 1));
} return dst;
}
// Rewrite all the references in the copied object to point to their image address equivalent class ImageWriter::FixupVisitor { public:
FixupVisitor(ImageWriter* image_writer, Object* copy)
: image_writer_(image_writer), copy_(copy) {
}
// We do not visit native roots. These are handled with other logic. void VisitRootIfNonNull(
[[maybe_unused]] mirror::CompressedReference<mirror::Object>* root) const {
LOG(FATAL) << "UNREACHABLE";
} void VisitRoot([[maybe_unused]] mirror::CompressedReference<mirror::Object>* root) const {
LOG(FATAL) << "UNREACHABLE";
}
voidoperator()(ObjPtr<Object> obj, MemberOffset offset, [[maybe_unused]] bool is_static) const
REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(Locks::heap_bitmap_lock_) {
ObjPtr<Object> ref = obj->GetFieldObject<Object, kVerifyNone, kWithoutReadBarrier>(offset); // Copy the reference and record the fixup if necessary.
image_writer_->CopyAndFixupReference(
copy_->GetFieldObjectReferenceAddr<kVerifyNone>(offset), ref);
}
void ImageWriter::CopyAndFixupObjects() { // Copy and fix up pointer arrays first as they require special treatment. auto method_pointer_array_visitor =
[&](ObjPtr<mirror::PointerArray> pointer_array) REQUIRES_SHARED(Locks::mutator_lock_) {
CopyAndFixupMethodPointerArray(pointer_array.Ptr());
}; for (ImageInfo& image_info : image_infos_) { if (image_info.class_table_size_ != 0u) {
DCHECK(image_info.class_table_.has_value()); for (const ClassTable::TableSlot& slot : *image_info.class_table_) {
ObjPtr<mirror::Class> klass = slot.Read<kWithoutReadBarrier>();
DCHECK(klass != nullptr); // Do not process boot image classes present in app image class table.
DCHECK(!IsInBootImage(klass.Ptr()) || compiler_options_.IsAppImage()); if (!IsInBootImage(klass.Ptr())) { // Do not fix up method pointer arrays inherited from superclass. If they are part // of the current image, they were or shall be copied when visiting the superclass.
VisitNewMethodPointerArrays(klass, method_pointer_array_visitor);
}
}
}
}
ArtField* ImageWriter::NativeLocationInImage(ArtField* src_field) { // Fields are not individually stored in the native relocation map. Use the field array.
ObjPtr<mirror::Class> declaring_class = src_field->GetDeclaringClass<kWithoutReadBarrier>();
LengthPrefixedArray<ArtField>* src_fields = declaring_class->GetFieldsPtr();
DCHECK(src_fields != nullptr);
LengthPrefixedArray<ArtField>* dst_fields = NativeLocationInImage(src_fields);
DCHECK(dst_fields != nullptr);
size_t field_offset = reinterpret_cast<uint8_t*>(src_field) - reinterpret_cast<uint8_t*>(src_fields); returnreinterpret_cast<ArtField*>(reinterpret_cast<uint8_t*>(dst_fields) + field_offset);
}
class ImageWriter::NativeLocationVisitor { public: explicit NativeLocationVisitor(ImageWriter* image_writer)
: image_writer_(image_writer) {}
template <typename T>
T* operator()(T* ptr, void** dest_addr) const REQUIRES_SHARED(Locks::mutator_lock_) { if (ptr != nullptr) {
image_writer_->CopyAndFixupPointer(dest_addr, ptr);
} // TODO: The caller shall overwrite the value stored by CopyAndFixupPointer() // with the value we return here. We should try to avoid the duplicate work. return image_writer_->NativeLocationInImage(ptr);
}
if (kBitstringSubtypeCheckEnabled && !compiler_options_.IsBootImage()) { // When we call SubtypeCheck::EnsureInitialize, it Assigns new bitstring // values to the parent of that class. // // Every time this happens, the parent class has to mutate to increment // the "Next" value. // // If any of these parents are in the boot image, the changes [in the parents] // would be lost when the app image is reloaded. // // To prevent newly loaded classes (not in the app image) from being reassigned // the same bitstring value as an existing app image class, uninitialize // all the classes in the app image. // // On startup, the class linker will then re-initialize all the app // image bitstrings. See also ClassLinker::AddImageSpace. // // FIXME: Deal with boot image extensions.
MutexLock subtype_check_lock(Thread::Current(), *Locks::subtype_check_lock_); // Lock every time to prevent a dcheck failure when we suspend with the lock held.
SubtypeCheck<mirror::Class*>::ForceUninitialize(copy);
}
// A tid in clinit_thread_id_or_hash_ would violate image determinism.
copy->FixThreadId(orig);
// We never emit kRetryVerificationAtRuntime, instead we mark the class as // resolved and the class will therefore be re-verified at runtime. if (orig->ShouldVerifyAtRuntime()) {
copy->SetStatusInternal(ClassStatus::kResolved);
}
}
void ImageWriter::FixupObject(Object* orig, Object* copy) {
DCHECK(orig != nullptr);
DCHECK(copy != nullptr); if (kUseBakerReadBarrier) {
orig->AssertReadBarrierState();
}
ObjPtr<mirror::Class> klass = orig->GetClass<kVerifyNone, kWithoutReadBarrier>(); if (klass->IsClassClass()) {
FixupClass(orig->AsClass<kVerifyNone>().Ptr(), down_cast<mirror::Class*>(copy));
} else {
ObjPtr<mirror::ObjectArray<mirror::Class>> class_roots =
Runtime::Current()->GetClassLinker()->GetClassRoots<kWithoutReadBarrier>(); if (klass == GetClassRoot<mirror::String, kWithoutReadBarrier>(class_roots)) { // Make sure all image strings have the hash code calculated, even if they are not interned.
down_cast<mirror::String*>(copy)->GetHashCode();
} elseif (klass == GetClassRoot<mirror::Method, kWithoutReadBarrier>(class_roots) ||
klass == GetClassRoot<mirror::Constructor, kWithoutReadBarrier>(class_roots)) { // Need to update the ArtMethod. auto* dest = down_cast<mirror::Executable*>(copy); auto* src = down_cast<mirror::Executable*>(orig);
ArtMethod* src_method = src->GetArtMethod(); if (src_method != nullptr) {
CopyAndFixupPointer(dest, mirror::Executable::ArtMethodOffset(), src_method);
}
} elseif (klass == GetClassRoot<mirror::FieldVarHandle, kWithoutReadBarrier>(class_roots) ||
klass == GetClassRoot<mirror::StaticFieldVarHandle, kWithoutReadBarrier>(class_roots)) { // Need to update the ArtField. auto* dest = down_cast<mirror::FieldVarHandle*>(copy); auto* src = down_cast<mirror::FieldVarHandle*>(orig);
ArtField* src_field = src->GetArtField();
CopyAndFixupPointer(dest, mirror::FieldVarHandle::ArtFieldOffset(), src_field);
} elseif (klass == GetClassRoot<mirror::DexCache, kWithoutReadBarrier>(class_roots)) {
down_cast<mirror::DexCache*>(copy)->SetDexFile(nullptr);
down_cast<mirror::DexCache*>(copy)->ResetNativeArrays();
} elseif (klass->IsClassLoaderClass()) {
mirror::ClassLoader* copy_loader = down_cast<mirror::ClassLoader*>(copy); // If src is a ClassLoader, set the class table to null so that it gets recreated by the // ClassLinker.
copy_loader->SetClassTable(nullptr); // Also set allocator to null to be safe. The allocator is created when we create the class // table. We also never expect to unload things in the image since they are held live as // roots.
copy_loader->SetAllocator(nullptr);
}
FixupVisitor visitor(this, copy);
orig->VisitReferences</*kVisitNativeRoots=*/ false, kVerifyNone, kWithoutReadBarrier>(
visitor, visitor);
}
}
const uint8_t* ImageWriter::GetOatAddress(StubType type) const {
DCHECK_LE(type, StubType::kLast); // If we are compiling a boot image extension or app image, // we need to use the stubs of the primary boot image. if (!compiler_options_.IsBootImage()) { // Use the current image pointers. const std::vector<gc::space::ImageSpace*>& image_spaces =
Runtime::Current()->GetHeap()->GetBootImageSpaces();
DCHECK(!image_spaces.empty()); const OatFile* oat_file = image_spaces[0]->GetOatFile();
CHECK(oat_file != nullptr); const OatHeader& header = oat_file->GetOatHeader(); return header.GetOatAddress(type);
} const ImageInfo& primary_image_info = GetImageInfo(0); return GetOatAddressForOffset(primary_image_info.GetStubOffset(type), primary_image_info);
}
if (UNLIKELY(IsInBootImage(method->GetDeclaringClass<kWithoutReadBarrier>().Ptr()))) {
DCHECK(method->IsCopied()); // If the code is not in the oat file corresponding to this image (e.g. default methods)
quick_code = reinterpret_cast<const uint8_t*>(quick_oat_entry_point);
} else {
uint32_t quick_oat_code_offset = PointerToLowMemUInt32(quick_oat_entry_point);
quick_code = GetOatAddressForOffset(quick_oat_code_offset, image_info);
}
if (quick_code == nullptr) { // If we don't have code, use generic jni / interpreter. if (method->IsNative()) { // The generic JNI trampolines performs class initialization check if needed.
quick_code = GetOatAddress(StubType::kQuickGenericJNITrampoline);
} elseif (CanMethodUseNterp(method, compiler_options_.GetInstructionSet())) { // The nterp trampoline doesn't do initialization checks, so install the // resolution stub if needed. if (still_needs_clinit_check) {
quick_code = GetOatAddress(StubType::kQuickResolutionTrampoline);
} else {
quick_code = GetOatAddress(StubType::kNterpTrampoline);
}
} else { // The interpreter brige performs class initialization check if needed.
quick_code = GetOatAddress(StubType::kQuickToInterpreterBridge);
}
} elseif (still_needs_clinit_check && !compiler_options_.ShouldCompileWithClinitCheck(method)) { // If we do have code but the method needs a class initialization check before calling // that code, install the resolution stub that will perform the check.
quick_code = GetOatAddress(StubType::kQuickResolutionTrampoline);
} return quick_code;
}
staticinline uint32_t ResetNterpFastPathFlags(
uint32_t access_flags, ArtMethod* orig, InstructionSet isa)
REQUIRES_SHARED(Locks::mutator_lock_) {
DCHECK(orig != nullptr);
DCHECK(!orig->IsProxyMethod()); // `UnstartedRuntime` does not support creating proxy classes.
DCHECK(!orig->IsRuntimeMethod());
// Clear old nterp fast path flags.
access_flags = ArtMethod::ClearNterpFastPathFlags(access_flags);
// Check if nterp fast paths are available on the target ISA.
std::string_view shorty = orig->GetShortyView(); // Use orig, copy's class not yet ready.
uint32_t new_nterp_flags = GetNterpFastPathFlags(shorty, access_flags, isa);
// Add the new nterp fast path flags, if any. return access_flags | new_nterp_flags;
}
// OatWriter replaces the code_ with an offset value. Here we re-adjust to a pointer relative to // oat_begin_
// The resolution method has a special trampoline to call.
Runtime* runtime = Runtime::Current(); constvoid* quick_code; if (orig->IsRuntimeMethod()) {
ImtConflictTable* orig_table = orig->GetImtConflictTable(target_ptr_size_); if (orig_table != nullptr) { // Special IMT conflict method, normal IMT conflict method or unimplemented IMT method.
quick_code = GetOatAddress(StubType::kQuickIMTConflictTrampoline);
CopyAndFixupPointer(copy, ArtMethod::DataOffset(target_ptr_size_), orig_table);
} elseif (UNLIKELY(orig == runtime->GetResolutionMethod())) {
quick_code = GetOatAddress(StubType::kQuickResolutionTrampoline); // Set JNI entrypoint for resolving @CriticalNative methods called from compiled code . constvoid* jni_code = GetOatAddress(StubType::kJNIDlsymLookupCriticalTrampoline);
copy->SetEntryPointFromJniPtrSize(jni_code, target_ptr_size_);
} else { bool found_one = false; for (size_t i = 0; i < static_cast<size_t>(CalleeSaveType::kLastCalleeSaveType); ++i) { auto idx = static_cast<CalleeSaveType>(i); if (runtime->HasCalleeSaveMethod(idx) && runtime->GetCalleeSaveMethod(idx) == orig) {
found_one = true; break;
}
}
CHECK(found_one) << "Expected to find callee save method but got " << orig->PrettyMethod();
CHECK(copy->IsRuntimeMethod());
CHECK(copy->GetEntryPointFromQuickCompiledCodePtrSize(target_ptr_size_) == nullptr);
quick_code = nullptr;
}
} else { // We assume all methods have code. If they don't currently then we set them to the use the // resolution trampoline. Abstract methods never have code and so we need to make sure their // use results in an AbstractMethodError. We use the interpreter to achieve this. if (UNLIKELY(!orig->IsInvokable())) {
quick_code = GetOatAddress(StubType::kQuickToInterpreterBridge);
} else { const ImageInfo& image_info = image_infos_[oat_index];
quick_code = GetQuickCode(orig, image_info);
// JNI entrypoint: if (orig->IsNative()) { // Find boot JNI stub for those methods that skipped AOT compilation and don't need // clinit check. bool still_needs_clinit_check = orig->StillNeedsClinitCheck<kWithoutReadBarrier>(); if (!still_needs_clinit_check &&
!compiler_options_.IsBootImage() &&
quick_code == GetOatAddress(StubType::kQuickGenericJNITrampoline)) {
ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); constvoid* boot_jni_stub = class_linker->FindBootJniStub(orig); if (boot_jni_stub != nullptr) {
quick_code = boot_jni_stub;
}
} // The native method's pointer is set to a stub to lookup via dlsym. // Note this is not the code_ pointer, that is handled above.
StubType stub_type = orig->IsCriticalNative() ? StubType::kJNIDlsymLookupCriticalTrampoline
: StubType::kJNIDlsymLookupTrampoline;
copy->SetEntryPointFromJniPtrSize(GetOatAddress(stub_type), target_ptr_size_);
} elseif (!orig->HasCodeItem()) {
CHECK(copy->GetDataPtrSize(target_ptr_size_) == nullptr);
} else {
CHECK(copy->GetDataPtrSize(target_ptr_size_) != nullptr);
}
}
} if (quick_code != nullptr) {
copy->SetEntryPointFromQuickCompiledCodePtrSize(quick_code, target_ptr_size_);
}
}
ImageWriter::BinSlot::BinSlot(uint32_t lockword) : lockword_(lockword) { // These values may need to get updated if more bins are added to the enum Bin
static_assert(kBinBits == 3, "wrong number of bin bits");
static_assert(kBinShift == 27, "wrong number of shift");
static_assert(sizeof(BinSlot) == sizeof(LockWord), "BinSlot/LockWord must have equal sizes");
ImageWriter::Bin ImageWriter::BinTypeForNativeRelocationType(NativeObjectRelocationType type) { switch (type) { case NativeObjectRelocationType::kArtFieldArray: return Bin::kArtField; case NativeObjectRelocationType::kArtMethodClean: case NativeObjectRelocationType::kArtMethodArrayClean: return Bin::kArtMethodClean; case NativeObjectRelocationType::kArtMethodDirty: case NativeObjectRelocationType::kArtMethodArrayDirty: return Bin::kArtMethodDirty; case NativeObjectRelocationType::kRuntimeMethod: return Bin::kRuntimeMethod; case NativeObjectRelocationType::kIMTable: return Bin::kImTable; case NativeObjectRelocationType::kIMTConflictTable: return Bin::kIMTConflictTable; case NativeObjectRelocationType::kGcRootPointer: return Bin::kMetadata;
}
}
size_t ImageWriter::GetOatIndex(mirror::Object* obj) const { if (!IsMultiImage()) {
DCHECK(oat_index_map_.empty()); return GetDefaultOatIndex();
} auto it = oat_index_map_.find(obj);
DCHECK(it != oat_index_map_.end()) << obj; return it->second;
}
size_t ImageWriter::GetOatIndexForDexFile(const DexFile* dex_file) const { if (!IsMultiImage()) { return GetDefaultOatIndex();
} auto it = dex_file_oat_index_map_.find(dex_file);
DCHECK(it != dex_file_oat_index_map_.end()) << dex_file->GetLocation(); return it->second;
}
if (compiler_options_.IsAppImage()) {
CHECK_EQ(oat_filenames_.size(), 1u) << "App image should have no next image."; return;
}
// Update the oat_offset of the next image info. if (oat_index + 1u != oat_filenames_.size()) { // There is a following one.
ImageInfo& next_image_info = GetImageInfo(oat_index + 1u);
next_image_info.oat_offset_ = cur_image_info.oat_offset_ + oat_loaded_size;
}
}
ImageWriter::ImageWriter(const CompilerOptions& compiler_options,
uintptr_t image_begin,
ImageHeader::StorageMode image_storage_mode, const std::vector<std::string>& oat_filenames, const HashMap<const DexFile*, size_t>& dex_file_oat_index_map,
jobject class_loader, const std::vector<std::string>* dirty_image_objects)
: compiler_options_(compiler_options),
target_ptr_size_(InstructionSetPointerSize(compiler_options.GetInstructionSet())), // If we're compiling a boot image and we have a profile, set methods as being shared // memory (to avoid dirtying them with hotness counter). We expect important methods // to be AOT, and non-important methods to be run in the interpreter.
mark_memory_shared_methods_(
CompilerFilter::DependsOnProfile(compiler_options_.GetCompilerFilter()) &&
(compiler_options_.IsBootImage() || compiler_options_.IsBootImageExtension())),
boot_image_begin_(Runtime::Current()->GetHeap()->GetBootImagesStartAddress()),
boot_image_size_(Runtime::Current()->GetHeap()->GetBootImagesSize()),
global_image_begin_(reinterpret_cast<uint8_t*>(image_begin)),
image_objects_offset_begin_(0),
image_infos_(oat_filenames.size()),
jni_stub_map_(JniStubKeyHash(compiler_options.GetInstructionSet()),
JniStubKeyEquals(compiler_options.GetInstructionSet())),
dirty_methods_(0u),
clean_methods_(0u),
app_class_loader_(class_loader),
boot_image_live_objects_(nullptr),
image_roots_(),
image_storage_mode_(image_storage_mode),
oat_filenames_(oat_filenames),
dex_file_oat_index_map_(dex_file_oat_index_map),
dirty_image_objects_(dirty_image_objects) {
DCHECK(compiler_options.IsBootImage() ||
compiler_options.IsBootImageExtension() ||
compiler_options.IsAppImage());
DCHECK_EQ(compiler_options.IsBootImage(), boot_image_begin_ == 0u);
DCHECK_EQ(compiler_options.IsBootImage(), boot_image_size_ == 0u);
CHECK_NE(image_begin, 0U);
std::fill_n(image_methods_, arraysize(image_methods_), nullptr);
CHECK_EQ(compiler_options.IsBootImage(),
Runtime::Current()->GetHeap()->GetBootImageSpaces().empty())
<< "Compiling a boot image should occur iff there are no boot image spaces loaded"; if (compiler_options_.IsAppImage()) { // Make sure objects are not crossing region boundaries for app images.
region_size_ = gc::space::RegionSpace::kRegionSize;
}
}
ImageWriter::~ImageWriter() { if (!image_roots_.empty()) {
Thread* self = Thread::Current();
JavaVMExt* vm = down_cast<JNIEnvExt*>(self->GetJniEnv())->GetVm(); for (jobject image_roots : image_roots_) {
vm->DeleteGlobalRef(self, image_roots);
}
}
}
¤ 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.0.75Bemerkung:
(Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können 2026-06-29)
¤
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.