Reland "Add strings from dex caches in runtime app image."

This reverts commit d1faf6d0d72c464753d64f1c92bdd999f69e179e.

Bug: 260557058
Bug: 263016547

Reason for reland: create String::Equals version without ObjPtr

Change-Id: Ib1c0d5c7a0e1e6c8f0cd821eb8ff0d1cb5561559
diff --git a/runtime/mirror/string.cc b/runtime/mirror/string.cc
index a21c967..839e01a 100644
--- a/runtime/mirror/string.cc
+++ b/runtime/mirror/string.cc
@@ -255,7 +255,7 @@
   return Alloc(self, length_with_flag, allocator_type, visitor);
 }
 
-bool String::Equals(ObjPtr<String> that) {
+bool String::Equals(mirror::String* that) {
   if (this == that) {
     // Quick reference equality test
     return true;
diff --git a/runtime/mirror/string.h b/runtime/mirror/string.h
index 2b0f6f3..90eb092 100644
--- a/runtime/mirror/string.h
+++ b/runtime/mirror/string.h
@@ -197,7 +197,14 @@
 
   bool Equals(const char* modified_utf8) REQUIRES_SHARED(Locks::mutator_lock_);
 
-  bool Equals(ObjPtr<String> that) REQUIRES_SHARED(Locks::mutator_lock_);
+  bool Equals(ObjPtr<mirror::String> that) REQUIRES_SHARED(Locks::mutator_lock_) {
+    return Equals(that.Ptr());
+  }
+
+  // A version that takes a mirror::String pointer instead of ObjPtr as it's being
+  // called by the runtime app image code which can encode mirror::String at 64bit
+  // addresses (ObjPtr only works with 32bit pointers).
+  bool Equals(mirror::String* that) REQUIRES_SHARED(Locks::mutator_lock_);
 
   // Create a modified UTF-8 encoded std::string from a java/lang/String object.
   std::string ToModifiedUtf8() REQUIRES_SHARED(Locks::mutator_lock_);
diff --git a/runtime/runtime_image.cc b/runtime/runtime_image.cc
index 5efe951..ffbb10c 100644
--- a/runtime/runtime_image.cc
+++ b/runtime/runtime_image.cc
@@ -36,6 +36,7 @@
 #include "mirror/object_array-alloc-inl.h"
 #include "mirror/object_array-inl.h"
 #include "mirror/object_array.h"
+#include "mirror/string-inl.h"
 #include "scoped_thread_state_change-inl.h"
 #include "vdex_file.h"
 
@@ -51,10 +52,12 @@
     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)) {}
+    object_section_size_(sizeof(ImageHeader)),
+    intern_table_(InternStringHash(this), InternStringEquals(this)) {}
+
 
   bool Generate(std::string* error_msg) {
-    if (!WriteImageRoot(error_msg)) {
+    if (!WriteObjects(error_msg)) {
       return false;
     }
 
@@ -136,6 +139,10 @@
     return dex_location_;
   }
 
+  void GenerateInternData(std::vector<uint8_t>& data) const {
+    intern_table_.WriteToMemory(data.data());
+  }
+
  private:
   bool IsInBootImage(const void* obj) const {
     return reinterpret_cast<uintptr_t>(obj) - boot_image_begin_ < boot_image_size_;
@@ -177,30 +184,100 @@
     // Round up to the alignment the string table expects. See HashSet::WriteToMemory.
     size_t cur_pos = RoundUp(sections[ImageHeader::kSectionRuntimeMethods].End(), sizeof(uint64_t));
 
-    sections[ImageHeader::kSectionInternedStrings] =
-        ImageSection(cur_pos, 0u);
+    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));
-
-    sections[ImageHeader::kSectionClassTable] =
-        ImageSection(cur_pos, 0u);
+    sections[ImageHeader::kSectionClassTable] = ImageSection(cur_pos, 0u);
 
     // 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, 0u);
+    sections[ImageHeader::kSectionStringReferenceOffsets] = ImageSection(cur_pos, 0u);
 
     // Round up to the alignment of the offsets we are going to store.
     cur_pos =
         RoundUp(sections[ImageHeader::kSectionStringReferenceOffsets].End(), sizeof(uint32_t));
 
-    sections[ImageHeader::kSectionMetadata] =
-        ImageSection(cur_pos, 0u);
+    sections[ImageHeader::kSectionMetadata] = ImageSection(cur_pos, 0u);
   }
 
