Add PreResolved strings dex cache array

For app images, this dex cache may be prepopulated to speed up
application startup. This new dex cache array is created when
--resolve-startup-const-strings=true.

Test: test-art-host
Bug: 116059983
Bug: 118385560

Change-Id: I379dc15174281665d7f4ceb106f7fbf51f89b921
diff --git a/compiler/driver/compiler_driver.cc b/compiler/driver/compiler_driver.cc
index 864b215..df6e8a8 100644
--- a/compiler/driver/compiler_driver.cc
+++ b/compiler/driver/compiler_driver.cc
@@ -719,6 +719,10 @@
 
   for (const DexFile* dex_file : dex_files) {
     dex_cache.Assign(class_linker->FindDexCache(soa.Self(), *dex_file));
+    if (only_startup_strings) {
+      // When resolving startup strings, create the preresolved strings array.
+      dex_cache->AddPreResolvedStringsArray();
+    }
     TimingLogger::ScopedTiming t("Resolve const-string Strings", timings);
 
     for (ClassAccessor accessor : dex_file->GetClasses()) {
@@ -757,6 +761,10 @@
                   : inst->VRegB_31c());
               ObjPtr<mirror::String> string = class_linker->ResolveString(string_index, dex_cache);
               CHECK(string != nullptr) << "Could not allocate a string when forcing determinism";
+              if (only_startup_strings) {
+                dex_cache->GetPreResolvedStrings()[string_index.index_] =
+                    GcRoot<mirror::String>(string);
+              }
               ++num_instructions;
               break;
             }
diff --git a/compiler/driver/compiler_options_map.def b/compiler/driver/compiler_options_map.def
index a593240..1ec34ec 100644
--- a/compiler/driver/compiler_options_map.def
+++ b/compiler/driver/compiler_options_map.def
@@ -52,7 +52,7 @@
 COMPILER_OPTIONS_KEY (double,                      TopKProfileThreshold)
 COMPILER_OPTIONS_KEY (bool,                        AbortOnHardVerifierFailure)
 COMPILER_OPTIONS_KEY (bool,                        AbortOnSoftVerifierFailure)
-COMPILER_OPTIONS_KEY (bool,                        ResolveStartupConstStrings, false)
+COMPILER_OPTIONS_KEY (bool,                        ResolveStartupConstStrings, kIsDebugBuild)
 COMPILER_OPTIONS_KEY (std::string,                 DumpInitFailures)
 COMPILER_OPTIONS_KEY (std::string,                 DumpCFG)
 COMPILER_OPTIONS_KEY (Unit,                        DumpCFGAppend)
diff --git a/dex2oat/dex2oat_test.cc b/dex2oat/dex2oat_test.cc
index 898940a..88ce324 100644
--- a/dex2oat/dex2oat_test.cc
+++ b/dex2oat/dex2oat_test.cc
@@ -2079,8 +2079,8 @@
   ScratchFile profile_file;
   std::vector<uint16_t> methods;
   std::vector<dex::TypeIndex> classes;
+  std::unique_ptr<const DexFile> dex(OpenTestDexFile("StringLiterals"));
   {
-    std::unique_ptr<const DexFile> dex(OpenTestDexFile("StringLiterals"));
     for (ClassAccessor accessor : dex->GetClasses()) {
       if (accessor.GetDescriptor() == std::string("LStringLiterals$StartupClass;")) {
         classes.push_back(accessor.GetClassIdx());
@@ -2140,15 +2140,43 @@
         seen.insert(str.Read()->ToModifiedUtf8());
       }
     });
+    // Ensure that the dex cache has a preresolved string array.
+    std::set<std::string> preresolved_seen;
+    bool saw_dexcache = false;
+    space->GetLiveBitmap()->VisitAllMarked(
+        [&](mirror::Object* obj) REQUIRES_SHARED(Locks::mutator_lock_) {
+      if (obj->IsDexCache<kVerifyNone>()) {
+        ObjPtr<mirror::DexCache> dex_cache = obj->AsDexCache();
+        GcRoot<mirror::String>* preresolved_strings = dex_cache->GetPreResolvedStrings();
+        ASSERT_EQ(dex->NumStringIds(), dex_cache->NumPreResolvedStrings());
+        for (size_t i = 0; i < dex_cache->NumPreResolvedStrings(); ++i) {
+          ObjPtr<mirror::String> string = preresolved_strings[i].Read<kWithoutReadBarrier>();
+          if (string != nullptr) {
+            preresolved_seen.insert(string->ToModifiedUtf8());
+          }
+        }
+        saw_dexcache = true;
+      }
+    });
+    ASSERT_TRUE(saw_dexcache);
+    // Everything in the preresolved array should also be in the intern table.
+    for (const std::string& str : preresolved_seen) {
+      EXPECT_TRUE(seen.find(str) != seen.end());
+    }
     // Normal methods
-    EXPECT_TRUE(seen.find("Loading ") != seen.end());
-    EXPECT_TRUE(seen.find("Starting up") != seen.end());
-    EXPECT_TRUE(seen.find("abcd.apk") != seen.end());
+    EXPECT_TRUE(preresolved_seen.find("Loading ") != preresolved_seen.end());
+    EXPECT_TRUE(preresolved_seen.find("Starting up") != preresolved_seen.end());
+    EXPECT_TRUE(preresolved_seen.find("abcd.apk") != preresolved_seen.end());
     EXPECT_TRUE(seen.find("Unexpected error") == seen.end());
     EXPECT_TRUE(seen.find("Shutting down!") == seen.end());
