/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "runtime_image.h"

#include <lz4.h>
#include <unistd.h>

#include "android-base/file.h"
#include "android-base/stringprintf.h"
#include "android-base/strings.h"
#include "arch/instruction_set.h"
#include "arch/instruction_set_features.h"
#include "base/arena_allocator.h"
#include "base/arena_containers.h"
#include "base/bit_utils.h"
#include "base/file_utils.h"
#include "base/length_prefixed_array.h"
#include "base/scoped_flock.h"
#include "base/stl_util.h"
#include "base/systrace.h"
#include "base/unix_file/fd_file.h"
#include "base/utils.h"
#include "class_loader_context.h"
#include "class_loader_utils.h"
#include "class_root-inl.h"
#include "dex/class_accessor-inl.h"
#include "gc/space/image_space.h"
#include "mirror/object-inl.h"
#include "mirror/object-refvisitor-inl.h"
#include "mirror/object_array-alloc-inl.h"
#include "mirror/object_array-inl.h"
#include "mirror/object_array.h"
#include "mirror/string-inl.h"
#include "nterp_helpers.h"
#include "oat/image.h"
#include "oat/oat.h"
#include "profile/profile_compilation_info.h"
#include "scoped_thread_state_change-inl.h"
#include "vdex_file.h"

namespace art HIDDEN {

using android::base::StringPrintf;

/**
 * The native data structures that we store in the image.
 */
enum class NativeRelocationKind {
  kArtFieldArray,
  kArtMethodArray,
  kArtMethod,
  kImTable,
  // For dex cache arrays which can stay in memory even after startup. Those are
  // dex cache arrays whose size is below a given threshold, defined by
  // DexCache::ShouldAllocateFullArray.
  kFullNativeDexCacheArray,
  // For dex cache arrays which we will want to release after app startup.
  kStartupNativeDexCacheArray,
};

/**
 * Helper class to generate an app image at runtime.
 */
class RuntimeImageHelper {
 public:
  explicit RuntimeImageHelper(gc::Heap* heap) :
    allocator_(Runtime::Current()->GetArenaPool()),
    objects_(allocator_.Adapter()),
    art_fields_(allocator_.Adapter()),
    art_methods_(allocator_.Adapter()),
    im_tables_(allocator_.Adapter()),
    metadata_(allocator_.Adapter()),
    dex_cache_arrays_(allocator_.Adapter()),
    string_reference_offsets_(allocator_.Adapter()),
    sections_(ImageHeader::kSectionCount, allocator_.Adapter()),
    object_offsets_(allocator_.Adapter()),
    classes_(allocator_.Adapter()),
    array_classes_(allocator_.Adapter()),
    dex_caches_(allocator_.Adapter()),
    class_hashes_(allocator_.Adapter()),
    native_relocations_(allocator_.Adapter()),
    boot_image_begin_(heap->GetBootImagesStartAddress()),
    boot_image_size_(heap->GetBootImagesSize()),
    image_begin_(boot_image_begin_ + boot_image_size_),
    // Note: image relocation considers the image header in the bitmap.
    object_section_size_(sizeof(ImageHeader)),
    intern_table_(InternStringHash(this), InternStringEquals(this)),
    class_table_(ClassDescriptorHash(this), ClassDescriptorEquals()) {}

  bool Generate(std::string* error_msg) {
    if (!WriteObjects(error_msg)) {
      return false;
    }

    // Generate the sections information stored in the header.
    CreateImageSections();

    // Now that all sections have been created and we know their offset and
    // size, relocate native pointers inside classes and ImTables.
    RelocateNativePointers();

    // Generate the bitmap section, stored kElfSegmentAlignment-aligned after the sections data and
    // of size `object_section_size_` rounded up to kCardSize to match the bitmap size expected by
    // Loader::Init at art::gc::space::ImageSpace.
    size_t sections_end = sections_[ImageHeader::kSectionMetadata].End();
    image_bitmap_ = gc::accounting::ContinuousSpaceBitmap::Create(
        "image bitmap",
        reinterpret_cast<uint8_t*>(image_begin_),
        RoundUp(object_section_size_, gc::accounting::CardTable::kCardSize));
    for (uint32_t offset : object_offsets_) {
      DCHECK(IsAligned<kObjectAlignment>(image_begin_ + sizeof(ImageHeader) + offset));
      image_bitmap_.Set(
          reinterpret_cast<mirror::Object*>(image_begin_ + sizeof(ImageHeader) + offset));
    }
    const size_t bitmap_bytes = image_bitmap_.Size();
    auto* bitmap_section = &sections_[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(sections_end, kElfSegmentAlignment), bitmap_bytes);

    // Compute boot image checksum and boot image components, to be stored in
    // the header.
    gc::Heap* const heap = Runtime::Current()->GetHeap();
    uint32_t boot_image_components = 0u;
    uint32_t boot_image_checksums = 0u;
    const std::vector<gc::space::ImageSpace*>& image_spaces = heap->GetBootImageSpaces();
    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();
    }

    header_ = ImageHeader(
        /* image_reservation_size= */ RoundUp(sections_end, kElfSegmentAlignment),
        /* component_count= */ 1,
        image_begin_,
        sections_end,
        sections_.data(),
        /* image_roots= */ image_begin_ + sizeof(ImageHeader),
        /* oat_checksum= */ 0,
        /* oat_file_begin= */ 0,
        /* oat_data_begin= */ 0,
        /* oat_data_end= */ 0,
        /* oat_file_end= */ 0,
        heap->GetBootImagesStartAddress(),
        heap->GetBootImagesSize(),
        boot_image_components,
        boot_image_checksums,
        kRuntimePointerSize);

    // Data size includes everything except the bitmap and the header.
    header_.data_size_ = sections_end - sizeof(ImageHeader);

    // Write image methods - needs to happen after creation of the header.
    WriteImageMethods();

    return true;
  }

  void FillData(std::vector<uint8_t>& data) {
    // Note we don't put the header, we only have it reserved in `data` as
    // Image::WriteData expects the object section to contain the image header.
    auto compute_dest = [&](const ImageSection& section) {
      return data.data() + section.Offset();
    };

    auto objects_section = header_.GetImageSection(ImageHeader::kSectionObjects);
    memcpy(compute_dest(objects_section) + sizeof(ImageHeader), objects_.data(), objects_.size());

    auto fields_section = header_.GetImageSection(ImageHeader::kSectionArtFields);
    memcpy(compute_dest(fields_section), art_fields_.data(), fields_section.Size());

    auto methods_section = header_.GetImageSection(ImageHeader::kSectionArtMethods);
    memcpy(compute_dest(methods_section), art_methods_.data(), methods_section.Size());

    auto im_tables_section = header_.GetImageSection(ImageHeader::kSectionImTables);
    memcpy(compute_dest(im_tables_section), im_tables_.data(), im_tables_section.Size());

    auto intern_section = header_.GetImageSection(ImageHeader::kSectionInternedStrings);
    intern_table_.WriteToMemory(compute_dest(intern_section));

    auto class_table_section = header_.GetImageSection(ImageHeader::kSectionClassTable);
    class_table_.WriteToMemory(compute_dest(class_table_section));

    auto string_offsets_section =
        header_.GetImageSection(ImageHeader::kSectionStringReferenceOffsets);
    memcpy(compute_dest(string_offsets_section),
           string_reference_offsets_.data(),
           string_offsets_section.Size());

    auto dex_cache_section = header_.GetImageSection(ImageHeader::kSectionDexCacheArrays);
    memcpy(compute_dest(dex_cache_section), dex_cache_arrays_.data(), dex_cache_section.Size());

    auto metadata_section = header_.GetImageSection(ImageHeader::kSectionMetadata);
    memcpy(compute_dest(metadata_section), metadata_.data(), metadata_section.Size());

    DCHECK_EQ(metadata_section.Offset() + metadata_section.Size(), data.size());
  }


  ImageHeader* GetHeader() {
    return &header_;
  }

  const gc::accounting::ContinuousSpaceBitmap& GetImageBitmap() const {
    return image_bitmap_;
  }

  const std::string& GetDexLocation() const {
    return dex_location_;
  }

 private:
  bool IsInBootImage(const void* obj) const {
    return reinterpret_cast<uintptr_t>(obj) - boot_image_begin_ < boot_image_size_;
  }

  // Returns the image contents for `cls`. If `cls` is in the boot image, the
  // method just returns it.
  mirror::Class* GetClassContent(ObjPtr<mirror::Class> cls) REQUIRES_SHARED(Locks::mutator_lock_) {
    if (cls == nullptr || IsInBootImage(cls.Ptr())) {
      return cls.Ptr();
    }
    const dex::ClassDef* class_def = cls->GetClassDef();
    DCHECK(class_def != nullptr) << cls->PrettyClass();
    auto it = classes_.find(class_def);
    DCHECK(it != classes_.end()) << cls->PrettyClass();
    mirror::Class* result = reinterpret_cast<mirror::Class*>(objects_.data() + it->second);
    DCHECK(result->GetClass()->IsClass());
    return result;
  }

  // Returns a pointer that can be stored in `objects_`:
  // - The pointer itself for boot image objects,
  // - The offset in the image for all other objects.
  template <typename T> T* GetOrComputeImageAddress(ObjPtr<T> object)
      REQUIRES_SHARED(Locks::mutator_lock_) {
    if (object == nullptr || IsInBootImage(object.Ptr())) {
      DCHECK(object == nullptr || Runtime::Current()->GetHeap()->ObjectIsInBootImageSpace(object));
      return object.Ptr();
    }

    if (object->IsClassLoader()) {
      // DexCache and Class point to class loaders. For runtime-generated app
      // images, we don't encode the class loader. It will be set when the
      // runtime is loading the image.
      return nullptr;
    }

    if (object->GetClass() == GetClassRoot<mirror::ClassExt>()) {
      // No need to encode `ClassExt`. If needed, it will be reconstructed at
      // runtime.
      return nullptr;
    }

    uint32_t offset = 0u;
    if (object->IsClass()) {
      offset = CopyClass(object->AsClass());
    } else if (object->IsDexCache()) {
      offset = CopyDexCache(object->AsDexCache());
    } else {
      offset = CopyObject(object);
    }
    return reinterpret_cast<T*>(image_begin_ + sizeof(ImageHeader) + offset);
  }

