Use mmapped boot image intern table for PIC app HLoadString.

Implement new HLoadString load kind for boot image strings
referenced by PIC-compiled apps (i.e. prebuilts) that uses
PC-relative load from a boot image InternTable mmapped into
the apps .bss. This reduces the size of the PIC prebuilts
that reference boot image strings compared to the kBssEntry
as we can completely avoid the slow path and stack map.

We separate the InternedStrings and ClassTable sections of
the boot image (.art) file from the rest, aligning the
start of the InternedStrings section to a page boundary.
This may actually increase the size of the boot image file
by a page but it also allows mprotecting() these tables as
read-only. The ClassTable section is included in
anticipation of a similar load kind for HLoadClass.

Prebuilt services.odex for aosp_angler-userdebug (arm64):
  - before: 20862776
  - after: 20308512 (-541KiB)
Note that 92KiB savings could have been achieved by simply
avoiding the read barrier, similar to the HLoadClass flag
IsInBootImage(). Such flag is now unnecessary.

Test: m test-art-host-gtest
Test: testrunner.py --host
Test: testrunner.py --host --pictest
Test: testrunner.py --target on Nexus 6P.
Test: testrunner.py --target --pictest on Nexus 6P.
Test: Nexus 6P boots.
Bug: 31951624
Change-Id: I5f2bf1fc0bb36a8483244317cfdfa69e192ef6c5
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index 4712c93..77ce39c 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -1760,6 +1760,17 @@
     header.VisitPackedArtMethods(&visitor, space->Begin(), image_pointer_size_);
   }
 