+    EXPECT_TRUE(preresolved_seen.find("Unexpected error") == preresolved_seen.end());
+    EXPECT_TRUE(preresolved_seen.find("Shutting down!") == preresolved_seen.end());
     // Classes initializers
-    EXPECT_TRUE(seen.find("Startup init") != seen.end());
+    EXPECT_TRUE(preresolved_seen.find("Startup init") != preresolved_seen.end());
     EXPECT_TRUE(seen.find("Other class init") == seen.end());
+    EXPECT_TRUE(preresolved_seen.find("Other class init") == preresolved_seen.end());
+    // Expect the sets match.
+    EXPECT_GE(seen.size(), preresolved_seen.size());
   }
 }
 
diff --git a/dex2oat/linker/image_writer.cc b/dex2oat/linker/image_writer.cc
index be62058..33af833 100644
--- a/dex2oat/linker/image_writer.cc
+++ b/dex2oat/linker/image_writer.cc
@@ -288,11 +288,16 @@
     for (const HeapReferencePointerInfo& ref_info : string_ref_info) {
       uint32_t base_offset;
 
-      if (HasDexCacheNativeRefTag(ref_info.first)) {
+      if (HasDexCacheStringNativeRefTag(ref_info.first)) {
         ++native_string_refs;
-        auto* obj_ptr = reinterpret_cast<mirror::Object*>(ClearDexCacheNativeRefTag(
+        auto* obj_ptr = reinterpret_cast<mirror::Object*>(ClearDexCacheNativeRefTags(
             ref_info.first));
-        base_offset = SetDexCacheNativeRefTag(GetImageOffset(obj_ptr));
+        base_offset = SetDexCacheStringNativeRefTag(GetImageOffset(obj_ptr));
+      } else if (HasDexCachePreResolvedStringNativeRefTag(ref_info.first)) {
+        ++native_string_refs;
+        auto* obj_ptr = reinterpret_cast<mirror::Object*>(ClearDexCacheNativeRefTags(
+            ref_info.first));
+        base_offset = SetDexCachePreResolvedStringNativeRefTag(GetImageOffset(obj_ptr));
       } else {
         ++managed_string_refs;
         base_offset = GetImageOffset(reinterpret_cast<mirror::Object*>(ref_info.first));
@@ -447,7 +452,19 @@
             if (IsValidAppImageStringReference(referred_string)) {
               ++string_info_collected;
               visitor.AddStringRefInfo(
-                  SetDexCacheNativeRefTag(reinterpret_cast<uintptr_t>(object.Ptr())), index);
+                  SetDexCacheStringNativeRefTag(reinterpret_cast<uintptr_t>(object.Ptr())), index);
+            }
+          }
+
+          // Visit all of the preinitialized strings.
+          GcRoot<mirror::String>* preresolved_strings = dex_cache->GetPreResolvedStrings();
+          for (size_t index = 0; index < dex_cache->NumPreResolvedStrings(); ++index) {
+            ObjPtr<mirror::String> referred_string = preresolved_strings[index].Read();
+            if (IsValidAppImageStringReference(referred_string)) {
+              ++string_info_collected;
+              visitor.AddStringRefInfo(SetDexCachePreResolvedStringNativeRefTag(
+                reinterpret_cast<uintptr_t>(object.Ptr())),
+                index);
             }
           }
 
@@ -852,15 +869,27 @@
     DCHECK_EQ(dex_file->NumStringIds() != 0u, dex_cache->GetStrings() != nullptr);
     AddDexCacheArrayRelocation(dex_cache->GetStrings(), start + layout.StringsOffset(), oat_index);
 
-    if (dex_cache->GetResolvedMethodTypes() != nullptr) {
-      AddDexCacheArrayRelocation(dex_cache->GetResolvedMethodTypes(),
-                                 start + layout.MethodTypesOffset(),
-                                 oat_index);
-    }
-    if (dex_cache->GetResolvedCallSites() != nullptr) {
-      AddDexCacheArrayRelocation(dex_cache->GetResolvedCallSites(),
-                                 start + layout.CallSitesOffset(),
-                                 oat_index);
+    AddDexCacheArrayRelocation(dex_cache->GetResolvedMethodTypes(),
+                               start + layout.MethodTypesOffset(),
+                               oat_index);
+    AddDexCacheArrayRelocation(dex_cache->GetResolvedCallSites(),
+                                start + layout.CallSitesOffset(),
+                                oat_index);
+
+    // Preresolved strings aren't part of the special layout.
+    GcRoot<mirror::String>* preresolved_strings = dex_cache->GetPreResolvedStrings();
+    if (preresolved_strings != nullptr) {
+      DCHECK(!IsInBootImage(preresolved_strings));
+      // Add the array to the metadata section.
+      const size_t count = dex_cache->NumPreResolvedStrings();
+      auto bin = BinTypeForNativeRelocationType(NativeObjectRelocationType::kGcRootPointer);
+      for (size_t i = 0; i < count; ++i) {
+        native_object_relocations_.emplace(&preresolved_strings[i],
+            NativeObjectRelocation { oat_index,
+                                     image_info.GetBinSlotSize(bin),
+                                     NativeObjectRelocationType::kGcRootPointer });
+        image_info.IncrementBinSlotSize(bin, sizeof(GcRoot<mirror::Object>));
+      }
     }
   }
 }
@@ -2340,9 +2369,21 @@
                        sizeof(typename decltype(string_reference_offsets_)::value_type) *
                            num_string_references_);
 
+  /*
+   * Metadata section.
+   */
+
+  // Round up to the alignment of the offsets we are going to store.
+  cur_pos = RoundUp(string_reference_offsets.End(),
+                    mirror::DexCache::PreResolvedStringsAlignment());
+
+  const ImageSection& metadata_section =
+      sections[ImageHeader::kSectionMetadata] =
+          ImageSection(cur_pos, GetBinSlotSize(Bin::kMetadata));
+
   // Return the number of bytes described by these sections, and the sections
   // themselves.
-  return make_pair(string_reference_offsets.End(), std::move(sections));
+  return make_pair(metadata_section.End(), std::move(sections));
 }
 
 void ImageWriter::CreateHeader(size_t oat_index) {
@@ -2546,6 +2587,12 @@
             new(dest)ImtConflictTable(orig_table->NumEntries(target_ptr_size_), target_ptr_size_));
         break;
       }