-  bool WriteImageRoot(std::string* error_msg) {
+  // Returns the copied mirror Object. 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) {
+    uint32_t vector_data_offset = offset - sizeof(ImageHeader) - image_begin_;
+    return reinterpret_cast<T*>(image_data_.data() + vector_data_offset);
+  }
+
+  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>;
+
+  void VisitDexCache(ObjPtr<mirror::DexCache> dex_cache) REQUIRES_SHARED(Locks::mutator_lock_) {
+    const DexFile& dex_file = *dex_cache->GetDexFile();
+    // Currently only copy string objects into the image. Populate the intern
+    // table with these strings.
+    for (uint32_t i = 0; i < dex_file.NumStringIds(); ++i) {
+      ObjPtr<mirror::String> str = dex_cache->GetResolvedString(dex::StringIndex(i));
+      if (str != nullptr && !IsInBootImage(str.Ptr())) {
+        uint32_t hash = static_cast<uint32_t>(str->GetStoredHashCode());
+        DCHECK_EQ(hash, static_cast<uint32_t>(str->ComputeHashCode()))
+            << "Dex cache strings should be interned";
+        if (intern_table_.FindWithHash(str.Ptr(), hash) == intern_table_.end()) {
+          uint32_t offset = CopyObject(str);
+          intern_table_.InsertWithHash(image_begin_ + offset + sizeof(ImageHeader), hash);
+        }
+      }
+    }
+  }
+
+  void VisitDexCaches(Handle<mirror::ObjectArray<mirror::Object>> dex_cache_array)
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+    for (int32_t i = 0; i < dex_cache_array->GetLength(); ++i) {
+      VisitDexCache(ObjPtr<mirror::DexCache>::DownCast((dex_cache_array->Get(i))));
+    }
+  }
+
+  bool WriteObjects(std::string* error_msg) {
     ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
     ScopedObjectAccess soa(Thread::Current());
     VariableSizedHandleScope handles(soa.Self());
@@ -287,12 +364,17 @@
     image_roots->Set(ImageHeader::kClassRoots, class_linker->GetClassRoots());
     image_roots->Set(ImageHeader::kAppImageDexChecksums, checksums_array.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());
+    {
+      // 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());
+    }
+
+    // Copy objects stored in the dex caches.
+    VisitDexCaches(dex_cache_array);
     return true;
   }
 
@@ -474,6 +556,9 @@
 
   // 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_;
 };
 
 std::string RuntimeImage::GetRuntimeImagePath(const std::string& dex_location) {
@@ -521,20 +606,26 @@
     return false;
   }
 
-  // Write bitmap at aligned offset.
-  size_t aligned_offset = RoundUp(sizeof(ImageHeader) + image.GetData().size(), kPageSize);
-  if (!out->Write(reinterpret_cast<const char*>(image.GetImageBitmap().Begin()),
-                  image.GetImageBitmap().Size(),
-                  aligned_offset)) {
-    *error_msg = "Could not write image bitmap " + temp_path;
-    out->Unlink();
-    return false;
+  {
+    // Write intern string set.
+    auto intern_section = image.GetHeader().GetImageSection(ImageHeader::kSectionInternedStrings);
+    std::vector<uint8_t> intern_data(intern_section.Size());
+    image.GenerateInternData(intern_data);
+    if (!out->Write(reinterpret_cast<const char*>(intern_data.data()),
+                    intern_section.Size(),
+                    intern_section.Offset())) {
+      *error_msg = "Could not write intern section " + temp_path;
+      out->Unlink();
+      return false;
+    }
   }
 
-  // Set the file length page aligned.
-  size_t total_size = aligned_offset + RoundUp(image.GetImageBitmap().Size(), kPageSize);
-  if (out->SetLength(total_size) != 0) {
-    *error_msg = "Could not change size of image " + temp_path;
+  // Write bitmap.
+  auto bitmap_section = image.GetHeader().GetImageSection(ImageHeader::kSectionImageBitmap);
+  if (!out->Write(reinterpret_cast<const char*>(image.GetImageBitmap().Begin()),
+                  bitmap_section.Size(),
+                  bitmap_section.Offset())) {
+    *error_msg = "Could not write image bitmap " + temp_path;
     out->Unlink();
     return false;
   }