  void CreateImageSections() {
    sections_[ImageHeader::kSectionObjects] = ImageSection(0u, object_section_size_);
    sections_[ImageHeader::kSectionArtFields] =
        ImageSection(sections_[ImageHeader::kSectionObjects].End(), art_fields_.size());

    // Round up to the alignment for ArtMethod.
    static_assert(IsAligned<sizeof(void*)>(ArtMethod::Size(kRuntimePointerSize)));
    size_t cur_pos = RoundUp(sections_[ImageHeader::kSectionArtFields].End(), sizeof(void*));
    sections_[ImageHeader::kSectionArtMethods] = ImageSection(cur_pos, art_methods_.size());

    // Round up to the alignment for ImTables.
    cur_pos = RoundUp(sections_[ImageHeader::kSectionArtMethods].End(), sizeof(void*));
    sections_[ImageHeader::kSectionImTables] = ImageSection(cur_pos, im_tables_.size());

    // Round up to the alignment for conflict tables.
    cur_pos = RoundUp(sections_[ImageHeader::kSectionImTables].End(), sizeof(void*));
    sections_[ImageHeader::kSectionIMTConflictTables] = ImageSection(cur_pos, 0u);

    sections_[ImageHeader::kSectionRuntimeMethods] =
        ImageSection(sections_[ImageHeader::kSectionIMTConflictTables].End(), 0u);

    // Round up to the alignment the string table expects. See HashSet::WriteToMemory.
    cur_pos = RoundUp(sections_[ImageHeader::kSectionRuntimeMethods].End(), sizeof(uint64_t));

    size_t intern_table_bytes = intern_table_.WriteToMemory(nullptr);
    sections_[ImageHeader::kSectionInternedStrings] = ImageSection(cur_pos, intern_table_bytes);

    // Obtain the new position and round it up to the appropriate alignment.
    cur_pos = RoundUp(sections_[ImageHeader::kSectionInternedStrings].End(), sizeof(uint64_t));

    size_t class_table_bytes = class_table_.WriteToMemory(nullptr);
    sections_[ImageHeader::kSectionClassTable] = ImageSection(cur_pos, class_table_bytes);

    // Round up to the alignment of the offsets we are going to store.
    cur_pos = RoundUp(sections_[ImageHeader::kSectionClassTable].End(), sizeof(uint32_t));
    sections_[ImageHeader::kSectionStringReferenceOffsets] = ImageSection(
        cur_pos, string_reference_offsets_.size() * sizeof(string_reference_offsets_[0]));

    // Round up to the alignment dex caches arrays expects.
    cur_pos =
        RoundUp(sections_[ImageHeader::kSectionStringReferenceOffsets].End(), sizeof(void*));
    sections_[ImageHeader::kSectionDexCacheArrays] =
        ImageSection(cur_pos, dex_cache_arrays_.size());

    // Round up to the alignment expected for the metadata, which holds dex
    // cache arrays.
    cur_pos = RoundUp(sections_[ImageHeader::kSectionDexCacheArrays].End(), sizeof(void*));
    sections_[ImageHeader::kSectionMetadata] = ImageSection(cur_pos, metadata_.size());
  }

  // Returns the copied mirror Object if in the image, or the object directly if
  // in the boot image. For the copy, this is really its content, it should not
  // be returned as an `ObjPtr` (as it's not a GC object), nor stored anywhere.
  template<typename T> T* FromImageOffsetToRuntimeContent(uint32_t offset) {
    if (offset == 0u || IsInBootImage(reinterpret_cast<const void*>(offset))) {
      return reinterpret_cast<T*>(offset);
    }
    uint32_t vector_data_offset = FromImageOffsetToVectorOffset(offset);
    return reinterpret_cast<T*>(objects_.data() + vector_data_offset);
  }

  uint32_t FromImageOffsetToVectorOffset(uint32_t offset) const {
    DCHECK(!IsInBootImage(reinterpret_cast<const void*>(offset)));
    return offset - sizeof(ImageHeader) - image_begin_;
  }

  class InternStringHash {
   public:
    explicit InternStringHash(RuntimeImageHelper* helper) : helper_(helper) {}

    // NO_THREAD_SAFETY_ANALYSIS as these helpers get passed to `HashSet`.
    size_t operator()(mirror::String* str) const NO_THREAD_SAFETY_ANALYSIS {
      int32_t hash = str->GetStoredHashCode();
      DCHECK_EQ(hash, str->ComputeHashCode());
      // An additional cast to prevent undesired sign extension.
      return static_cast<uint32_t>(hash);
    }

    size_t operator()(uint32_t entry) const NO_THREAD_SAFETY_ANALYSIS {
      return (*this)(helper_->FromImageOffsetToRuntimeContent<mirror::String>(entry));
    }

   private:
    RuntimeImageHelper* helper_;
  };

  class InternStringEquals {
   public:
    explicit InternStringEquals(RuntimeImageHelper* helper) : helper_(helper) {}

    // NO_THREAD_SAFETY_ANALYSIS as these helpers get passed to `HashSet`.
    bool operator()(uint32_t entry, mirror::String* other) const NO_THREAD_SAFETY_ANALYSIS {
      if (kIsDebugBuild) {
        Locks::mutator_lock_->AssertSharedHeld(Thread::Current());
      }
      return other->Equals(helper_->FromImageOffsetToRuntimeContent<mirror::String>(entry));
    }

    bool operator()(uint32_t entry, uint32_t other) const NO_THREAD_SAFETY_ANALYSIS {
      return (*this)(entry, helper_->FromImageOffsetToRuntimeContent<mirror::String>(other));
    }

   private:
    RuntimeImageHelper* helper_;
  };

  using InternTableSet =
        HashSet<uint32_t, DefaultEmptyFn<uint32_t>, InternStringHash, InternStringEquals>;

  class ClassDescriptorHash {
   public:
    explicit ClassDescriptorHash(RuntimeImageHelper* helper) : helper_(helper) {}

    uint32_t operator()(const ClassTable::TableSlot& slot) const NO_THREAD_SAFETY_ANALYSIS {
      uint32_t ptr = slot.NonHashData();
      if (helper_->IsInBootImage(reinterpret_cast32<const void*>(ptr))) {
        return reinterpret_cast32<mirror::Class*>(ptr)->DescriptorHash();
      }
      return helper_->class_hashes_.Get(helper_->FromImageOffsetToVectorOffset(ptr));
    }

   private:
    RuntimeImageHelper* helper_;
  };

  class ClassDescriptorEquals {
   public:
    ClassDescriptorEquals() {}

    bool operator()(const ClassTable::TableSlot& a, const ClassTable::TableSlot& b)
        const NO_THREAD_SAFETY_ANALYSIS {
      // No need to fetch the descriptor: we know the classes we are inserting
      // in the ClassTable are unique.
      return a.Data() == b.Data();
    }
  };

  using ClassTableSet = HashSet<ClassTable::TableSlot,
                                ClassTable::TableSlotEmptyFn,
                                ClassDescriptorHash,
                                ClassDescriptorEquals>;

  // Helper class to collect classes that we will generate in the image.
  class ClassTableVisitor {
   public:
    ClassTableVisitor(Handle<mirror::ClassLoader> loader, VariableSizedHandleScope& handles)
        : loader_(loader), handles_(handles) {}

    bool operator()(ObjPtr<mirror::Class> klass) REQUIRES_SHARED(Locks::mutator_lock_) {
      // Record app classes and boot classpath classes: app classes will be
      // generated in the image and put in the class table, boot classpath
      // classes will be put in the class table.
      ObjPtr<mirror::ClassLoader> class_loader = klass->GetClassLoader();
      if (klass->IsResolved() && (class_loader == loader_.Get() || class_loader == nullptr)) {
        handles_.NewHandle(klass);
      }
      return true;
    }

   private:
    Handle<mirror::ClassLoader> loader_;
    VariableSizedHandleScope& handles_;
  };

  // Helper class visitor to filter out classes we cannot emit.
  class PruneVisitor {
   public:
    PruneVisitor(Thread* self,
                 RuntimeImageHelper* helper,
                 const ArenaSet<const DexFile*>& dex_files,
                 ArenaVector<Handle<mirror::Class>>& classes,
                 ArenaAllocator& allocator)
        : self_(self),
          helper_(helper),
          dex_files_(dex_files),
          visited_(allocator.Adapter()),
          classes_to_write_(classes) {}

    bool CanEmitHelper(Handle<mirror::Class> cls) REQUIRES_SHARED(Locks::mutator_lock_) {
      // If the class comes from a dex file which is not part of the primary
      // APK, don't encode it.
      if (!ContainsElement(dex_files_, &cls->GetDexFile())) {
        return false;
      }

      // Ensure pointers to classes in `cls` can also be emitted.
      StackHandleScope<1> hs(self_);
      MutableHandle<mirror::Class> other_class = hs.NewHandle(cls->GetSuperClass());
      if (!CanEmit(other_class)) {
        return false;
      }

      other_class.Assign(cls->GetComponentType());
      if (!CanEmit(other_class)) {
        return false;
      }

      for (size_t i = 0, num_interfaces = cls->NumDirectInterfaces(); i < num_interfaces; ++i) {
        other_class.Assign(cls->GetDirectInterface(i));
        DCHECK(other_class != nullptr);
        if (!CanEmit(other_class)) {
          return false;
        }
      }
      return true;
    }

    bool CanEmit(Handle<mirror::Class> cls) REQUIRES_SHARED(Locks::mutator_lock_) {
      if (cls == nullptr) {
        return true;
      }
      DCHECK(cls->IsResolved());
      // Only emit classes that are resolved and not erroneous.
      if (cls->IsErroneous()) {
        return false;
      }

      // Proxy classes are generated at runtime, so don't emit them.
      if (cls->IsProxyClass()) {
        return false;
      }

      // Classes in the boot image can be trivially encoded directly.
      if (helper_->IsInBootImage(cls.Get())) {
        return true;
      }

      if (cls->IsBootStrapClassLoaded()) {
        // We cannot encode classes that are part of the boot classpath.
        return false;
      }

      DCHECK(!cls->IsPrimitive());

      if (cls->IsArrayClass()) {
        if (cls->IsBootStrapClassLoaded()) {
          // For boot classpath arrays, we can only emit them if they are
          // in the boot image already.
          return helper_->IsInBootImage(cls.Get());
        }
        ObjPtr<mirror::Class> temp = cls.Get();
        while ((temp = temp->GetComponentType())->IsArrayClass()) {}
        StackHandleScope<1> hs(self_);
        Handle<mirror::Class> other_class = hs.NewHandle(temp);
        return CanEmit(other_class);
      }
      const dex::ClassDef* class_def = cls->GetClassDef();
      DCHECK_NE(class_def, nullptr);
      auto existing = visited_.find(class_def);
      if (existing != visited_.end()) {
        // Already processed;
        return existing->second == VisitState::kCanEmit;
      }

      visited_.Put(class_def, VisitState::kVisiting);
      if (CanEmitHelper(cls)) {
        visited_.Overwrite(class_def, VisitState::kCanEmit);
        return true;
      } else {
        visited_.Overwrite(class_def, VisitState::kCannotEmit);
        return false;
      }
    }