+      case NativeObjectRelocationType::kGcRootPointer: {
+        auto* orig_pointer = reinterpret_cast<GcRoot<mirror::Object>*>(pair.first);
+        auto* dest_pointer = reinterpret_cast<GcRoot<mirror::Object>*>(dest);
+        CopyAndFixupReference(dest_pointer->AddressWithoutBarrier(), orig_pointer->Read());
+        break;
+      }
     }
   }
   // Fixup the image method roots.
@@ -2966,6 +3013,12 @@
                                                copy_dex_cache,
                                                DexCache::ResolvedCallSitesOffset(),
                                                orig_dex_cache->NumResolvedCallSites());
+  if (orig_dex_cache->GetPreResolvedStrings() != nullptr) {
+    CopyAndFixupPointer(copy_dex_cache,
+                        DexCache::PreResolvedStringsOffset(),
+                        orig_dex_cache->GetPreResolvedStrings(),
+                        PointerSize::k64);
+  }
 
   // Remove the DexFile pointers. They will be fixed up when the runtime loads the oat file. Leaving
   // compiler pointers in here will make the output non-deterministic.
@@ -3179,6 +3232,8 @@
       return Bin::kImTable;
     case NativeObjectRelocationType::kIMTConflictTable:
       return Bin::kIMTConflictTable;
+    case NativeObjectRelocationType::kGcRootPointer:
+      return Bin::kMetadata;
   }
   UNREACHABLE();
 }
diff --git a/dex2oat/linker/image_writer.h b/dex2oat/linker/image_writer.h
index e019a50..06c694c 100644
--- a/dex2oat/linker/image_writer.h
+++ b/dex2oat/linker/image_writer.h
@@ -209,6 +209,8 @@
     kIMTConflictTable,
     // Runtime methods (always clean, do not have a length prefix array).
     kRuntimeMethod,
+    // Metadata bin for data that is temporary during image lifetime.
+    kMetadata,
     // Dex cache arrays have a special slot for PC-relative addressing. Since they are
     // huge, and as such their dirtiness is not important for the clean/dirty separation,
     // we arbitrarily keep them at the end of the native data.
@@ -226,6 +228,7 @@
     kArtMethodArrayClean,
     kArtMethodDirty,
     kArtMethodArrayDirty,
+    kGcRootPointer,
     kRuntimeMethod,
     kIMTable,
     kIMTConflictTable,
diff --git a/oatdump/oatdump.cc b/oatdump/oatdump.cc
index 86a36f2..0610b97 100644
--- a/oatdump/oatdump.cc
+++ b/oatdump/oatdump.cc
@@ -1953,6 +1953,8 @@
     const auto& dex_cache_arrays_section = image_header_.GetDexCacheArraysSection();
     const auto& intern_section = image_header_.GetInternedStringsSection();
     const auto& class_table_section = image_header_.GetClassTableSection();
+    const auto& sro_section = image_header_.GetImageStringReferenceOffsetsSection();
+    const auto& metadata_section = image_header_.GetMetadataSection();
     const auto& bitmap_section = image_header_.GetImageBitmapSection();
 
     stats_.header_bytes = header_bytes;
@@ -1999,6 +2001,9 @@
     stats_.dex_cache_arrays_bytes += dex_cache_arrays_section.Size();
     stats_.interned_strings_bytes += intern_section.Size();
     stats_.class_table_bytes += class_table_section.Size();
+    stats_.sro_offset_bytes += sro_section.Size();
+    stats_.metadata_bytes += metadata_section.Size();
+
     stats_.Dump(os, indent_os);
     os << "\n";
 