+  if (!app_image) {
+    // Make the string intern table and class table immutable for boot image.
+    // PIC app oat files may mmap a read-only copy into their own .bss section,
+    // so enforce that the data in the boot image tables remains unchanged.
+    //
+    // We cannot do that for app image even after the fixup as some interned
+    // String references may actually end up pointing to moveable Strings.
+    uint8_t* const_section_begin = space->Begin() + header.GetBootImageConstantTablesOffset();
+    mprotect(const_section_begin, header.GetBootImageConstantTablesSize(), PROT_READ);
+  }
+
   ClassTable* class_table = nullptr;
   {
     WriterMutexLock mu(self, *Locks::classlinker_classes_lock_);
diff --git a/runtime/class_linker.h b/runtime/class_linker.h
index 783ec74..8c2f0a5 100644
--- a/runtime/class_linker.h
+++ b/runtime/class_linker.h
@@ -1278,6 +1278,7 @@
   friend class ImageWriter;  // for GetClassRoots
   friend class JniCompilerTest;  // for GetRuntimeQuickGenericJniStub
   friend class JniInternalTest;  // for GetRuntimeQuickGenericJniStub
+  friend class OatWriter;  // for boot image string table slot address lookup.
   friend class VMClassLoader;  // for LookupClass and FindClassInBaseDexClassLoader.
   ART_FRIEND_TEST(ClassLinkerTest, RegisterDexFileName);  // for DexLock, and RegisterDexFileLocked
   ART_FRIEND_TEST(mirror::DexCacheMethodHandlesTest, Open);  // for AllocDexCache
diff --git a/runtime/image.cc b/runtime/image.cc
index 8debc71..c8581c1 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', '4', '7', '\0' };  // Smaller ArtMethod.
+const uint8_t ImageHeader::kImageVersion[] = { '0', '4', '8', '\0' };  // Map boot image tables.
 
 ImageHeader::ImageHeader(uint32_t image_begin,
                          uint32_t image_size,
diff --git a/runtime/image.h b/runtime/image.h
index 42abffc..2b24087 100644
--- a/runtime/image.h
+++ b/runtime/image.h
@@ -19,6 +19,7 @@
 
 #include <string.h>
 
+#include "base/bit_utils.h"
 #include "base/enums.h"
 #include "globals.h"
 #include "mirror/object.h"
@@ -311,6 +312,22 @@
     return boot_image_size_ != 0u;
   }
 
+  uint32_t GetBootImageConstantTablesOffset() const {
+    // Interned strings table and class table for boot image are mmapped read only.
+    DCHECK(!IsAppImage());
+    const ImageSection& interned_strings = GetInternedStringsSection();
+    DCHECK_ALIGNED(interned_strings.Offset(), kPageSize);
+    return interned_strings.Offset();
+  }
+
+  uint32_t GetBootImageConstantTablesSize() const {
+    uint32_t start_offset = GetBootImageConstantTablesOffset();
+    const ImageSection& class_table = GetClassTableSection();
+    DCHECK_LE(start_offset, class_table.Offset());
+    size_t tables_size = class_table.Offset() + class_table.Size() - start_offset;
+    return RoundUp(tables_size, kPageSize);
+  }
+
   // Visit ArtMethods in the section starting at base. Includes runtime methods.
   // TODO: Delete base parameter if it is always equal to GetImageBegin.
   void VisitPackedArtMethods(ArtMethodVisitor* visitor,
diff --git a/runtime/intern_table.h b/runtime/intern_table.h
index 8714840..3e3abbd 100644
--- a/runtime/intern_table.h
+++ b/runtime/intern_table.h
@@ -223,6 +223,7 @@
     // modifying the zygote intern table. The back of table is modified when strings are interned.
     std::vector<UnorderedSet> tables_;
 
+    friend class OatWriter;  // for boot image string table slot address lookup.
     ART_FRIEND_TEST(InternTableTest, CrossHash);
   };
 
@@ -282,6 +283,7 @@
   // Weak root state, used for concurrent system weak processing and more.
   gc::WeakRootState weak_root_state_ GUARDED_BY(Locks::intern_table_lock_);
 
+  friend class OatWriter;  // for boot image string table slot address lookup.
   friend class Transaction;
   ART_FRIEND_TEST(InternTableTest, CrossHash);
   DISALLOW_COPY_AND_ASSIGN(InternTable);
diff --git a/runtime/oat.h b/runtime/oat.h
index ab7c42e..a3e8eef 100644
--- a/runtime/oat.h
+++ b/runtime/oat.h
@@ -32,8 +32,8 @@
 class PACKED(4) OatHeader {
  public:
   static constexpr uint8_t kOatMagic[] = { 'o', 'a', 't', '\n' };
-  // Last oat version changed reason: Remove DexCache arrays from .bss.
-  static constexpr uint8_t kOatVersion[] = { '1', '3', '3', '\0' };
+  // Last oat version changed reason: Map boot image InternTable and ClassTable into app .bss.
+  static constexpr uint8_t kOatVersion[] = { '1', '3', '4', '\0' };
 
   static constexpr const char* kImageLocationKey = "image-location";
   static constexpr const char* kDex2OatCmdLineKey = "dex2oat-cmdline";
diff --git a/runtime/oat_file.cc b/runtime/oat_file.cc
index 200681e..6515cfa 100644
--- a/runtime/oat_file.cc
+++ b/runtime/oat_file.cc
@@ -44,6 +44,7 @@
 #include "elf_file.h"
 #include "elf_utils.h"
 #include "gc_root.h"
+#include "gc/space/image_space.h"
 #include "mem_map.h"
 #include "mirror/class.h"
 #include "mirror/object-inl.h"
@@ -278,6 +279,37 @@
   return true;
 }
 
+static inline bool MapConstantTables(const gc::space::ImageSpace* space,
+                                     uint8_t* address) {
+  // If MREMAP_DUP is ever merged to Linux kernel, use it to avoid the unnecessary open()/close().
+  // Note: The current approach relies on the filename still referencing the same inode.
+
+  File file(space->GetImageFilename(), O_RDONLY, /* checkUsage */ false);
+  if (!file.IsOpened()) {
+    LOG(ERROR) << "Failed to open boot image file " << space->GetImageFilename();
+    return false;
+  }
+
+  uint32_t offset = space->GetImageHeader().GetBootImageConstantTablesOffset();
+  uint32_t size = space->GetImageHeader().GetBootImageConstantTablesSize();
+  std::string error_msg;
+  std::unique_ptr<MemMap> mem_map(MemMap::MapFileAtAddress(address,
+                                                           size,
+                                                           PROT_READ,
+                                                           MAP_PRIVATE,
+                                                           file.Fd(),
+                                                           offset,
+                                                           /* low_4gb */ false,
+                                                           /* reuse */ true,
+                                                           file.GetPath().c_str(),
+                                                           &error_msg));
+  if (mem_map == nullptr) {
+    LOG(ERROR) << "Failed to mmap boot image tables from file " << space->GetImageFilename();
+    return false;
+  }
+  return true;
+}
+
 bool OatFileBase::Setup(const char* abs_dex_location, std::string* error_msg) {
   if (!GetOatHeader().IsValid()) {
     std::string cause = GetOatHeader().GetValidationErrorMessage();
@@ -339,15 +371,12 @@
     return false;
   }
 
-  if (bss_methods_ != nullptr && bss_methods_ != bss_begin_) {
-    *error_msg = StringPrintf("In oat file '%s' found unexpected .bss gap before 'oatbssmethods': "
-                                  "begin = %p, methods = %p",
-                              GetLocation().c_str(),
-                              bss_begin_,
-                              bss_methods_);
-    return false;
-  }
-
+  uint8_t* after_tables =
+      (bss_methods_ != nullptr) ? bss_methods_ : bss_roots_;  // May be null.
+  uint8_t* boot_image_tables = (bss_begin_ == after_tables) ? nullptr : bss_begin_;
+  uint8_t* boot_image_tables_end =
+      (bss_begin_ == after_tables) ? nullptr : (after_tables != nullptr) ? after_tables : bss_end_;
+  DCHECK_EQ(boot_image_tables != nullptr, boot_image_tables_end != nullptr);
   uint32_t dex_file_count = GetOatHeader().GetDexFileCount();
   oat_dex_files_storage_.reserve(dex_file_count);
   for (size_t i = 0; i < dex_file_count; i++) {
@@ -605,6 +634,31 @@
     }
   }
 
+  if (boot_image_tables != nullptr) {
+    // Map boot image tables into the .bss. The reserved size must match size of the tables.
+    size_t reserved_size = static_cast<size_t>(boot_image_tables_end - boot_image_tables);
+    size_t tables_size = 0u;
+    for (gc::space::ImageSpace* space : Runtime::Current()->GetHeap()->GetBootImageSpaces()) {
+      tables_size += space->GetImageHeader().GetBootImageConstantTablesSize();
+      DCHECK_ALIGNED(tables_size, kPageSize);
+    }
+    if (tables_size != reserved_size) {
+      *error_msg = StringPrintf("In oat file '%s' found unexpected boot image table sizes, "
+                                    " %zu bytes, should be %zu.",
+                                GetLocation().c_str(),
+                                reserved_size,
+                                tables_size);
+      return false;
+    }
+    for (gc::space::ImageSpace* space : Runtime::Current()->GetHeap()->GetBootImageSpaces()) {
+      uint32_t current_tables_size = space->GetImageHeader().GetBootImageConstantTablesSize();
+      if (current_tables_size != 0u && !MapConstantTables(space, boot_image_tables)) {
+        return false;
+      }
+      boot_image_tables += current_tables_size;
+    }
+    DCHECK(boot_image_tables == boot_image_tables_end);
+  }
   return true;
 }