    void Visit(Handle<mirror::Object> obj) REQUIRES_SHARED(Locks::mutator_lock_) {
      MutableHandle<mirror::Class> cls(obj.GetReference());
      if (CanEmit(cls)) {
        if (cls->IsBootStrapClassLoaded()) {
          DCHECK(helper_->IsInBootImage(cls.Get()));
          // Insert the bootclasspath class in the class table.
          uint32_t hash = cls->DescriptorHash();
          helper_->class_table_.InsertWithHash(ClassTable::TableSlot(cls.Get(), hash), hash);
        } else {
          classes_to_write_.push_back(cls);
        }
      }
    }

   private:
    enum class VisitState {
      kVisiting,
      kCanEmit,
      kCannotEmit,
    };

    Thread* const self_;
    RuntimeImageHelper* const helper_;
    const ArenaSet<const DexFile*>& dex_files_;
    ArenaSafeMap<const dex::ClassDef*, VisitState> visited_;
    ArenaVector<Handle<mirror::Class>>& classes_to_write_;
  };

  void EmitClasses(Thread* self, Handle<mirror::ObjectArray<mirror::Object>> dex_cache_array)
      REQUIRES_SHARED(Locks::mutator_lock_) {
    ScopedTrace trace("Emit strings and classes");
    ArenaSet<const DexFile*> dex_files(allocator_.Adapter());
    for (int32_t i = 0; i < dex_cache_array->GetLength(); ++i) {
      dex_files.insert(dex_cache_array->Get(i)->AsDexCache()->GetDexFile());
    }

    StackHandleScope<1> hs(self);
    Handle<mirror::ClassLoader> loader = hs.NewHandle(
        dex_cache_array->Get(0)->AsDexCache()->GetClassLoader());
    ClassTable* const class_table = loader->GetClassTable();
    if (class_table == nullptr) {
      return;
    }

    VariableSizedHandleScope handles(self);
    {
      ClassTableVisitor class_table_visitor(loader, handles);
      class_table->Visit(class_table_visitor);
    }

    ArenaVector<Handle<mirror::Class>> classes_to_write(allocator_.Adapter());
    classes_to_write.reserve(class_table->Size());
    {
      PruneVisitor prune_visitor(self, this, dex_files, classes_to_write, allocator_);
      handles.VisitHandles(prune_visitor);
    }

    for (Handle<mirror::Class> cls : classes_to_write) {
      {
        ScopedAssertNoThreadSuspension sants("Writing class");
        CopyClass(cls.Get());
      }
      self->AllowThreadSuspension();
    }

    // Relocate the type array entries. We do this now before creating image
    // sections because we may add new boot image classes into our
    // `class_table`_.
    for (auto entry : dex_caches_) {
      const DexFile& dex_file = *entry.first;
      mirror::DexCache* cache = reinterpret_cast<mirror::DexCache*>(&objects_[entry.second]);
      mirror::GcRootArray<mirror::Class>* old_types_array = cache->GetResolvedTypesArray();
      if (HasNativeRelocation(old_types_array)) {
        auto reloc_it = native_relocations_.find(old_types_array);
        DCHECK(reloc_it != native_relocations_.end());
        ArenaVector<uint8_t>& data =
            (reloc_it->second.first == NativeRelocationKind::kFullNativeDexCacheArray)
                ? dex_cache_arrays_ : metadata_;
        mirror::GcRootArray<mirror::Class>* content_array =
            reinterpret_cast<mirror::GcRootArray<mirror::Class>*>(
                data.data() + reloc_it->second.second);
        for (uint32_t i = 0; i < dex_file.NumTypeIds(); ++i) {
          ObjPtr<mirror::Class> cls = old_types_array->Get(i);
          if (cls == nullptr) {
            content_array->Set(i, nullptr);
          } else if (IsInBootImage(cls.Ptr())) {
            if (!cls->IsPrimitive()) {
              // The dex cache is concurrently updated by the app. If the class
              // collection logic in `PruneVisitor` did not see this class, insert it now.
              // Note that application class tables do not contain primitive
              // classes.
              uint32_t hash = cls->DescriptorHash();
              class_table_.InsertWithHash(ClassTable::TableSlot(cls.Ptr(), hash), hash);
            }
            content_array->Set(i, cls.Ptr());
          } else if (cls->IsArrayClass()) {
            std::string class_name;
            cls->GetDescriptor(&class_name);
            auto class_it = array_classes_.find(class_name);
            if (class_it == array_classes_.end()) {
              content_array->Set(i, nullptr);
            } else {
              mirror::Class* ptr = reinterpret_cast<mirror::Class*>(
                  image_begin_ + sizeof(ImageHeader) + class_it->second);
              content_array->Set(i, ptr);
            }
          } else {
            DCHECK(!cls->IsPrimitive());
            DCHECK(!cls->IsProxyClass());
            const dex::ClassDef* class_def = cls->GetClassDef();
            DCHECK_NE(class_def, nullptr);
            auto class_it = classes_.find(class_def);
            if (class_it == classes_.end()) {
              content_array->Set(i, nullptr);
            } else {
              mirror::Class* ptr = reinterpret_cast<mirror::Class*>(
                  image_begin_ + sizeof(ImageHeader) + class_it->second);
              content_array->Set(i, ptr);
            }
          }
        }
      }
    }
  }

  // Helper visitor returning the location of a native pointer in the image.
  class NativePointerVisitor {
   public:
    explicit NativePointerVisitor(RuntimeImageHelper* helper) : helper_(helper) {}

    template <typename T>
    T* operator()(T* ptr, [[maybe_unused]] void** dest_addr) const {
      return helper_->NativeLocationInImage(ptr, /* must_have_relocation= */ true);
    }

    template <typename T> T* operator()(T* ptr, bool must_have_relocation = true) const {
      return helper_->NativeLocationInImage(ptr, must_have_relocation);
    }

   private:
    RuntimeImageHelper* helper_;
  };

  template <typename T> T* NativeLocationInImage(T* ptr, bool must_have_relocation) const {
    if (ptr == nullptr || IsInBootImage(ptr)) {
      return ptr;
    }

    auto it = native_relocations_.find(ptr);
    if (it == native_relocations_.end()) {
      DCHECK(!must_have_relocation);
      return nullptr;
    }
    switch (it->second.first) {
      case NativeRelocationKind::kArtMethod:
      case NativeRelocationKind::kArtMethodArray: {
        uint32_t offset = sections_[ImageHeader::kSectionArtMethods].Offset();
        return reinterpret_cast<T*>(image_begin_ + offset + it->second.second);
      }
      case NativeRelocationKind::kArtFieldArray: {
        uint32_t offset = sections_[ImageHeader::kSectionArtFields].Offset();
        return reinterpret_cast<T*>(image_begin_ + offset + it->second.second);
      }
      case NativeRelocationKind::kImTable: {
        uint32_t offset = sections_[ImageHeader::kSectionImTables].Offset();
        return reinterpret_cast<T*>(image_begin_ + offset + it->second.second);
      }
      case NativeRelocationKind::kStartupNativeDexCacheArray: {
        uint32_t offset = sections_[ImageHeader::kSectionMetadata].Offset();
        return reinterpret_cast<T*>(image_begin_ + offset + it->second.second);
      }
      case NativeRelocationKind::kFullNativeDexCacheArray: {
        uint32_t offset = sections_[ImageHeader::kSectionDexCacheArrays].Offset();
        return reinterpret_cast<T*>(image_begin_ + offset + it->second.second);
      }
    }
  }

  template <typename Visitor>
  void RelocateMethodPointerArrays(mirror::Class* klass, const Visitor& visitor)
      REQUIRES_SHARED(Locks::mutator_lock_) {
    // A bit of magic here: we cast contents from our buffer to mirror::Class,
    // and do pointer comparison between 1) these classes, and 2) boot image objects.
    // Both kinds do not move.

    // See if we need to fixup the vtable field.
    mirror::Class* super = FromImageOffsetToRuntimeContent<mirror::Class>(
        reinterpret_cast32<uint32_t>(
            klass->GetSuperClass<kVerifyNone, kWithoutReadBarrier>().Ptr()));
    DCHECK(super != nullptr) << "j.l.Object should never be in an app runtime image";
    mirror::PointerArray* vtable = FromImageOffsetToRuntimeContent<mirror::PointerArray>(
        reinterpret_cast32<uint32_t>(klass->GetVTable<kVerifyNone, kWithoutReadBarrier>().Ptr()));
    mirror::PointerArray* super_vtable = FromImageOffsetToRuntimeContent<mirror::PointerArray>(
        reinterpret_cast32<uint32_t>(super->GetVTable<kVerifyNone, kWithoutReadBarrier>().Ptr()));
    if (vtable != nullptr && vtable != super_vtable) {
      DCHECK(!IsInBootImage(vtable));
      vtable->Fixup(vtable, kRuntimePointerSize, visitor);
    }

    // See if we need to fixup entries in the IfTable.
    mirror::IfTable* iftable = FromImageOffsetToRuntimeContent<mirror::IfTable>(
        reinterpret_cast32<uint32_t>(
            klass->GetIfTable<kVerifyNone, kWithoutReadBarrier>().Ptr()));
    mirror::IfTable* super_iftable = FromImageOffsetToRuntimeContent<mirror::IfTable>(
        reinterpret_cast32<uint32_t>(
            super->GetIfTable<kVerifyNone, kWithoutReadBarrier>().Ptr()));
    int32_t iftable_count = iftable->Count();
    int32_t super_iftable_count = super_iftable->Count();
    for (int32_t i = 0; i < iftable_count; ++i) {
      mirror::PointerArray* methods = FromImageOffsetToRuntimeContent<mirror::PointerArray>(
          reinterpret_cast32<uint32_t>(
              iftable->GetMethodArrayOrNull<kVerifyNone, kWithoutReadBarrier>(i).Ptr()));
      mirror::PointerArray* super_methods = (i < super_iftable_count)
          ? FromImageOffsetToRuntimeContent<mirror::PointerArray>(
                reinterpret_cast32<uint32_t>(
                    super_iftable->GetMethodArrayOrNull<kVerifyNone, kWithoutReadBarrier>(i).Ptr()))
          : nullptr;
      if (methods != super_methods) {
        DCHECK(!IsInBootImage(methods));
        methods->Fixup(methods, kRuntimePointerSize, visitor);
      }
    }
  }