@@ -2415,55 +2420,38 @@
 
  public:
   struct Stats {
-    size_t oat_file_bytes;
-    size_t file_bytes;
+    size_t oat_file_bytes = 0u;
+    size_t file_bytes = 0u;
 
-    size_t header_bytes;
-    size_t object_bytes;
-    size_t art_field_bytes;
-    size_t art_method_bytes;
-    size_t dex_cache_arrays_bytes;
-    size_t interned_strings_bytes;
-    size_t class_table_bytes;
-    size_t bitmap_bytes;
-    size_t alignment_bytes;
+    size_t header_bytes = 0u;
+    size_t object_bytes = 0u;
+    size_t art_field_bytes = 0u;
+    size_t art_method_bytes = 0u;
+    size_t dex_cache_arrays_bytes = 0u;
+    size_t interned_strings_bytes = 0u;
+    size_t class_table_bytes = 0u;
+    size_t sro_offset_bytes = 0u;
+    size_t metadata_bytes = 0u;
+    size_t bitmap_bytes = 0u;
+    size_t alignment_bytes = 0u;
 
-    size_t managed_code_bytes;
-    size_t managed_code_bytes_ignoring_deduplication;
-    size_t native_to_managed_code_bytes;
-    size_t class_initializer_code_bytes;
-    size_t large_initializer_code_bytes;
-    size_t large_method_code_bytes;
+    size_t managed_code_bytes = 0u;
+    size_t managed_code_bytes_ignoring_deduplication = 0u;
+    size_t native_to_managed_code_bytes = 0u;
+    size_t class_initializer_code_bytes = 0u;
+    size_t large_initializer_code_bytes = 0u;
+    size_t large_method_code_bytes = 0u;
 
-    size_t vmap_table_bytes;
+    size_t vmap_table_bytes = 0u;
 
-    size_t dex_instruction_bytes;
+    size_t dex_instruction_bytes = 0u;
 
     std::vector<ArtMethod*> method_outlier;
     std::vector<size_t> method_outlier_size;
     std::vector<double> method_outlier_expansion;
     std::vector<std::pair<std::string, size_t>> oat_dex_file_sizes;
 
-    Stats()
-        : oat_file_bytes(0),
-          file_bytes(0),
-          header_bytes(0),
-          object_bytes(0),
-          art_field_bytes(0),
-          art_method_bytes(0),
-          dex_cache_arrays_bytes(0),
-          interned_strings_bytes(0),
-          class_table_bytes(0),
-          bitmap_bytes(0),
-          alignment_bytes(0),
-          managed_code_bytes(0),
-          managed_code_bytes_ignoring_deduplication(0),
-          native_to_managed_code_bytes(0),
-          class_initializer_code_bytes(0),
-          large_initializer_code_bytes(0),
-          large_method_code_bytes(0),
-          vmap_table_bytes(0),
-          dex_instruction_bytes(0) {}
+    Stats() {}
 
     struct SizeAndCount {
       SizeAndCount(size_t bytes_in, size_t count_in) : bytes(bytes_in), count(count_in) {}
@@ -2617,6 +2605,8 @@
                                   "dex_cache_arrays_bytes =  %8zd (%2.0f%% of art file bytes)\n"
                                   "interned_string_bytes  =  %8zd (%2.0f%% of art file bytes)\n"
                                   "class_table_bytes      =  %8zd (%2.0f%% of art file bytes)\n"
+                                  "sro_bytes              =  %8zd (%2.0f%% of art file bytes)\n"
+                                  "metadata_bytes         =  %8zd (%2.0f%% of art file bytes)\n"
                                   "bitmap_bytes           =  %8zd (%2.0f%% of art file bytes)\n"
                                   "alignment_bytes        =  %8zd (%2.0f%% of art file bytes)\n\n",
                                   header_bytes, PercentOfFileBytes(header_bytes),
@@ -2628,13 +2618,15 @@
                                   interned_strings_bytes,
                                   PercentOfFileBytes(interned_strings_bytes),
                                   class_table_bytes, PercentOfFileBytes(class_table_bytes),
+                                  sro_offset_bytes, PercentOfFileBytes(sro_offset_bytes),
+                                  metadata_bytes, PercentOfFileBytes(metadata_bytes),
                                   bitmap_bytes, PercentOfFileBytes(bitmap_bytes),
                                   alignment_bytes, PercentOfFileBytes(alignment_bytes))
             << std::flush;
         CHECK_EQ(file_bytes,
                  header_bytes + object_bytes + art_field_bytes + art_method_bytes +
                  dex_cache_arrays_bytes + interned_strings_bytes + class_table_bytes +
-                 bitmap_bytes + alignment_bytes);
+                 sro_offset_bytes + metadata_bytes + bitmap_bytes + alignment_bytes);
       }
 
       os << "object_bytes breakdown:\n";
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index e240167..9b45b48 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -1415,9 +1415,9 @@
   for (size_t offset_index = 0; offset_index < num_string_offsets; ++offset_index) {
     uint32_t base_offset = sro_base[offset_index].first;
 
-    if (HasDexCacheNativeRefTag(base_offset)) {
-      base_offset = ClearDexCacheNativeRefTag(base_offset);
-      DCHECK_ALIGNED(base_offset,  2);
+    if (HasDexCacheStringNativeRefTag(base_offset)) {
+      base_offset = ClearDexCacheNativeRefTags(base_offset);
+      DCHECK_ALIGNED(base_offset, 2);
 
       ObjPtr<mirror::DexCache> dex_cache =
           reinterpret_cast<mirror::DexCache*>(space->Begin() + base_offset);
@@ -1437,10 +1437,27 @@
         dex_cache->GetStrings()[string_index].store(
             mirror::StringDexCachePair(it->second, source.index));
       }
+    } else if (HasDexCachePreResolvedStringNativeRefTag(base_offset)) {
+      base_offset = ClearDexCacheNativeRefTags(base_offset);
+      DCHECK_ALIGNED(base_offset, 2);
 
+      ObjPtr<mirror::DexCache> dex_cache =
+          reinterpret_cast<mirror::DexCache*>(space->Begin() + base_offset);
+      uint32_t string_index = sro_base[offset_index].second;
+
+      ObjPtr<mirror::String> referred_string =
+          dex_cache->GetPreResolvedStrings()[string_index].Read();
+      DCHECK(referred_string != nullptr);
+
+      auto it = intern_remap.find(referred_string.Ptr());
+      if (it != intern_remap.end()) {
+        // Because we are not using a helper function we need to mark the GC card manually.
+        WriteBarrier::ForEveryFieldWrite(dex_cache);
+        dex_cache->GetPreResolvedStrings()[string_index] = GcRoot<mirror::String>(it->second);
+      }
     } else {
       uint32_t raw_member_offset = sro_base[offset_index].second;
-      DCHECK_ALIGNED(base_offset,  2);
+      DCHECK_ALIGNED(base_offset, 2);
       DCHECK_ALIGNED(raw_member_offset, 2);
 
       ObjPtr<mirror::Object> obj_ptr =
diff --git a/runtime/class_linker_test.cc b/runtime/class_linker_test.cc
index 91636dc..56fdd06 100644
--- a/runtime/class_linker_test.cc
+++ b/runtime/class_linker_test.cc
@@ -662,12 +662,14 @@
   DexCacheOffsets() : CheckOffsets<mirror::DexCache>(false, "Ljava/lang/DexCache;") {
     addOffset(OFFSETOF_MEMBER(mirror::DexCache, dex_file_), "dexFile");
     addOffset(OFFSETOF_MEMBER(mirror::DexCache, location_), "location");
+    addOffset(OFFSETOF_MEMBER(mirror::DexCache, num_preresolved_strings_), "numPreResolvedStrings");
     addOffset(OFFSETOF_MEMBER(mirror::DexCache, num_resolved_call_sites_), "numResolvedCallSites");
     addOffset(OFFSETOF_MEMBER(mirror::DexCache, num_resolved_fields_), "numResolvedFields");
     addOffset(OFFSETOF_MEMBER(mirror::DexCache, num_resolved_method_types_), "numResolvedMethodTypes");
     addOffset(OFFSETOF_MEMBER(mirror::DexCache, num_resolved_methods_), "numResolvedMethods");
     addOffset(OFFSETOF_MEMBER(mirror::DexCache, num_resolved_types_), "numResolvedTypes");
     addOffset(OFFSETOF_MEMBER(mirror::DexCache, num_strings_), "numStrings");
+    addOffset(OFFSETOF_MEMBER(mirror::DexCache, preresolved_strings_), "preResolvedStrings");
     addOffset(OFFSETOF_MEMBER(mirror::DexCache, resolved_call_sites_), "resolvedCallSites");
     addOffset(OFFSETOF_MEMBER(mirror::DexCache, resolved_fields_), "resolvedFields");
     addOffset(OFFSETOF_MEMBER(mirror::DexCache, resolved_method_types_), "resolvedMethodTypes");
diff --git a/runtime/gc/accounting/space_bitmap.h b/runtime/gc/accounting/space_bitmap.h
index 6a3faef..fcc3007 100644
--- a/runtime/gc/accounting/space_bitmap.h
+++ b/runtime/gc/accounting/space_bitmap.h
@@ -151,6 +151,12 @@
   void VisitMarkedRange(uintptr_t visit_begin, uintptr_t visit_end, Visitor&& visitor) const
       NO_THREAD_SAFETY_ANALYSIS;
 
+  // Visit all of the set bits in HeapBegin(), HeapLimit().
+  template <typename Visitor>
+  void VisitAllMarked(Visitor&& visitor) const {
+    VisitMarkedRange(HeapBegin(), HeapLimit(), visitor);
+  }
+
   // Visits set bits in address order.  The callback is not permitted to change the bitmap bits or
   // max during the traversal.
   template <typename Visitor>
diff --git a/runtime/gc/space/image_space.cc b/runtime/gc/space/image_space.cc
index 9e67957..db4a48c 100644
--- a/runtime/gc/space/image_space.cc
+++ b/runtime/gc/space/image_space.cc
@@ -1172,6 +1172,19 @@
           }
           dex_cache->FixupResolvedCallSites<kWithoutReadBarrier>(new_call_sites, fixup_adapter);
         }
+
+        GcRoot<mirror::String>* preresolved_strings = dex_cache->GetPreResolvedStrings();
+        if (preresolved_strings != nullptr) {
+          GcRoot<mirror::String>* new_array = fixup_adapter.ForwardObject(preresolved_strings);
+          if (preresolved_strings != new_array) {
+            dex_cache->SetPreResolvedStrings(new_array);
+          }
+          const size_t num_preresolved_strings = dex_cache->NumPreResolvedStrings();
+          for (size_t j = 0; j < num_preresolved_strings; ++j) {
+            new_array[j] = GcRoot<mirror::String>(
+                fixup_adapter(new_array[j].Read<kWithoutReadBarrier>()));
+          }
+        }
       }
     }
     {
@@ -1731,6 +1744,10 @@
           dex_cache,
           mirror::DexCache::ResolvedCallSitesOffset(),
           dex_cache->NumResolvedCallSites<kVerifyNone>());
+      FixupDexCacheArray<GcRoot<mirror::String>>(
+          dex_cache,
+          mirror::DexCache::PreResolvedStringsOffset(),
+          dex_cache->NumPreResolvedStrings<kVerifyNone>());
     }
 
    private:
@@ -1775,6 +1792,11 @@
       PatchGcRoot(diff_, &array[index]);
     }
 
+    void FixupDexCacheArrayEntry(GcRoot<mirror::String>* array, uint32_t index)
+        REQUIRES_SHARED(Locks::mutator_lock_) {
+      PatchGcRoot(diff_, &array[index]);
+    }
+
     template <typename EntryType>
     void FixupDexCacheArray(mirror::DexCache* dex_cache,
                             MemberOffset array_offset,
diff --git a/runtime/image.cc b/runtime/image.cc
index e7f4486..376742a 100644
--- a/runtime/image.cc
+++ b/runtime/image.cc
@@ -26,7 +26,7 @@
 namespace art {
 
 const uint8_t ImageHeader::kImageMagic[] = { 'a', 'r', 't', '\n' };
-const uint8_t ImageHeader::kImageVersion[] = { '0', '6', '5', '\0' };  // Remove relocation section.
+const uint8_t ImageHeader::kImageVersion[] = { '0', '6', '6', '\0' };  // Add metadata section.
 
 ImageHeader::ImageHeader(uint32_t image_begin,
                          uint32_t image_size,
diff --git a/runtime/image.h b/runtime/image.h
index 0dec5f7..d925956 100644
--- a/runtime/image.h
+++ b/runtime/image.h
@@ -236,6 +236,7 @@
     kSectionInternedStrings,
     kSectionClassTable,
     kSectionStringReferenceOffsets,
+    kSectionMetadata,
     kSectionImageBitmap,
     kSectionCount,  // Number of elements in enum.
   };
@@ -293,6 +294,10 @@
     return GetImageSection(kSectionStringReferenceOffsets);
   }
 
+  const ImageSection& GetMetadataSection() const {
+    return GetImageSection(kSectionMetadata);
+  }
+
   const ImageSection& GetImageBitmapSection() const {
     return GetImageSection(kSectionImageBitmap);
   }
@@ -462,22 +467,45 @@
  * to managed objects and pointers to native reference arrays.
  */
 template<typename T>
-T SetDexCacheNativeRefTag(T val) {
+T SetDexCacheStringNativeRefTag(T val) {
   static_assert(std::is_integral<T>::value, "Expected integral type.");
 
   return val | 1u;
 }
 
 /*
+ * Tags the second last bit.  Used by AppImage logic to differentiate between pointers
+ * to managed objects and pointers to native reference arrays.
+ */
+template<typename T>
+T SetDexCachePreResolvedStringNativeRefTag(T val) {
+  static_assert(std::is_integral<T>::value, "Expected integral type.");
+
+  return val | 2u;
+}
+
+/*
  * Retrieves the value of the last bit.  Used by AppImage logic to
  * differentiate between pointers to managed objects and pointers to native
  * reference arrays.
  */
 template<typename T>
-bool HasDexCacheNativeRefTag(T val) {
+bool HasDexCacheStringNativeRefTag(T val) {
   static_assert(std::is_integral<T>::value, "Expected integral type.");
 
-  return (val & 1u) == 1u;
+  return (val & 1u) != 0u;
+}
+
+/*
+ * Retrieves the value of the second last bit.  Used by AppImage logic to
+ * differentiate between pointers to managed objects and pointers to native
+ * reference arrays.
+ */
+template<typename T>
+bool HasDexCachePreResolvedStringNativeRefTag(T val) {
+  static_assert(std::is_integral<T>::value, "Expected integral type.");
+
+  return (val & 2u) != 0u;
 }
 
 /*
@@ -486,10 +514,10 @@
  * reference arrays.
  */
 template<typename T>
-T ClearDexCacheNativeRefTag(T val) {
+T ClearDexCacheNativeRefTags(T val) {
   static_assert(std::is_integral<T>::value, "Expected integral type.");
 
-  return val & ~1u;
+  return val & ~3u;
 }
 
 std::ostream& operator<<(std::ostream& os, const ImageHeader::ImageMethod& policy);
diff --git a/runtime/mirror/dex_cache-inl.h b/runtime/mirror/dex_cache-inl.h
index 13eaf3d..47b621a 100644
--- a/runtime/mirror/dex_cache-inl.h
+++ b/runtime/mirror/dex_cache-inl.h
@@ -84,6 +84,15 @@
 }
 
 inline String* DexCache::GetResolvedString(dex::StringIndex string_idx) {
+  const uint32_t num_preresolved_strings = NumPreResolvedStrings();
+  if (num_preresolved_strings != 0u) {
+    DCHECK_LT(string_idx.index_, num_preresolved_strings);
+    DCHECK_EQ(num_preresolved_strings, GetDexFile()->NumStringIds());
+    mirror::String* string = GetPreResolvedStrings()[string_idx.index_].Read();
+    if (LIKELY(string != nullptr)) {
+      return string;
+    }
+  }
   return GetStrings()[StringSlotIndex(string_idx)].load(
       std::memory_order_relaxed).GetObjectForIndex(string_idx.index_);
 }
@@ -101,6 +110,18 @@
   WriteBarrier::ForEveryFieldWrite(this);
 }
 
+inline void DexCache::SetPreResolvedString(dex::StringIndex string_idx,
+                                           ObjPtr<String> resolved) {
+  DCHECK(resolved != nullptr);
+  DCHECK_LT(string_idx.index_, GetDexFile()->NumStringIds());
+  GetPreResolvedStrings()[string_idx.index_] = GcRoot<mirror::String>(resolved);
+  Runtime* const runtime = Runtime::Current();
+  CHECK(runtime->IsAotCompiler());
+  CHECK(!runtime->IsActiveTransaction());
+  // TODO: Fine-grained marking, so that we don't need to go through all arrays in full.
+  WriteBarrier::ForEveryFieldWrite(this);
+}
+
 inline void DexCache::ClearString(dex::StringIndex string_idx) {
   DCHECK(Runtime::Current()->IsAotCompiler());
   uint32_t slot_idx = StringSlotIndex(string_idx);
@@ -344,6 +365,12 @@
     for (size_t i = 0; i != num_call_sites; ++i) {
       visitor.VisitRootIfNonNull(resolved_call_sites[i].AddressWithoutBarrier());
     }
+
+    GcRoot<mirror::String>* const preresolved_strings = GetPreResolvedStrings();
+    const size_t num_preresolved_strings = NumPreResolvedStrings();
+    for (size_t i = 0; i != num_preresolved_strings; ++i) {
+      visitor.VisitRootIfNonNull(preresolved_strings[i].AddressWithoutBarrier());
+    }
   }
 }
 
diff --git a/runtime/mirror/dex_cache.cc b/runtime/mirror/dex_cache.cc
index 661f954..8d2b838 100644
--- a/runtime/mirror/dex_cache.cc
+++ b/runtime/mirror/dex_cache.cc
@@ -172,6 +172,21 @@
                   dex_file->NumCallSiteIds());
 }
 