  template <typename Visitor, typename T>
  void RelocateNativeDexCacheArray(mirror::NativeArray<T>* old_method_array,
                                   uint32_t num_ids,
                                   const Visitor& visitor)
      REQUIRES_SHARED(Locks::mutator_lock_) {
    if (old_method_array == nullptr) {
      return;
    }

    auto it = native_relocations_.find(old_method_array);
    DCHECK(it != native_relocations_.end());
    ArenaVector<uint8_t>& data =
        (it->second.first == NativeRelocationKind::kFullNativeDexCacheArray)
            ? dex_cache_arrays_ : metadata_;

    mirror::NativeArray<T>* content_array =
        reinterpret_cast<mirror::NativeArray<T>*>(data.data() + it->second.second);
    for (uint32_t i = 0; i < num_ids; ++i) {
      // We may not have relocations for some entries, in which case we'll
      // just store null.
      content_array->Set(i, visitor(content_array->Get(i), /* must_have_relocation= */ false));
    }
  }

  template <typename Visitor>
  void RelocateDexCacheArrays(mirror::DexCache* cache,
                              const DexFile& dex_file,
                              const Visitor& visitor)
      REQUIRES_SHARED(Locks::mutator_lock_) {
    mirror::NativeArray<ArtMethod>* old_method_array = cache->GetResolvedMethodsArray();
    cache->SetResolvedMethodsArray(visitor(old_method_array));
    RelocateNativeDexCacheArray(old_method_array, dex_file.NumMethodIds(), visitor);

    mirror::NativeArray<ArtField>* old_field_array = cache->GetResolvedFieldsArray();
    cache->SetResolvedFieldsArray(visitor(old_field_array));
    RelocateNativeDexCacheArray(old_field_array, dex_file.NumFieldIds(), visitor);

    mirror::GcRootArray<mirror::String>* old_strings_array = cache->GetStringsArray();
    cache->SetStringsArray(visitor(old_strings_array));

    mirror::GcRootArray<mirror::Class>* old_types_array = cache->GetResolvedTypesArray();
    cache->SetResolvedTypesArray(visitor(old_types_array));
  }

  void RelocateNativePointers() {
    ScopedTrace relocate_native_pointers("Relocate native pointers");
    ScopedObjectAccess soa(Thread::Current());
    NativePointerVisitor visitor(this);
    for (auto&& entry : classes_) {
      mirror::Class* cls = reinterpret_cast<mirror::Class*>(&objects_[entry.second]);
      cls->FixupNativePointers(cls, kRuntimePointerSize, visitor);
      RelocateMethodPointerArrays(cls, visitor);
    }
    for (auto&& entry : array_classes_) {
      mirror::Class* cls = reinterpret_cast<mirror::Class*>(&objects_[entry.second]);
      cls->FixupNativePointers(cls, kRuntimePointerSize, visitor);
      RelocateMethodPointerArrays(cls, visitor);
    }
    for (auto&& entry : native_relocations_) {
      if (entry.second.first == NativeRelocationKind::kImTable) {
        ImTable* im_table = reinterpret_cast<ImTable*>(im_tables_.data() + entry.second.second);
        RelocateImTable(im_table, visitor);
      }
    }
    for (auto&& entry : dex_caches_) {
      mirror::DexCache* cache = reinterpret_cast<mirror::DexCache*>(&objects_[entry.second]);
      RelocateDexCacheArrays(cache, *entry.first, visitor);
    }
  }

  void RelocateImTable(ImTable* im_table, const NativePointerVisitor& visitor) {
    for (size_t i = 0; i < ImTable::kSize; ++i) {
      ArtMethod* method = im_table->Get(i, kRuntimePointerSize);
      ArtMethod* new_method = nullptr;
      if (method->IsRuntimeMethod() && !IsInBootImage(method)) {
        // New IMT conflict method: just use the boot image version.
        // TODO: Consider copying the new IMT conflict method.
        new_method = Runtime::Current()->GetImtConflictMethod();
        DCHECK(IsInBootImage(new_method));
      } else {
        new_method = visitor(method);
      }
      if (method != new_method) {
        im_table->Set(i, new_method, kRuntimePointerSize);
      }
    }
  }

  void CopyFieldArrays(ObjPtr<mirror::Class> cls, uint32_t class_image_address)
      REQUIRES_SHARED(Locks::mutator_lock_) {
    LengthPrefixedArray<ArtField>* fields[] = {
        cls->GetSFieldsPtr(), cls->GetIFieldsPtr(),
    };
    for (LengthPrefixedArray<ArtField>* cur_fields : fields) {
      if (cur_fields != nullptr) {
        // Copy the array.
        size_t number_of_fields = cur_fields->size();
        size_t size = LengthPrefixedArray<ArtField>::ComputeSize(number_of_fields);
        size_t offset = art_fields_.size();
        art_fields_.resize(offset + size);
        auto* dest_array =
            reinterpret_cast<LengthPrefixedArray<ArtField>*>(art_fields_.data() + offset);
        memcpy(dest_array, cur_fields, size);
        native_relocations_.Put(cur_fields,
                                std::make_pair(NativeRelocationKind::kArtFieldArray, offset));

        // Update the class pointer of individual fields.
        for (size_t i = 0; i != number_of_fields; ++i) {
          dest_array->At(i).GetDeclaringClassAddressWithoutBarrier()->Assign(
              reinterpret_cast<mirror::Class*>(class_image_address));
        }
      }
    }
  }

  void CopyMethodArrays(ObjPtr<mirror::Class> cls,
                        uint32_t class_image_address,
                        bool is_class_initialized)
      REQUIRES_SHARED(Locks::mutator_lock_) {
    size_t number_of_methods = cls->NumMethods();
    if (number_of_methods == 0) {
      return;
    }

    size_t size = LengthPrefixedArray<ArtMethod>::ComputeSize(number_of_methods);
    size_t offset = art_methods_.size();
    art_methods_.resize(offset + size);
    auto* dest_array =
        reinterpret_cast<LengthPrefixedArray<ArtMethod>*>(art_methods_.data() + offset);
    memcpy(dest_array, cls->GetMethodsPtr(), size);
    native_relocations_.Put(cls->GetMethodsPtr(),
                            std::make_pair(NativeRelocationKind::kArtMethodArray, offset));

    for (size_t i = 0; i != number_of_methods; ++i) {
      ArtMethod* method = &cls->GetMethodsPtr()->At(i);
      ArtMethod* copy = &dest_array->At(i);

      // Update the class pointer.
      ObjPtr<mirror::Class> declaring_class = method->GetDeclaringClass();
      if (declaring_class == cls) {
        copy->GetDeclaringClassAddressWithoutBarrier()->Assign(
            reinterpret_cast<mirror::Class*>(class_image_address));
      } else {
        DCHECK(method->IsCopied());
        if (!IsInBootImage(declaring_class.Ptr())) {
          DCHECK(classes_.find(declaring_class->GetClassDef()) != classes_.end());
          copy->GetDeclaringClassAddressWithoutBarrier()->Assign(
              reinterpret_cast<mirror::Class*>(
                  image_begin_ +
                  sizeof(ImageHeader) +
                  classes_.Get(declaring_class->GetClassDef())));
        }
      }

      // Record the native relocation of the method.
      uintptr_t copy_offset =
          reinterpret_cast<uintptr_t>(copy) - reinterpret_cast<uintptr_t>(art_methods_.data());
      native_relocations_.Put(method,
                              std::make_pair(NativeRelocationKind::kArtMethod, copy_offset));

      // Ignore the single-implementation info for abstract method.
      if (method->IsAbstract()) {
        copy->SetHasSingleImplementation(false);
        copy->SetSingleImplementation(nullptr, kRuntimePointerSize);
      }

      // Set the entrypoint and data pointer of the method.
      StubType stub;
      if (method->IsNative()) {
        stub = StubType::kQuickGenericJNITrampoline;
      } else if (!cls->IsVerified()) {
        stub = StubType::kQuickToInterpreterBridge;
      } else if (!is_class_initialized && method->NeedsClinitCheckBeforeCall()) {
        stub = StubType::kQuickResolutionTrampoline;
      } else if (interpreter::IsNterpSupported() && CanMethodUseNterp(method)) {
        stub = StubType::kNterpTrampoline;
      } else {
        stub = StubType::kQuickToInterpreterBridge;
      }
      const std::vector<gc::space::ImageSpace*>& image_spaces =
          Runtime::Current()->GetHeap()->GetBootImageSpaces();
      DCHECK(!image_spaces.empty());
      const OatFile* oat_file = image_spaces[0]->GetOatFile();
      DCHECK(oat_file != nullptr);
      const OatHeader& header = oat_file->GetOatHeader();
      const void* entrypoint = header.GetOatAddress(stub);
      if (method->IsNative() && (is_class_initialized || !method->NeedsClinitCheckBeforeCall())) {
        // Use boot JNI stub if found.
        ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
        const void* boot_jni_stub = class_linker->FindBootJniStub(method);
        if (boot_jni_stub != nullptr) {
          entrypoint = boot_jni_stub;
        }
      }
      copy->SetEntryPointFromQuickCompiledCode(entrypoint);

      if (method->IsNative()) {
        StubType stub_type = method->IsCriticalNative()
            ? StubType::kJNIDlsymLookupCriticalTrampoline
            : StubType::kJNIDlsymLookupTrampoline;
        copy->SetEntryPointFromJni(header.GetOatAddress(stub_type));
      } else if (method->HasCodeItem()) {
        const uint8_t* code_item = reinterpret_cast<const uint8_t*>(method->GetCodeItem());
        DCHECK_GE(code_item, method->GetDexFile()->DataBegin());
        uint32_t code_item_offset = dchecked_integral_cast<uint32_t>(
            code_item - method->GetDexFile()->DataBegin());;
        copy->SetDataPtrSize(
            reinterpret_cast<const void*>(code_item_offset), kRuntimePointerSize);
      }
    }
  }