+void DexCache::AddPreResolvedStringsArray() {
+  DCHECK_EQ(NumPreResolvedStrings(), 0u);
+  Thread* const self = Thread::Current();
+  LinearAlloc* linear_alloc = Runtime::Current()->GetLinearAlloc();
+  const size_t num_strings = GetDexFile()->NumStringIds();
+  SetField32<false>(NumPreResolvedStringsOffset(), num_strings);
+  GcRoot<mirror::String>* strings =
+      linear_alloc->AllocArray<GcRoot<mirror::String>>(self, num_strings);
+  CHECK(strings != nullptr);
+  SetPreResolvedStrings(strings);
+  for (size_t i = 0; i < GetDexFile()->NumStringIds(); ++i) {
+    CHECK(GetPreResolvedStrings()[i].Read() == nullptr);
+  }
+}
+
 void DexCache::Init(const DexFile* dex_file,
                     ObjPtr<String> location,
                     StringDexCacheType* strings,
diff --git a/runtime/mirror/dex_cache.h b/runtime/mirror/dex_cache.h
index 6149f9c..58b199d 100644
--- a/runtime/mirror/dex_cache.h
+++ b/runtime/mirror/dex_cache.h
@@ -217,6 +217,10 @@
     return OFFSET_OF_OBJECT_MEMBER(DexCache, strings_);
   }
 