  void CopyImTable(ObjPtr<mirror::Class> cls) REQUIRES_SHARED(Locks::mutator_lock_) {
    ImTable* table = cls->GetImt(kRuntimePointerSize);

    // If the table is null or shared and/or already emitted, we can skip.
    if (table == nullptr || IsInBootImage(table) || HasNativeRelocation(table)) {
      return;
    }
    const size_t size = ImTable::SizeInBytes(kRuntimePointerSize);
    size_t offset = im_tables_.size();
    im_tables_.resize(offset + size);
    uint8_t* dest = im_tables_.data() + offset;
    memcpy(dest, table, size);
    native_relocations_.Put(table, std::make_pair(NativeRelocationKind::kImTable, offset));
  }

  bool HasNativeRelocation(void* ptr) const {
    return native_relocations_.find(ptr) != native_relocations_.end();
  }


  static void LoadClassesFromReferenceProfile(
      Thread* self,
      const dchecked_vector<Handle<mirror::DexCache>>& dex_caches)
          REQUIRES_SHARED(Locks::mutator_lock_) {
    AppInfo* app_info = Runtime::Current()->GetAppInfo();
    std::string profile_file = app_info->GetPrimaryApkReferenceProfile();

    if (profile_file.empty()) {
      return;
    }

    // Lock the file, it could be concurrently updated by the system. Don't block
    // as this is app startup sensitive.
    std::string error;
    ScopedFlock profile =
        LockedFile::Open(profile_file.c_str(), O_RDONLY, /*block=*/false, &error);

    if (profile == nullptr) {
      LOG(DEBUG) << "Couldn't lock the profile file " << profile_file << ": " << error;
      return;
    }

    ProfileCompilationInfo profile_info(/* for_boot_image= */ false);

    if (!profile_info.Load(profile->Fd())) {
      LOG(DEBUG) << "Could not load profile file";
      return;
    }

    StackHandleScope<1> hs(self);
    Handle<mirror::ClassLoader> class_loader =
        hs.NewHandle<mirror::ClassLoader>(dex_caches[0]->GetClassLoader());
    ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
    ScopedTrace loading_classes("Loading classes from profile");
    for (auto dex_cache : dex_caches) {
      const DexFile* dex_file = dex_cache->GetDexFile();
      const ArenaSet<dex::TypeIndex>* class_types = profile_info.GetClasses(*dex_file);
      if (class_types == nullptr) {
        // This means the profile file did not reference the dex file, which is the case
        // if there's no classes and methods of that dex file in the profile.
        continue;
      }

      for (dex::TypeIndex idx : *class_types) {
        // The index is greater or equal to NumTypeIds if the type is an extra
        // descriptor, not referenced by the dex file.
        if (idx.index_ < dex_file->NumTypeIds()) {
          ObjPtr<mirror::Class> klass = class_linker->ResolveType(idx, dex_cache, class_loader);
          if (klass == nullptr) {
            self->ClearException();
            LOG(DEBUG) << "Failed to preload " << dex_file->PrettyType(idx);
            continue;
          }
        }
      }
    }
  }

  bool WriteObjects(std::string* error_msg) {
    ScopedTrace write_objects("Writing objects");
    ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
    ScopedObjectAccess soa(Thread::Current());
    VariableSizedHandleScope handles(soa.Self());

    Handle<mirror::Class> object_array_class = handles.NewHandle(
        GetClassRoot<mirror::ObjectArray<mirror::Object>>(class_linker));

    Handle<mirror::ObjectArray<mirror::Object>> image_roots = handles.NewHandle(
        mirror::ObjectArray<mirror::Object>::Alloc(
            soa.Self(), object_array_class.Get(), ImageHeader::kImageRootsMax));

    if (image_roots == nullptr) {
      DCHECK(soa.Self()->IsExceptionPending());
      soa.Self()->ClearException();
      *error_msg = "Out of memory when trying to generate a runtime app image";
      return false;
    }

    // Find the dex files that will be used for generating the app image.
    dchecked_vector<Handle<mirror::DexCache>> dex_caches;
    FindDexCaches(soa.Self(), dex_caches, handles);

    if (dex_caches.size() == 0) {
      *error_msg = "Did not find dex caches to generate an app image";
      return false;
    }
    const OatDexFile* oat_dex_file = dex_caches[0]->GetDexFile()->GetOatDexFile();
    VdexFile* vdex_file = oat_dex_file->GetOatFile()->GetVdexFile();
    // The first entry in `dex_caches` contains the location of the primary APK.
    dex_location_ = oat_dex_file->GetDexFileLocation();

    size_t number_of_dex_files = vdex_file->GetNumberOfDexFiles();
    if (number_of_dex_files != dex_caches.size()) {
      // This means some dex files haven't been executed. For simplicity, just
      // register them and recollect dex caches.
      Handle<mirror::ClassLoader> loader = handles.NewHandle(dex_caches[0]->GetClassLoader());
      VisitClassLoaderDexFiles(soa.Self(), loader, [&](const art::DexFile* dex_file)
          REQUIRES_SHARED(Locks::mutator_lock_) {
        class_linker->RegisterDexFile(*dex_file, dex_caches[0]->GetClassLoader());
        return true;  // Continue with other dex files.
      });
      dex_caches.clear();
      FindDexCaches(soa.Self(), dex_caches, handles);
      if (number_of_dex_files != dex_caches.size()) {
        *error_msg = "Number of dex caches does not match number of dex files in the primary APK";
        return false;
      }
    }

    // If classes referenced in the reference profile are not loaded, preload
    // them. This makes sure we generate a good runtime app image, even if this
    // current app run did not load all startup classes.
    LoadClassesFromReferenceProfile(soa.Self(), dex_caches);

    // We store the checksums of the dex files used at runtime. These can be
    // different compared to the vdex checksums due to compact dex.
    std::vector<uint32_t> checksums(number_of_dex_files);
    uint32_t checksum_index = 0;
    for (const OatDexFile* current_oat_dex_file : oat_dex_file->GetOatFile()->GetOatDexFiles()) {
      const DexFile::Header* header =
          reinterpret_cast<const DexFile::Header*>(current_oat_dex_file->GetDexFilePointer());
      checksums[checksum_index++] = header->checksum_;
    }
    DCHECK_EQ(checksum_index, number_of_dex_files);

    // Create the fake OatHeader to store the dependencies of the image.
    SafeMap<std::string, std::string> key_value_store;
    Runtime* runtime = Runtime::Current();
    key_value_store.Put(OatHeader::kApexVersionsKey, runtime->GetApexVersions());
    key_value_store.Put(OatHeader::kBootClassPathKey,
                        android::base::Join(runtime->GetBootClassPathLocations(), ':'));
    key_value_store.Put(OatHeader::kBootClassPathChecksumsKey,
                        runtime->GetBootClassPathChecksums());
    key_value_store.Put(OatHeader::kClassPathKey,
                        oat_dex_file->GetOatFile()->GetClassLoaderContext());
    key_value_store.Put(OatHeader::kConcurrentCopying,
                        gUseReadBarrier ? OatHeader::kTrueValue : OatHeader::kFalseValue);

    std::unique_ptr<const InstructionSetFeatures> isa_features =
        InstructionSetFeatures::FromCppDefines();
    std::unique_ptr<OatHeader> oat_header(
        OatHeader::Create(kRuntimeQuickCodeISA,
                          isa_features.get(),
                          number_of_dex_files,
                          &key_value_store));

    // Create the byte array containing the oat header and dex checksums.
    uint32_t checksums_size = checksums.size() * sizeof(uint32_t);
    Handle<mirror::ByteArray> header_data = handles.NewHandle(
        mirror::ByteArray::Alloc(soa.Self(), oat_header->GetHeaderSize() + checksums_size));

    if (header_data == nullptr) {
      DCHECK(soa.Self()->IsExceptionPending());
      soa.Self()->ClearException();
      *error_msg = "Out of memory when trying to generate a runtime app image";
      return false;
    }

    memcpy(header_data->GetData(), oat_header.get(), oat_header->GetHeaderSize());
    memcpy(header_data->GetData() + oat_header->GetHeaderSize(), checksums.data(), checksums_size);

    // Create and populate the dex caches aray.
    Handle<mirror::ObjectArray<mirror::Object>> dex_cache_array = handles.NewHandle(
        mirror::ObjectArray<mirror::Object>::Alloc(
            soa.Self(), object_array_class.Get(), dex_caches.size()));

    if (dex_cache_array == nullptr) {
      DCHECK(soa.Self()->IsExceptionPending());
      soa.Self()->ClearException();
      *error_msg = "Out of memory when trying to generate a runtime app image";
      return false;
    }

    for (uint32_t i = 0; i < dex_caches.size(); ++i) {
      dex_cache_array->Set(i, dex_caches[i].Get());
    }

    image_roots->Set(ImageHeader::kDexCaches, dex_cache_array.Get());
    image_roots->Set(ImageHeader::kClassRoots, class_linker->GetClassRoots());
    image_roots->Set(ImageHeader::kAppImageOatHeader, header_data.Get());

    {
      // Now that we have created all objects needed for the `image_roots`, copy
      // it into the buffer. Note that this will recursively copy all objects
      // contained in `image_roots`. That's acceptable as we don't have cycles,
      // nor a deep graph.
      ScopedAssertNoThreadSuspension sants("Writing runtime app image");
      CopyObject(image_roots.Get());
    }

    // Emit classes defined in the app class loader (which will also indirectly
    // emit dex caches and their arrays).
    EmitClasses(soa.Self(), dex_cache_array);

    return true;
  }

  class FixupVisitor {
   public:
    FixupVisitor(RuntimeImageHelper* image, size_t copy_offset)
        : image_(image), copy_offset_(copy_offset) {}

    // 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";
    }

    void operator()(ObjPtr<mirror::Object> obj,
                    MemberOffset offset,
                    bool is_static) const
        REQUIRES_SHARED(Locks::mutator_lock_) {
      // We don't copy static fields, they are being handled when we try to
      // initialize the class.
      ObjPtr<mirror::Object> ref =
          is_static ? nullptr : obj->GetFieldObject<mirror::Object>(offset);
      mirror::Object* address = image_->GetOrComputeImageAddress(ref);
      mirror::Object* copy =
          reinterpret_cast<mirror::Object*>(image_->objects_.data() + copy_offset_);
      copy->GetFieldObjectReferenceAddr<kVerifyNone>(offset)->Assign(address);
    }

    // java.lang.ref.Reference visitor.
    void operator()([[maybe_unused]] ObjPtr<mirror::Class> klass,
                    ObjPtr<mirror::Reference> ref) const REQUIRES_SHARED(Locks::mutator_lock_) {
      operator()(ref, mirror::Reference::ReferentOffset(), /* is_static */ false);
    }