+  static constexpr MemberOffset PreResolvedStringsOffset() {
+    return OFFSET_OF_OBJECT_MEMBER(DexCache, preresolved_strings_);
+  }
+
   static constexpr MemberOffset ResolvedTypesOffset() {
     return OFFSET_OF_OBJECT_MEMBER(DexCache, resolved_types_);
   }
@@ -241,6 +245,10 @@
     return OFFSET_OF_OBJECT_MEMBER(DexCache, num_strings_);
   }
 
+  static constexpr MemberOffset NumPreResolvedStringsOffset() {
+    return OFFSET_OF_OBJECT_MEMBER(DexCache, num_preresolved_strings_);
+  }
+
   static constexpr MemberOffset NumResolvedTypesOffset() {
     return OFFSET_OF_OBJECT_MEMBER(DexCache, num_resolved_types_);
   }
@@ -261,12 +269,20 @@
     return OFFSET_OF_OBJECT_MEMBER(DexCache, num_resolved_call_sites_);
   }
 
+  static constexpr size_t PreResolvedStringsAlignment() {
+    return alignof(GcRoot<mirror::String>);
+  }
+
   String* GetResolvedString(dex::StringIndex string_idx) ALWAYS_INLINE
       REQUIRES_SHARED(Locks::mutator_lock_);
 
   void SetResolvedString(dex::StringIndex string_idx, ObjPtr<mirror::String> resolved) ALWAYS_INLINE
       REQUIRES_SHARED(Locks::mutator_lock_);
 
+  void SetPreResolvedString(dex::StringIndex string_idx,
+                            ObjPtr<mirror::String> resolved)
+      ALWAYS_INLINE REQUIRES_SHARED(Locks::mutator_lock_);
+
   // Clear a string for a string_idx, used to undo string intern transactions to make sure
   // the string isn't kept live.
   void ClearString(dex::StringIndex string_idx) REQUIRES_SHARED(Locks::mutator_lock_);