   private:
    RuntimeImageHelper* image_;
    size_t copy_offset_;
  };

  template <typename T>
  void CopyNativeDexCacheArray(uint32_t num_entries,
                               uint32_t max_entries,
                               mirror::NativeArray<T>* array) {
    if (array == nullptr) {
      return;
    }

    bool only_startup = !mirror::DexCache::ShouldAllocateFullArray(num_entries, max_entries);
    ArenaVector<uint8_t>& data = only_startup ? metadata_ : dex_cache_arrays_;
    NativeRelocationKind relocation_kind = only_startup
        ? NativeRelocationKind::kStartupNativeDexCacheArray
        : NativeRelocationKind::kFullNativeDexCacheArray;

    size_t size = num_entries * sizeof(void*);
    // We need to reserve space to store `num_entries` because ImageSpace doesn't have
    // access to the dex files when relocating dex caches.
    size_t offset = RoundUp(data.size(), sizeof(void*)) + sizeof(uintptr_t);
    data.resize(RoundUp(data.size(), sizeof(void*)) + sizeof(uintptr_t) + size);
    reinterpret_cast<uintptr_t*>(data.data() + offset)[-1] = num_entries;

    // Copy each entry individually. We cannot use memcpy, as the entries may be
    // updated concurrently by other mutator threads.
    mirror::NativeArray<T>* copy = reinterpret_cast<mirror::NativeArray<T>*>(data.data() + offset);
    for (uint32_t i = 0; i < num_entries; ++i) {
      copy->Set(i, array->Get(i));
    }
    native_relocations_.Put(array, std::make_pair(relocation_kind, offset));
  }

  template <typename T>
  mirror::GcRootArray<T>* CreateGcRootDexCacheArray(uint32_t num_entries,
                                                    uint32_t max_entries,
                                                    mirror::GcRootArray<T>* array) {
    if (array == nullptr) {
      return nullptr;
    }
    bool only_startup = !mirror::DexCache::ShouldAllocateFullArray(num_entries, max_entries);
    ArenaVector<uint8_t>& data = only_startup ? metadata_ : dex_cache_arrays_;
    NativeRelocationKind relocation_kind = only_startup
        ? NativeRelocationKind::kStartupNativeDexCacheArray
        : NativeRelocationKind::kFullNativeDexCacheArray;
    size_t size = num_entries * sizeof(GcRoot<T>);
    // We need to reserve space to store `num_entries` because ImageSpace doesn't have
    // access to the dex files when relocating dex caches.
    static_assert(sizeof(GcRoot<T>) == sizeof(uint32_t));
    size_t offset = data.size() + sizeof(uint32_t);
    data.resize(data.size() + sizeof(uint32_t) + size);
    reinterpret_cast<uint32_t*>(data.data() + offset)[-1] = num_entries;
    native_relocations_.Put(array, std::make_pair(relocation_kind, offset));

    return reinterpret_cast<mirror::GcRootArray<T>*>(data.data() + offset);
  }
  static bool EmitDexCacheArrays() {
    // We need to treat dex cache arrays specially in an image for userfaultfd.
    // Disable for now. See b/270936884.
    return !gUseUserfaultfd;
  }

  uint32_t CopyDexCache(ObjPtr<mirror::DexCache> cache) REQUIRES_SHARED(Locks::mutator_lock_) {
    auto it = dex_caches_.find(cache->GetDexFile());
    if (it != dex_caches_.end()) {
      return it->second;
    }
    uint32_t offset = CopyObject(cache);
    dex_caches_.Put(cache->GetDexFile(), offset);
    // For dex caches, clear pointers to data that will be set at runtime.
    mirror::Object* copy = reinterpret_cast<mirror::Object*>(objects_.data() + offset);
    reinterpret_cast<mirror::DexCache*>(copy)->ResetNativeArrays();
    reinterpret_cast<mirror::DexCache*>(copy)->SetDexFile(nullptr);

    if (!EmitDexCacheArrays()) {
      return offset;
    }

    // Copy the ArtMethod array.
    mirror::NativeArray<ArtMethod>* resolved_methods = cache->GetResolvedMethodsArray();
    CopyNativeDexCacheArray(cache->GetDexFile()->NumMethodIds(),
                            mirror::DexCache::kDexCacheMethodCacheSize,
                            resolved_methods);
    // Store the array pointer in the dex cache, which will be relocated at the end.
    reinterpret_cast<mirror::DexCache*>(copy)->SetResolvedMethodsArray(resolved_methods);

    // Copy the ArtField array.
    mirror::NativeArray<ArtField>* resolved_fields = cache->GetResolvedFieldsArray();
    CopyNativeDexCacheArray(cache->GetDexFile()->NumFieldIds(),
                            mirror::DexCache::kDexCacheFieldCacheSize,
                            resolved_fields);
    // Store the array pointer in the dex cache, which will be relocated at the end.
    reinterpret_cast<mirror::DexCache*>(copy)->SetResolvedFieldsArray(resolved_fields);

    // Copy the type array.
    mirror::GcRootArray<mirror::Class>* resolved_types = cache->GetResolvedTypesArray();
    CreateGcRootDexCacheArray(cache->GetDexFile()->NumTypeIds(),
                              mirror::DexCache::kDexCacheTypeCacheSize,
                              resolved_types);
    // Store the array pointer in the dex cache, which will be relocated at the end.
    reinterpret_cast<mirror::DexCache*>(copy)->SetResolvedTypesArray(resolved_types);

    // Copy the string array.
    mirror::GcRootArray<mirror::String>* strings = cache->GetStringsArray();
    // Note: `new_strings` points to temporary data, and is only valid here.
    mirror::GcRootArray<mirror::String>* new_strings =
        CreateGcRootDexCacheArray(cache->GetDexFile()->NumStringIds(),
                                  mirror::DexCache::kDexCacheStringCacheSize,
                                  strings);
    // Store the array pointer in the dex cache, which will be relocated at the end.
    reinterpret_cast<mirror::DexCache*>(copy)->SetStringsArray(strings);

    // The code below copies new objects, so invalidate the address we have for
    // `copy`.
    copy = nullptr;
    if (strings != nullptr) {
      for (uint32_t i = 0; i < cache->GetDexFile()->NumStringIds(); ++i) {
        ObjPtr<mirror::String> str = strings->Get(i);
        if (str == nullptr || IsInBootImage(str.Ptr())) {
          new_strings->Set(i, str.Ptr());
        } else {
          uint32_t hash = static_cast<uint32_t>(str->GetStoredHashCode());
          DCHECK_EQ(hash, static_cast<uint32_t>(str->ComputeHashCode()))
              << "Dex cache strings should be interned";
          auto it2 = intern_table_.FindWithHash(str.Ptr(), hash);
          if (it2 == intern_table_.end()) {
            uint32_t string_offset = CopyObject(str);
            uint32_t address = image_begin_ + string_offset + sizeof(ImageHeader);
            intern_table_.InsertWithHash(address, hash);
            new_strings->Set(i, reinterpret_cast<mirror::String*>(address));
          } else {
            new_strings->Set(i, reinterpret_cast<mirror::String*>(*it2));
          }
          // To not confuse string references from the dex cache object and
          // string references from the array, we put an offset bigger than the
          // size of a DexCache object. ClassLinker::VisitInternedStringReferences
          // knows how to decode this offset.
          string_reference_offsets_.emplace_back(
              sizeof(ImageHeader) + offset, sizeof(mirror::DexCache) + i);
        }
      }
    }

    return offset;
  }

  bool IsInitialized(mirror::Class* cls) REQUIRES_SHARED(Locks::mutator_lock_) {
    if (IsInBootImage(cls)) {
      const OatDexFile* oat_dex_file = cls->GetDexFile().GetOatDexFile();
      DCHECK(oat_dex_file != nullptr) << "We should always have an .oat file for a boot image";
      uint16_t class_def_index = cls->GetDexClassDefIndex();
      ClassStatus oat_file_class_status = oat_dex_file->GetOatClass(class_def_index).GetStatus();
      return oat_file_class_status == ClassStatus::kVisiblyInitialized;
    } else {
      return cls->IsVisiblyInitialized<kVerifyNone>();
    }
  }
  // Try to initialize `copy`. Note that `cls` may not be initialized.
  // This is called after the image generation logic has visited super classes
  // and super interfaces, so we can just check those directly.
  bool TryInitializeClass(mirror::Class* copy, ObjPtr<mirror::Class> cls, uint32_t class_offset)
      REQUIRES_SHARED(Locks::mutator_lock_) {
    if (!cls->IsVerified()) {
      return false;
    }
    if (cls->IsArrayClass()) {
      return true;
    }

    // Check if we have been able to initialize the super class.
    mirror::Class* super = GetClassContent(cls->GetSuperClass());
    DCHECK(super != nullptr)
        << "App image classes should always have a super class: " << cls->PrettyClass();
    if (!IsInitialized(super)) {
      return false;
    }

    // We won't initialize class with class initializers.
    if (cls->FindClassInitializer(kRuntimePointerSize) != nullptr) {
      return false;
    }

    // For non-interface classes, we require all implemented interfaces to be
    // initialized.
    if (!cls->IsInterface()) {
      for (size_t i = 0; i < cls->NumDirectInterfaces(); i++) {
        mirror::Class* itf = GetClassContent(cls->GetDirectInterface(i));
        if (!IsInitialized(itf)) {
          return false;
        }
      }
    }

    // Trivial case: no static fields.
    if (cls->NumStaticFields() == 0u) {
      return true;
    }

    // Go over all static fields and try to initialize them.
    EncodedStaticFieldValueIterator it(cls->GetDexFile(), *cls->GetClassDef());
    if (!it.HasNext()) {
      return true;
    }

    // Temporary string offsets in case we failed to initialize the class. We
    // will add the offsets at the end of this method if we are successful.
    ArenaVector<AppImageReferenceOffsetInfo> string_offsets(allocator_.Adapter());
    ClassLinker* linker = Runtime::Current()->GetClassLinker();
    ClassAccessor accessor(cls->GetDexFile(), *cls->GetClassDef());
    for (const ClassAccessor::Field& field : accessor.GetStaticFields()) {
      if (!it.HasNext()) {
        break;
      }
      ArtField* art_field = linker->LookupResolvedField(field.GetIndex(),
                                                        cls->GetDexCache(),
                                                        cls->GetClassLoader(),
                                                        /* is_static= */ true);
      DCHECK_NE(art_field, nullptr);
      MemberOffset offset(art_field->GetOffset());
      switch (it.GetValueType()) {
        case EncodedArrayValueIterator::ValueType::kBoolean:
          copy->SetFieldBoolean<false>(offset, it.GetJavaValue().z);
          break;
        case EncodedArrayValueIterator::ValueType::kByte:
          copy->SetFieldByte<false>(offset, it.GetJavaValue().b);
          break;
        case EncodedArrayValueIterator::ValueType::kShort:
          copy->SetFieldShort<false>(offset, it.GetJavaValue().s);
          break;
        case EncodedArrayValueIterator::ValueType::kChar:
          copy->SetFieldChar<false>(offset, it.GetJavaValue().c);
          break;
        case EncodedArrayValueIterator::ValueType::kInt:
          copy->SetField32<false>(offset, it.GetJavaValue().i);
          break;
        case EncodedArrayValueIterator::ValueType::kLong:
          copy->SetField64<false>(offset, it.GetJavaValue().j);
          break;
        case EncodedArrayValueIterator::ValueType::kFloat:
          copy->SetField32<false>(offset, it.GetJavaValue().i);
          break;
        case EncodedArrayValueIterator::ValueType::kDouble:
          copy->SetField64<false>(offset, it.GetJavaValue().j);
          break;
        case EncodedArrayValueIterator::ValueType::kNull:
          copy->SetFieldObject<false>(offset, nullptr);
          break;
        case EncodedArrayValueIterator::ValueType::kString: {
          ObjPtr<mirror::String> str =
              linker->LookupString(dex::StringIndex(it.GetJavaValue().i), cls->GetDexCache());
          mirror::String* str_copy = nullptr;
          if (str == nullptr) {
            // String wasn't created yet.
            return false;
          } else if (IsInBootImage(str.Ptr())) {
            str_copy = str.Ptr();
          } else {
            uint32_t hash = static_cast<uint32_t>(str->GetStoredHashCode());
            DCHECK_EQ(hash, static_cast<uint32_t>(str->ComputeHashCode()))
                << "Dex cache strings should be interned";
            auto string_it = intern_table_.FindWithHash(str.Ptr(), hash);
            if (string_it == intern_table_.end()) {
              // The string must be interned.
              uint32_t string_offset = CopyObject(str);
              // Reload the class copy after having copied the string.
              copy = reinterpret_cast<mirror::Class*>(objects_.data() + class_offset);
              uint32_t address = image_begin_ + string_offset + sizeof(ImageHeader);
              intern_table_.InsertWithHash(address, hash);
              str_copy = reinterpret_cast<mirror::String*>(address);
            } else {
              str_copy = reinterpret_cast<mirror::String*>(*string_it);
            }
            string_offsets.emplace_back(sizeof(ImageHeader) + class_offset, offset.Int32Value());
          }
          uint8_t* raw_addr = reinterpret_cast<uint8_t*>(copy) + offset.Int32Value();
          mirror::HeapReference<mirror::Object>* objref_addr =
              reinterpret_cast<mirror::HeapReference<mirror::Object>*>(raw_addr);
          objref_addr->Assign</* kIsVolatile= */ false>(str_copy);
          break;
        }
        case EncodedArrayValueIterator::ValueType::kType: {
          // Note that it may be that the referenced type hasn't been processed
          // yet by the image generation logic. In this case we bail out for
          // simplicity.
          ObjPtr<mirror::Class> type =
              linker->LookupResolvedType(dex::TypeIndex(it.GetJavaValue().i), cls);
          mirror::Class* type_copy = nullptr;
          if (type == nullptr) {
            // Class wasn't resolved yet.
            return false;
          } else if (IsInBootImage(type.Ptr())) {
            // Make sure the type is in our class table.
            uint32_t hash = type->DescriptorHash();
            class_table_.InsertWithHash(ClassTable::TableSlot(type.Ptr(), hash), hash);
            type_copy = type.Ptr();
          } else if (type->IsArrayClass()) {
            std::string class_name;
            type->GetDescriptor(&class_name);
            auto class_it = array_classes_.find(class_name);
            if (class_it == array_classes_.end()) {
              return false;
            }
            type_copy = reinterpret_cast<mirror::Class*>(
                image_begin_ + sizeof(ImageHeader) + class_it->second);
          } else {
            const dex::ClassDef* class_def = type->GetClassDef();
            DCHECK_NE(class_def, nullptr);
            auto class_it = classes_.find(class_def);
            if (class_it == classes_.end()) {
              return false;
            }
            type_copy = reinterpret_cast<mirror::Class*>(
                image_begin_ + sizeof(ImageHeader) + class_it->second);
          }
          uint8_t* raw_addr = reinterpret_cast<uint8_t*>(copy) + offset.Int32Value();
          mirror::HeapReference<mirror::Object>* objref_addr =
              reinterpret_cast<mirror::HeapReference<mirror::Object>*>(raw_addr);
          objref_addr->Assign</* kIsVolatile= */ false>(type_copy);
          break;
        }
        default:
          LOG(FATAL) << "Unreachable";
      }
      it.Next();
    }
    // We have successfully initialized the class, we can now record the string
    // offsets.
    string_reference_offsets_.insert(
        string_reference_offsets_.end(), string_offsets.begin(), string_offsets.end());
    return true;
  }

  uint32_t CopyClass(ObjPtr<mirror::Class> cls) REQUIRES_SHARED(Locks::mutator_lock_) {
    DCHECK(!cls->IsBootStrapClassLoaded());
    uint32_t offset = 0u;
    if (cls->IsArrayClass()) {
      std::string class_name;
      cls->GetDescriptor(&class_name);
      auto it = array_classes_.find(class_name);
      if (it != array_classes_.end()) {
        return it->second;
      }
      offset = CopyObject(cls);
      array_classes_.Put(class_name, offset);
    } else {
      const dex::ClassDef* class_def = cls->GetClassDef();
      auto it = classes_.find(class_def);
      if (it != classes_.end()) {
        return it->second;
      }
      offset = CopyObject(cls);
      classes_.Put(class_def, offset);
    }

    uint32_t hash = cls->DescriptorHash();
    // Save the hash, the `HashSet` implementation requires to find it.
    class_hashes_.Put(offset, hash);
    uint32_t class_image_address = image_begin_ + sizeof(ImageHeader) + offset;
    bool inserted =
        class_table_.InsertWithHash(ClassTable::TableSlot(class_image_address, hash), hash).second;
    DCHECK(inserted) << "Class " << cls->PrettyDescriptor()
                     << " (" << cls.Ptr() << ") already inserted";

    // Clear internal state.
    mirror::Class* copy = reinterpret_cast<mirror::Class*>(objects_.data() + offset);
    copy->SetClinitThreadId(static_cast<pid_t>(0u));
    if (cls->IsArrayClass()) {
      DCHECK(copy->IsVisiblyInitialized());
    } else {
      copy->SetStatusInternal(cls->IsVerified() ? ClassStatus::kVerified : ClassStatus::kResolved);
    }

    // Clear static field values.
    auto clear_class = [&] () REQUIRES_SHARED(Locks::mutator_lock_) {
      MemberOffset static_offset = cls->GetFirstReferenceStaticFieldOffset(kRuntimePointerSize);
      uint32_t ref_offsets = cls->GetReferenceInstanceOffsets();
      size_t size = cls->GetClassSize() - static_offset.Uint32Value();
      // Adjust for overflow instance-offset bitmap, which is after the static
      // fields.
      if ((ref_offsets & mirror::Class::kVisitReferencesSlowpathMask) != 0) {
        ref_offsets &= ~mirror::Class::kVisitReferencesSlowpathMask;
        size -= ref_offsets * sizeof(uint32_t);
      }
      memset(objects_.data() + offset + static_offset.Uint32Value(), 0, size);
    };
    clear_class();

    bool is_class_initialized = TryInitializeClass(copy, cls, offset);
    // Reload the copy, it may have moved after `TryInitializeClass`.
    copy = reinterpret_cast<mirror::Class*>(objects_.data() + offset);
    if (is_class_initialized) {
      copy->SetStatusInternal(ClassStatus::kVisiblyInitialized);
      if (!cls->IsArrayClass() && !cls->IsFinalizable()) {
        copy->SetObjectSizeAllocFastPath(RoundUp(cls->GetObjectSize(), kObjectAlignment));
      }
      if (cls->IsInterface()) {
        copy->SetAccessFlags(copy->GetAccessFlags() | kAccRecursivelyInitialized);
      }
    } else {
      // If we fail to initialize, remove initialization related flags and
      // clear again.
      copy->SetObjectSizeAllocFastPath(std::numeric_limits<uint32_t>::max());
      copy->SetAccessFlags(copy->GetAccessFlags() & ~kAccRecursivelyInitialized);
      clear_class();
    }

    CopyFieldArrays(cls, class_image_address);
    CopyMethodArrays(cls, class_image_address, is_class_initialized);
    if (cls->ShouldHaveImt()) {
      CopyImTable(cls);
    }

    return offset;
  }

  // Copy `obj` in `objects_` and relocate references. Returns the offset
  // within our buffer.
  uint32_t CopyObject(ObjPtr<mirror::Object> obj) REQUIRES_SHARED(Locks::mutator_lock_) {
    // Copy the object in `objects_`.
    size_t object_size = obj->SizeOf();
    size_t offset = objects_.size();
    DCHECK(IsAligned<kObjectAlignment>(offset));
    object_offsets_.push_back(offset);
    objects_.resize(RoundUp(offset + object_size, kObjectAlignment));

    mirror::Object* copy = reinterpret_cast<mirror::Object*>(objects_.data() + offset);
    mirror::Object::CopyRawObjectData(
        reinterpret_cast<uint8_t*>(copy), obj, object_size - sizeof(mirror::Object));
    // Clear any lockword data.
    copy->SetLockWord(LockWord::Default(), /* as_volatile= */ false);
    copy->SetClass(obj->GetClass());

    // Fixup reference pointers.
    FixupVisitor visitor(this, offset);
    obj->VisitReferences</*kVisitNativeRoots=*/ false>(visitor, visitor);

    if (obj->IsString()) {
      // Ensure a string always has a hashcode stored. This is checked at
      // runtime because boot images don't want strings dirtied due to hashcode.
      reinterpret_cast<mirror::String*>(copy)->GetHashCode();
    }

    object_section_size_ += RoundUp(object_size, kObjectAlignment);
    return offset;
  }

  class CollectDexCacheVisitor : public DexCacheVisitor {
   public:
    explicit CollectDexCacheVisitor(VariableSizedHandleScope& handles) : handles_(handles) {}

    void Visit(ObjPtr<mirror::DexCache> dex_cache)
        REQUIRES_SHARED(Locks::dex_lock_, Locks::mutator_lock_) override {
      dex_caches_.push_back(handles_.NewHandle(dex_cache));
    }
    const std::vector<Handle<mirror::DexCache>>& GetDexCaches() const {
      return dex_caches_;
    }
   private:
    VariableSizedHandleScope& handles_;
    std::vector<Handle<mirror::DexCache>> dex_caches_;
  };

  // Find dex caches corresponding to the primary APK.
  void FindDexCaches(Thread* self,
                     dchecked_vector<Handle<mirror::DexCache>>& dex_caches,
                     VariableSizedHandleScope& handles)
      REQUIRES_SHARED(Locks::mutator_lock_) {
    ScopedTrace trace("Find dex caches");
    DCHECK(dex_caches.empty());
    // Collect all dex caches.
    ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
    CollectDexCacheVisitor visitor(handles);
    {
      ReaderMutexLock mu(self, *Locks::dex_lock_);
      class_linker->VisitDexCaches(&visitor);
    }

    // Find the primary APK.
    AppInfo* app_info = Runtime::Current()->GetAppInfo();
    for (Handle<mirror::DexCache> cache : visitor.GetDexCaches()) {
      if (app_info->GetRegisteredCodeType(cache->GetDexFile()->GetLocation()) ==
              AppInfo::CodeType::kPrimaryApk) {
        dex_caches.push_back(handles.NewHandle(cache.Get()));
        break;
      }
    }

    if (dex_caches.empty()) {
      return;
    }

    const OatDexFile* oat_dex_file = dex_caches[0]->GetDexFile()->GetOatDexFile();
    if (oat_dex_file == nullptr) {
      // We need a .oat file for loading an app image;
      dex_caches.clear();
      return;
    }

    // Store the dex caches in the order in which their corresponding dex files
    // are stored in the oat file. When we check for checksums at the point of
    // loading the image, we rely on this order.
    for (const OatDexFile* current : oat_dex_file->GetOatFile()->GetOatDexFiles()) {
      if (current != oat_dex_file) {
        for (Handle<mirror::DexCache> cache : visitor.GetDexCaches()) {
          if (cache->GetDexFile()->GetOatDexFile() == current) {
            dex_caches.push_back(handles.NewHandle(cache.Get()));
          }
        }
      }
    }
  }

  static uint64_t PointerToUint64(void* ptr) {
    return reinterpret_cast64<uint64_t>(ptr);
  }

  void WriteImageMethods() {
    ScopedObjectAccess soa(Thread::Current());
    // We can just use plain runtime pointers.
    Runtime* runtime = Runtime::Current();
    header_.image_methods_[ImageHeader::kResolutionMethod] =
        PointerToUint64(runtime->GetResolutionMethod());
    header_.image_methods_[ImageHeader::kImtConflictMethod] =
        PointerToUint64(runtime->GetImtConflictMethod());
    header_.image_methods_[ImageHeader::kImtUnimplementedMethod] =
        PointerToUint64(runtime->GetImtUnimplementedMethod());
    header_.image_methods_[ImageHeader::kSaveAllCalleeSavesMethod] =
        PointerToUint64(runtime->GetCalleeSaveMethod(CalleeSaveType::kSaveAllCalleeSaves));
    header_.image_methods_[ImageHeader::kSaveRefsOnlyMethod] =
        PointerToUint64(runtime->GetCalleeSaveMethod(CalleeSaveType::kSaveRefsOnly));
    header_.image_methods_[ImageHeader::kSaveRefsAndArgsMethod] =
        PointerToUint64(runtime->GetCalleeSaveMethod(CalleeSaveType::kSaveRefsAndArgs));
    header_.image_methods_[ImageHeader::kSaveEverythingMethod] =
        PointerToUint64(runtime->GetCalleeSaveMethod(CalleeSaveType::kSaveEverything));
    header_.image_methods_[ImageHeader::kSaveEverythingMethodForClinit] =
        PointerToUint64(runtime->GetCalleeSaveMethod(CalleeSaveType::kSaveEverythingForClinit));
    header_.image_methods_[ImageHeader::kSaveEverythingMethodForSuspendCheck] =
        PointerToUint64(
            runtime->GetCalleeSaveMethod(CalleeSaveType::kSaveEverythingForSuspendCheck));
  }

  // Header for the image, created at the end once we know the size of all
  // sections.
  ImageHeader header_;

  // Allocator for the various data structures to allocate while generating the
  // image.
  ArenaAllocator allocator_;

  // Contents of the various sections.
  ArenaVector<uint8_t> objects_;
  ArenaVector<uint8_t> art_fields_;
  ArenaVector<uint8_t> art_methods_;
  ArenaVector<uint8_t> im_tables_;
  ArenaVector<uint8_t> metadata_;
  ArenaVector<uint8_t> dex_cache_arrays_;

  ArenaVector<AppImageReferenceOffsetInfo> string_reference_offsets_;

  // Bitmap of live objects in `objects_`. Populated from `object_offsets_`
  // once we know `object_section_size`.
  gc::accounting::ContinuousSpaceBitmap image_bitmap_;

  // Sections stored in the header.
  ArenaVector<ImageSection> sections_;

  // A list of offsets in `objects_` where objects begin.
  ArenaVector<uint32_t> object_offsets_;

  ArenaSafeMap<const dex::ClassDef*, uint32_t> classes_;
  ArenaSafeMap<std::string, uint32_t> array_classes_;
  ArenaSafeMap<const DexFile*, uint32_t> dex_caches_;
  ArenaSafeMap<uint32_t, uint32_t> class_hashes_;

  ArenaSafeMap<void*, std::pair<NativeRelocationKind, uint32_t>> native_relocations_;

  // Cached values of boot image information.
  const uint32_t boot_image_begin_;
  const uint32_t boot_image_size_;

  // Where the image begins: just after the boot image.
  const uint32_t image_begin_;

  // Size of the `kSectionObjects` section.
  size_t object_section_size_;

  // The location of the primary APK / dex file.
  std::string dex_location_;

  // The intern table for strings that we will write to disk.
  InternTableSet intern_table_;

  // The class table holding classes that we will write to disk.
  ClassTableSet class_table_;

  friend class ClassDescriptorHash;
  friend class PruneVisitor;
  friend class NativePointerVisitor;
};