@@ -318,10 +334,21 @@
     return GetFieldPtr64<StringDexCacheType*, kVerifyFlags>(StringsOffset());
   }
 
+  template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags>
+  GcRoot<mirror::String>* GetPreResolvedStrings() ALWAYS_INLINE
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+    return GetFieldPtr64<GcRoot<mirror::String>*, kVerifyFlags>(PreResolvedStringsOffset());
+  }
+
   void SetStrings(StringDexCacheType* strings) ALWAYS_INLINE REQUIRES_SHARED(Locks::mutator_lock_) {
     SetFieldPtr<false>(StringsOffset(), strings);
   }
 
+  void SetPreResolvedStrings(GcRoot<mirror::String>* strings)
+      ALWAYS_INLINE REQUIRES_SHARED(Locks::mutator_lock_) {
+    SetFieldPtr<false>(PreResolvedStringsOffset(), strings);
+  }
+
   template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags>
   TypeDexCacheType* GetResolvedTypes() ALWAYS_INLINE REQUIRES_SHARED(Locks::mutator_lock_) {
     return GetFieldPtr<TypeDexCacheType*, kVerifyFlags>(ResolvedTypesOffset());
@@ -384,6 +411,11 @@
   }
 
   template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags>
+  size_t NumPreResolvedStrings() REQUIRES_SHARED(Locks::mutator_lock_) {
+    return GetField32<kVerifyFlags>(NumPreResolvedStringsOffset());
+  }
+
+  template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags>
   size_t NumResolvedTypes() REQUIRES_SHARED(Locks::mutator_lock_) {
     return GetField32<kVerifyFlags>(NumResolvedTypesOffset());
   }
@@ -429,12 +461,18 @@
                                    NativeDexCachePair<T> pair,
                                    PointerSize ptr_size);
 
+  static size_t PreResolvedStringsSize(size_t num_strings) {
+    return sizeof(GcRoot<mirror::String>) * num_strings;
+  }
+
   uint32_t StringSlotIndex(dex::StringIndex string_idx) REQUIRES_SHARED(Locks::mutator_lock_);
   uint32_t TypeSlotIndex(dex::TypeIndex type_idx) REQUIRES_SHARED(Locks::mutator_lock_);
   uint32_t FieldSlotIndex(uint32_t field_idx) REQUIRES_SHARED(Locks::mutator_lock_);
   uint32_t MethodSlotIndex(uint32_t method_idx) REQUIRES_SHARED(Locks::mutator_lock_);
   uint32_t MethodTypeSlotIndex(dex::ProtoIndex proto_idx) REQUIRES_SHARED(Locks::mutator_lock_);
 
+  void AddPreResolvedStringsArray() REQUIRES_SHARED(Locks::mutator_lock_);
+
  private:
   void Init(const DexFile* dex_file,
             ObjPtr<String> location,
@@ -516,22 +554,25 @@
 #endif
 
   HeapReference<String> location_;
-  // Number of elements in the call_sites_ array. Note that this appears here
-  // because of our packing logic for 32 bit fields.
-  uint32_t num_resolved_call_sites_;
+  // Number of elements in the preresolved_strings_ array. Note that this appears here because of
+  // our packing logic for 32 bit fields.
+  uint32_t num_preresolved_strings_;
 
-  uint64_t dex_file_;               // const DexFile*
-  uint64_t resolved_call_sites_;    // GcRoot<CallSite>* array with num_resolved_call_sites_
-                                    // elements.
-  uint64_t resolved_fields_;        // std::atomic<FieldDexCachePair>*, array with
-                                    // num_resolved_fields_ elements.
-  uint64_t resolved_method_types_;  // std::atomic<MethodTypeDexCachePair>* array with
-                                    // num_resolved_method_types_ elements.
-  uint64_t resolved_methods_;       // ArtMethod*, array with num_resolved_methods_ elements.
-  uint64_t resolved_types_;         // TypeDexCacheType*, array with num_resolved_types_ elements.
-  uint64_t strings_;                // std::atomic<StringDexCachePair>*, array with num_strings_
-                                    // elements.
+  uint64_t dex_file_;                // const DexFile*
+  uint64_t preresolved_strings_;     // GcRoot<mirror::String*> array with num_preresolved_strings
+                                     // elements.
+  uint64_t resolved_call_sites_;     // GcRoot<CallSite>* array with num_resolved_call_sites_
+                                     // elements.
+  uint64_t resolved_fields_;         // std::atomic<FieldDexCachePair>*, array with
+                                     // num_resolved_fields_ elements.
+  uint64_t resolved_method_types_;   // std::atomic<MethodTypeDexCachePair>* array with
+                                     // num_resolved_method_types_ elements.
+  uint64_t resolved_methods_;        // ArtMethod*, array with num_resolved_methods_ elements.
+  uint64_t resolved_types_;          // TypeDexCacheType*, array with num_resolved_types_ elements.
+  uint64_t strings_;                 // std::atomic<StringDexCachePair>*, array with num_strings_
+                                     // elements.
 
+  uint32_t num_resolved_call_sites_;    // Number of elements in the call_sites_ array.
   uint32_t num_resolved_fields_;        // Number of elements in the resolved_fields_ array.
   uint32_t num_resolved_method_types_;  // Number of elements in the resolved_method_types_ array.
   uint32_t num_resolved_methods_;       // Number of elements in the resolved_methods_ array.