std::string RuntimeImage::GetRuntimeImageDir(const std::string& app_data_dir) {
  if (app_data_dir.empty()) {
    // The data directory is empty for tests.
    return "";
  }
  return app_data_dir + "/cache/oat_primary/";
}

// Note: this may return a relative path for tests.
std::string RuntimeImage::GetRuntimeImagePath(const std::string& app_data_dir,
                                              const std::string& dex_location,
                                              const std::string& isa) {
  std::string basename = android::base::Basename(dex_location);
  std::string filename = ReplaceFileExtension(basename, kArtExtension);

  return GetRuntimeImageDir(app_data_dir) + isa + "/" + filename;
}

std::string RuntimeImage::GetRuntimeImagePath(const std::string& dex_location) {
  return GetRuntimeImagePath(Runtime::Current()->GetProcessDataDirectory(),
                             dex_location,
                             GetInstructionSetString(kRuntimeQuickCodeISA));
}

static bool EnsureDirectoryExists(const std::string& directory, std::string* error_msg) {
  if (!OS::DirectoryExists(directory.c_str())) {
    static constexpr mode_t kDirectoryMode = S_IRWXU | S_IRGRP | S_IXGRP| S_IROTH | S_IXOTH;
    if (mkdir(directory.c_str(), kDirectoryMode) != 0) {
      *error_msg =
          StringPrintf("Could not create directory %s: %s", directory.c_str(), strerror(errno));
      return false;
    }
  }
  return true;
}

bool RuntimeImage::WriteImageToDisk(std::string* error_msg) {
  if (gPageSize != kMinPageSize) {
    *error_msg = "Writing runtime image is only supported on devices with 4K page size";
    return false;
  }

  gc::Heap* heap = Runtime::Current()->GetHeap();
  if (!heap->HasBootImageSpace()) {
    *error_msg = "Cannot generate an app image without a boot image";
    return false;
  }
  std::string oat_path = GetRuntimeImageDir(Runtime::Current()->GetProcessDataDirectory());
  if (!oat_path.empty() && !EnsureDirectoryExists(oat_path, error_msg)) {
    return false;
  }

  ScopedTrace generate_image_trace("Generating runtime image");
  std::unique_ptr<RuntimeImageHelper> image(new RuntimeImageHelper(heap));
  if (!image->Generate(error_msg)) {
    return false;
  }

  ScopedTrace write_image_trace("Writing runtime image to disk");

  const std::string path = GetRuntimeImagePath(image->GetDexLocation());
  if (!EnsureDirectoryExists(android::base::Dirname(path), error_msg)) {
    return false;
  }

  // We first generate the app image in a temporary file, which we will then
  // move to `path`.
  const std::string temp_path = ReplaceFileExtension(path, std::to_string(getpid()) + ".tmp");
  ImageFileGuard image_file;
  image_file.reset(OS::CreateEmptyFileWriteOnly(temp_path.c_str()));

  if (image_file == nullptr) {
    *error_msg = "Could not open " + temp_path + " for writing";
    return false;
  }

  std::vector<uint8_t> full_data(image->GetHeader()->GetImageSize());
  image->FillData(full_data);

  // Specify default block size of 512K to enable parallel image decompression.
  static constexpr size_t kMaxImageBlockSize = 524288;
  // Use LZ4 as good compromise between CPU time and compression. LZ4HC
  // empirically takes 10x more time compressing.
  static constexpr ImageHeader::StorageMode kImageStorageMode = ImageHeader::kStorageModeLZ4;
  // Note: no need to update the checksum of the runtime app image: we have no
  // use for it, and computing it takes CPU time.
  if (!image->GetHeader()->WriteData(
          image_file,
          full_data.data(),
          reinterpret_cast<const uint8_t*>(image->GetImageBitmap().Begin()),
          kImageStorageMode,
          kMaxImageBlockSize,
          /* update_checksum= */ false,
          error_msg)) {
    return false;
  }

  if (!image_file.WriteHeaderAndClose(temp_path, image->GetHeader(), error_msg)) {
    return false;
  }

  if (rename(temp_path.c_str(), path.c_str()) != 0) {
    *error_msg =
        "Failed to move runtime app image to " + path + ": " + std::string(strerror(errno));
    // Unlink directly: we cannot use `out` as we have closed it.
    unlink(temp_path.c_str());
    return false;
  }

  return true;
}

}  // namespace art
