Compiler changes for boot image extension.

Test: m test-art-host-gtest
Test: testrunnner.py --host --optimizing
Test: aosp_taimen-userdebug boots.
Test: run-gtest.sh
Test: testrunner.py --target --optimizing
Change-Id: I8e999c96ec908f26d8c529edc9d2a3be49a9379a
diff --git a/compiler/driver/compiler_options.cc b/compiler/driver/compiler_options.cc
index 8d09977..cde6ae9 100644
--- a/compiler/driver/compiler_options.cc
+++ b/compiler/driver/compiler_options.cc
@@ -20,11 +20,11 @@
 #include <string_view>
 
 #include "android-base/stringprintf.h"
-#include "android-base/strings.h"
 
 #include "arch/instruction_set.h"
 #include "arch/instruction_set_features.h"
 #include "base/runtime_debug.h"
+#include "base/string_view_cpp20.h"
 #include "base/variant_map.h"
 #include "class_linker.h"
 #include "cmdline_parser.h"
@@ -185,18 +185,23 @@
 }
 
 bool CompilerOptions::IsCoreImageFilename(const std::string& boot_image_filename) {
+  std::string_view filename(boot_image_filename);
+  size_t colon_pos = filename.find(':');
+  if (colon_pos != std::string_view::npos) {
+    filename = filename.substr(0u, colon_pos);
+  }
   // Look for "core.art" or "core-*.art".
-  if (android::base::EndsWith(boot_image_filename, "core.art")) {
+  if (EndsWith(filename, "core.art")) {
     return true;
   }
-  if (!android::base::EndsWith(boot_image_filename, ".art")) {
+  if (!EndsWith(filename, ".art")) {
     return false;
   }
-  size_t slash_pos = boot_image_filename.rfind('/');
+  size_t slash_pos = filename.rfind('/');
   if (slash_pos == std::string::npos) {
-    return android::base::StartsWith(boot_image_filename, "core-");
+    return StartsWith(filename, "core-");
   }
-  return boot_image_filename.compare(slash_pos + 1, 5u, "core-") == 0;
+  return filename.compare(slash_pos + 1, 5u, "core-") == 0;
 }
 
 }  // namespace art
diff --git a/compiler/driver/compiler_options.h b/compiler/driver/compiler_options.h
index 98b15dc..e24da59 100644
--- a/compiler/driver/compiler_options.h
+++ b/compiler/driver/compiler_options.h
@@ -72,6 +72,7 @@
   enum class ImageType : uint8_t {
     kNone,                // JIT or AOT app compilation producing only an oat file but no image.
     kBootImage,           // Creating boot image.
+    kBootImageExtension,  // Creating boot image extension.
     kAppImage,            // Creating app image.
     kApexBootImage,       // Creating the apex image for jit/zygote experiment b/119800099.
   };
@@ -200,6 +201,11 @@
     return image_type_ == ImageType::kApexBootImage;
   }
 
+  // Are we compiling a boot image extension?
+  bool IsBootImageExtension() const {
+    return image_type_ == ImageType::kBootImageExtension;
+  }
+
   bool IsBaseline() const {
     return baseline_;
   }
diff --git a/compiler/optimizing/code_generator_arm64.cc b/compiler/optimizing/code_generator_arm64.cc
index 43d466b..aeef90a 100644
--- a/compiler/optimizing/code_generator_arm64.cc
+++ b/compiler/optimizing/code_generator_arm64.cc
@@ -4101,7 +4101,7 @@
       callee_method = invoke->GetLocations()->InAt(invoke->GetSpecialInputIndex());
       break;
     case HInvokeStaticOrDirect::MethodLoadKind::kBootImageLinkTimePcRelative: {
-      DCHECK(GetCompilerOptions().IsBootImage());
+      DCHECK(GetCompilerOptions().IsBootImage() || GetCompilerOptions().IsBootImageExtension());
       // Add ADRP with its PC-relative method patch.
       vixl::aarch64::Label* adrp_label = NewBootImageMethodPatch(invoke->GetTargetMethod());
       EmitAdrpPlaceholder(adrp_label, XRegisterFrom(temp));
@@ -4461,7 +4461,7 @@
       call_entrypoint_patches_.size() +
       baker_read_barrier_patches_.size();
   linker_patches->reserve(size);
-  if (GetCompilerOptions().IsBootImage()) {
+  if (GetCompilerOptions().IsBootImage() || GetCompilerOptions().IsBootImageExtension()) {
     EmitPcRelativeLinkerPatches<linker::LinkerPatch::RelativeMethodPatch>(
         boot_image_method_patches_, linker_patches);
     EmitPcRelativeLinkerPatches<linker::LinkerPatch::RelativeTypePatch>(
@@ -4688,6 +4688,8 @@
       break;
     }
     case HLoadClass::LoadKind::kBootImageLinkTimePcRelative: {
+      DCHECK(codegen_->GetCompilerOptions().IsBootImage() ||
+             codegen_->GetCompilerOptions().IsBootImageExtension());
       DCHECK_EQ(read_barrier_option, kWithoutReadBarrier);
       // Add ADRP with its PC-relative type patch.
       const DexFile& dex_file = cls->GetDexFile();
@@ -4863,7 +4865,8 @@
 
   switch (load->GetLoadKind()) {
     case HLoadString::LoadKind::kBootImageLinkTimePcRelative: {
-      DCHECK(codegen_->GetCompilerOptions().IsBootImage());
+      DCHECK(codegen_->GetCompilerOptions().IsBootImage() ||
+             codegen_->GetCompilerOptions().IsBootImageExtension());
       // Add ADRP with its PC-relative String patch.
       const DexFile& dex_file = load->GetDexFile();
       const dex::StringIndex string_index = load->GetStringIndex();
diff --git a/compiler/optimizing/code_generator_arm_vixl.cc b/compiler/optimizing/code_generator_arm_vixl.cc
index b72a1a0..b3141bf 100644
--- a/compiler/optimizing/code_generator_arm_vixl.cc
+++ b/compiler/optimizing/code_generator_arm_vixl.cc
@@ -7076,7 +7076,8 @@
       break;
     }
     case HLoadClass::LoadKind::kBootImageLinkTimePcRelative: {
-      DCHECK(codegen_->GetCompilerOptions().IsBootImage());
+      DCHECK(codegen_->GetCompilerOptions().IsBootImage() ||
+             codegen_->GetCompilerOptions().IsBootImageExtension());
       DCHECK_EQ(read_barrier_option, kWithoutReadBarrier);
       CodeGeneratorARMVIXL::PcRelativePatchInfo* labels =
           codegen_->NewBootImageTypePatch(cls->GetDexFile(), cls->GetTypeIndex());
@@ -7300,7 +7301,8 @@
 
   switch (load_kind) {
     case HLoadString::LoadKind::kBootImageLinkTimePcRelative: {
-      DCHECK(codegen_->GetCompilerOptions().IsBootImage());
+      DCHECK(codegen_->GetCompilerOptions().IsBootImage() ||
+             codegen_->GetCompilerOptions().IsBootImageExtension());
       CodeGeneratorARMVIXL::PcRelativePatchInfo* labels =
           codegen_->NewBootImageStringPatch(load->GetDexFile(), load->GetStringIndex());
       codegen_->EmitMovwMovtPlaceholder(labels, out);
@@ -8770,7 +8772,7 @@
       callee_method = invoke->GetLocations()->InAt(invoke->GetSpecialInputIndex());
       break;
     case HInvokeStaticOrDirect::MethodLoadKind::kBootImageLinkTimePcRelative: {
-      DCHECK(GetCompilerOptions().IsBootImage());
+      DCHECK(GetCompilerOptions().IsBootImage() || GetCompilerOptions().IsBootImageExtension());
       PcRelativePatchInfo* labels = NewBootImageMethodPatch(invoke->GetTargetMethod());
       vixl32::Register temp_reg = RegisterFrom(temp);
       EmitMovwMovtPlaceholder(labels, temp_reg);
@@ -9071,7 +9073,7 @@
       call_entrypoint_patches_.size() +
       baker_read_barrier_patches_.size();
   linker_patches->reserve(size);
-  if (GetCompilerOptions().IsBootImage()) {
+  if (GetCompilerOptions().IsBootImage() || GetCompilerOptions().IsBootImageExtension()) {
     EmitPcRelativeLinkerPatches<linker::LinkerPatch::RelativeMethodPatch>(
         boot_image_method_patches_, linker_patches);
     EmitPcRelativeLinkerPatches<linker::LinkerPatch::RelativeTypePatch>(
diff --git a/compiler/optimizing/code_generator_mips.cc b/compiler/optimizing/code_generator_mips.cc
index 9ea9785..eafd051 100644
--- a/compiler/optimizing/code_generator_mips.cc
+++ b/compiler/optimizing/code_generator_mips.cc
@@ -1625,7 +1625,7 @@
       string_bss_entry_patches_.size() +
       boot_image_other_patches_.size();
   linker_patches->reserve(size);
-  if (GetCompilerOptions().IsBootImage()) {
+  if (GetCompilerOptions().IsBootImage() || GetCompilerOptions().IsBootImageExtension()) {
     EmitPcRelativeLinkerPatches<linker::LinkerPatch::RelativeMethodPatch>(
         boot_image_method_patches_, linker_patches);
     EmitPcRelativeLinkerPatches<linker::LinkerPatch::RelativeTypePatch>(
@@ -7998,7 +7998,7 @@
       callee_method = invoke->GetLocations()->InAt(invoke->GetSpecialInputIndex());
       break;
     case HInvokeStaticOrDirect::MethodLoadKind::kBootImageLinkTimePcRelative: {
-      DCHECK(GetCompilerOptions().IsBootImage());
+      DCHECK(GetCompilerOptions().IsBootImage() || GetCompilerOptions().IsBootImageExtension());
       PcRelativePatchInfo* info_high = NewBootImageMethodPatch(invoke->GetTargetMethod());
       PcRelativePatchInfo* info_low =
           NewBootImageMethodPatch(invoke->GetTargetMethod(), info_high);
@@ -8220,7 +8220,8 @@
       break;
     }
     case HLoadClass::LoadKind::kBootImageLinkTimePcRelative: {
-      DCHECK(codegen_->GetCompilerOptions().IsBootImage());
+      DCHECK(codegen_->GetCompilerOptions().IsBootImage() ||
+             codegen_->GetCompilerOptions().IsBootImageExtension());
       DCHECK_EQ(read_barrier_option, kWithoutReadBarrier);
       CodeGeneratorMIPS::PcRelativePatchInfo* info_high =
           codegen_->NewBootImageTypePatch(cls->GetDexFile(), cls->GetTypeIndex());
@@ -8427,7 +8428,8 @@
 
   switch (load_kind) {
     case HLoadString::LoadKind::kBootImageLinkTimePcRelative: {
-      DCHECK(codegen_->GetCompilerOptions().IsBootImage());
+      DCHECK(codegen_->GetCompilerOptions().IsBootImage() ||
+             codegen_->GetCompilerOptions().IsBootImageExtension());
       CodeGeneratorMIPS::PcRelativePatchInfo* info_high =
           codegen_->NewBootImageStringPatch(load->GetDexFile(), load->GetStringIndex());
       CodeGeneratorMIPS::PcRelativePatchInfo* info_low =
diff --git a/compiler/optimizing/code_generator_mips64.cc b/compiler/optimizing/code_generator_mips64.cc
index 06e9e58..7b91a19 100644
--- a/compiler/optimizing/code_generator_mips64.cc
+++ b/compiler/optimizing/code_generator_mips64.cc
@@ -1552,7 +1552,7 @@
       string_bss_entry_patches_.size() +
       boot_image_other_patches_.size();
   linker_patches->reserve(size);
-  if (GetCompilerOptions().IsBootImage()) {
+  if (GetCompilerOptions().IsBootImage() || GetCompilerOptions().IsBootImageExtension()) {
     EmitPcRelativeLinkerPatches<linker::LinkerPatch::RelativeMethodPatch>(
         boot_image_method_patches_, linker_patches);
     EmitPcRelativeLinkerPatches<linker::LinkerPatch::RelativeTypePatch>(
@@ -6089,7 +6089,7 @@
       callee_method = invoke->GetLocations()->InAt(invoke->GetSpecialInputIndex());
       break;
     case HInvokeStaticOrDirect::MethodLoadKind::kBootImageLinkTimePcRelative: {
-      DCHECK(GetCompilerOptions().IsBootImage());
+      DCHECK(GetCompilerOptions().IsBootImage() || GetCompilerOptions().IsBootImageExtension());
       CodeGeneratorMIPS64::PcRelativePatchInfo* info_high =
           NewBootImageMethodPatch(invoke->GetTargetMethod());
       CodeGeneratorMIPS64::PcRelativePatchInfo* info_low =
@@ -6276,7 +6276,8 @@
                               read_barrier_option);
       break;
     case HLoadClass::LoadKind::kBootImageLinkTimePcRelative: {
-      DCHECK(codegen_->GetCompilerOptions().IsBootImage());
+      DCHECK(codegen_->GetCompilerOptions().IsBootImage() ||
+             codegen_->GetCompilerOptions().IsBootImageExtension());
       DCHECK_EQ(read_barrier_option, kWithoutReadBarrier);
       CodeGeneratorMIPS64::PcRelativePatchInfo* info_high =
           codegen_->NewBootImageTypePatch(cls->GetDexFile(), cls->GetTypeIndex());
@@ -6424,7 +6425,8 @@
 
   switch (load_kind) {
     case HLoadString::LoadKind::kBootImageLinkTimePcRelative: {
-      DCHECK(codegen_->GetCompilerOptions().IsBootImage());
+      DCHECK(codegen_->GetCompilerOptions().IsBootImage() ||
+             codegen_->GetCompilerOptions().IsBootImageExtension());
       CodeGeneratorMIPS64::PcRelativePatchInfo* info_high =
           codegen_->NewBootImageStringPatch(load->GetDexFile(), load->GetStringIndex());
       CodeGeneratorMIPS64::PcRelativePatchInfo* info_low =
diff --git a/compiler/optimizing/code_generator_x86.cc b/compiler/optimizing/code_generator_x86.cc
index 5159553..6728dc9 100644
--- a/compiler/optimizing/code_generator_x86.cc
+++ b/compiler/optimizing/code_generator_x86.cc
@@ -4853,7 +4853,7 @@
       callee_method = invoke->GetLocations()->InAt(invoke->GetSpecialInputIndex());
       break;
     case HInvokeStaticOrDirect::MethodLoadKind::kBootImageLinkTimePcRelative: {
-      DCHECK(GetCompilerOptions().IsBootImage());
+      DCHECK(GetCompilerOptions().IsBootImage() || GetCompilerOptions().IsBootImageExtension());
       Register base_reg = GetInvokeStaticOrDirectExtraParameter(invoke,
                                                                 temp.AsRegister<Register>());
       __ leal(temp.AsRegister<Register>(), Address(base_reg, CodeGeneratorX86::kDummy32BitOffset));
@@ -5092,7 +5092,7 @@
       string_bss_entry_patches_.size() +
       boot_image_other_patches_.size();
   linker_patches->reserve(size);
-  if (GetCompilerOptions().IsBootImage()) {
+  if (GetCompilerOptions().IsBootImage() || GetCompilerOptions().IsBootImageExtension()) {
     EmitPcRelativeLinkerPatches<linker::LinkerPatch::RelativeMethodPatch>(
         boot_image_method_patches_, linker_patches);
     EmitPcRelativeLinkerPatches<linker::LinkerPatch::RelativeTypePatch>(
@@ -6601,7 +6601,8 @@
       break;
     }
     case HLoadClass::LoadKind::kBootImageLinkTimePcRelative: {
-      DCHECK(codegen_->GetCompilerOptions().IsBootImage());
+      DCHECK(codegen_->GetCompilerOptions().IsBootImage() ||
+             codegen_->GetCompilerOptions().IsBootImageExtension());
       DCHECK_EQ(read_barrier_option, kWithoutReadBarrier);
       Register method_address = locations->InAt(0).AsRegister<Register>();
       __ leal(out, Address(method_address, CodeGeneratorX86::kDummy32BitOffset));
@@ -6798,7 +6799,8 @@
 
   switch (load->GetLoadKind()) {
     case HLoadString::LoadKind::kBootImageLinkTimePcRelative: {
-      DCHECK(codegen_->GetCompilerOptions().IsBootImage());
+      DCHECK(codegen_->GetCompilerOptions().IsBootImage() ||
+             codegen_->GetCompilerOptions().IsBootImageExtension());
       Register method_address = locations->InAt(0).AsRegister<Register>();
       __ leal(out, Address(method_address, CodeGeneratorX86::kDummy32BitOffset));
       codegen_->RecordBootImageStringPatch(load);
diff --git a/compiler/optimizing/code_generator_x86_64.cc b/compiler/optimizing/code_generator_x86_64.cc
index 8c8b5e6..48a8320 100644
--- a/compiler/optimizing/code_generator_x86_64.cc
+++ b/compiler/optimizing/code_generator_x86_64.cc
@@ -999,7 +999,7 @@
       callee_method = invoke->GetLocations()->InAt(invoke->GetSpecialInputIndex());
       break;
     case HInvokeStaticOrDirect::MethodLoadKind::kBootImageLinkTimePcRelative:
-      DCHECK(GetCompilerOptions().IsBootImage());
+      DCHECK(GetCompilerOptions().IsBootImage() || GetCompilerOptions().IsBootImageExtension());
       __ leal(temp.AsRegister<CpuRegister>(),
               Address::Absolute(kDummy32BitOffset, /* no_rip= */ false));
       RecordBootImageMethodPatch(invoke);
@@ -1193,7 +1193,7 @@
       string_bss_entry_patches_.size() +
       boot_image_other_patches_.size();
   linker_patches->reserve(size);
-  if (GetCompilerOptions().IsBootImage()) {
+  if (GetCompilerOptions().IsBootImage() || GetCompilerOptions().IsBootImageExtension()) {
     EmitPcRelativeLinkerPatches<linker::LinkerPatch::RelativeMethodPatch>(
         boot_image_method_patches_, linker_patches);
     EmitPcRelativeLinkerPatches<linker::LinkerPatch::RelativeTypePatch>(
@@ -5966,7 +5966,8 @@
       break;
     }
     case HLoadClass::LoadKind::kBootImageLinkTimePcRelative:
-      DCHECK(codegen_->GetCompilerOptions().IsBootImage());
+      DCHECK(codegen_->GetCompilerOptions().IsBootImage() ||
+             codegen_->GetCompilerOptions().IsBootImageExtension());
       DCHECK_EQ(read_barrier_option, kWithoutReadBarrier);
       __ leal(out, Address::Absolute(CodeGeneratorX86_64::kDummy32BitOffset, /* no_rip= */ false));
       codegen_->RecordBootImageTypePatch(cls);
@@ -6120,7 +6121,8 @@
 
   switch (load->GetLoadKind()) {
     case HLoadString::LoadKind::kBootImageLinkTimePcRelative: {
-      DCHECK(codegen_->GetCompilerOptions().IsBootImage());
+      DCHECK(codegen_->GetCompilerOptions().IsBootImage() ||
+             codegen_->GetCompilerOptions().IsBootImageExtension());
       __ leal(out, Address::Absolute(CodeGeneratorX86_64::kDummy32BitOffset, /* no_rip= */ false));
       codegen_->RecordBootImageStringPatch(load);
       return;
diff --git a/compiler/optimizing/sharpening.cc b/compiler/optimizing/sharpening.cc
index 77b41ab..b8471e3 100644
--- a/compiler/optimizing/sharpening.cc
+++ b/compiler/optimizing/sharpening.cc
@@ -33,7 +33,6 @@
 #include "nodes.h"
 #include "runtime.h"
 #include "scoped_thread_state_change-inl.h"
-#include "utils/dex_cache_arrays_layout-inl.h"
 
 namespace art {
 
@@ -50,7 +49,7 @@
 }
 
 static bool BootImageAOTCanEmbedMethod(ArtMethod* method, const CompilerOptions& compiler_options) {
-  DCHECK(compiler_options.IsBootImage());
+  DCHECK(compiler_options.IsBootImage() || compiler_options.IsBootImageExtension());
   ScopedObjectAccess soa(Thread::Current());
   ObjPtr<mirror::Class> klass = method->GetDeclaringClass();
   DCHECK(klass != nullptr);
@@ -88,10 +87,13 @@
     // Recursive call.
     method_load_kind = HInvokeStaticOrDirect::MethodLoadKind::kRecursive;
     code_ptr_location = HInvokeStaticOrDirect::CodePtrLocation::kCallSelf;
-  } else if (compiler_options.IsBootImage()) {
+  } else if (compiler_options.IsBootImage() || compiler_options.IsBootImageExtension()) {
     if (!compiler_options.GetCompilePic()) {
       // Test configuration, do not sharpen.
       method_load_kind = HInvokeStaticOrDirect::MethodLoadKind::kRuntimeCall;
+    } else if (IsInBootImage(callee)) {
+      DCHECK(compiler_options.IsBootImageExtension());
+      method_load_kind = HInvokeStaticOrDirect::MethodLoadKind::kBootImageRelRo;
     } else if (BootImageAOTCanEmbedMethod(callee, compiler_options)) {
       method_load_kind = HInvokeStaticOrDirect::MethodLoadKind::kBootImageLinkTimePcRelative;
     } else {
@@ -161,19 +163,22 @@
     HLoadClass::LoadKind desired_load_kind = HLoadClass::LoadKind::kInvalid;
     Runtime* runtime = Runtime::Current();
     const CompilerOptions& compiler_options = codegen->GetCompilerOptions();
-    if (compiler_options.IsBootImage()) {
-      // Compiling boot image. Check if the class is a boot image class.
+    if (compiler_options.IsBootImage() || compiler_options.IsBootImageExtension()) {
+      // Compiling boot image or boot image extension. Check if the class is a boot image class.
       DCHECK(!runtime->UseJitCompilation());
       if (!compiler_options.GetCompilePic()) {
         // Test configuration, do not sharpen.
         desired_load_kind = HLoadClass::LoadKind::kRuntimeCall;
+      } else if (klass != nullptr && runtime->GetHeap()->ObjectIsInBootImageSpace(klass.Get())) {
+        DCHECK(compiler_options.IsBootImageExtension());
+        is_in_boot_image = true;
+        desired_load_kind = HLoadClass::LoadKind::kBootImageRelRo;
       } else if ((klass != nullptr) &&
                  compiler_options.IsImageClass(dex_file.StringByTypeIdx(type_index))) {
         is_in_boot_image = true;
         desired_load_kind = HLoadClass::LoadKind::kBootImageLinkTimePcRelative;
       } else {
         // Not a boot image class.
-        DCHECK(ContainsElement(compiler_options.GetDexFilesForOatFile(), &dex_file));
         desired_load_kind = HLoadClass::LoadKind::kBssEntry;
       }
     } else {
@@ -317,12 +322,11 @@
     ObjPtr<mirror::String> string = nullptr;
 
     const CompilerOptions& compiler_options = codegen->GetCompilerOptions();
-    if (compiler_options.IsBootImage()) {
-      // Compiling boot image. Resolve the string and allocate it if needed, to ensure
-      // the string will be added to the boot image.
+    if (compiler_options.IsBootImage() || compiler_options.IsBootImageExtension()) {
+      // Compiling boot image or boot image extension. Resolve the string and allocate it
+      // if needed, to ensure the string will be added to the boot image.
       DCHECK(!runtime->UseJitCompilation());
       if (compiler_options.GetCompilePic()) {
-        DCHECK(ContainsElement(compiler_options.GetDexFilesForOatFile(), &dex_file));
         if (compiler_options.IsForceDeterminism()) {
           // Strings for methods we're compiling should be pre-resolved but Strings in inlined
           // methods may not be if these inlined methods are not in the boot image profile.
@@ -337,7 +341,12 @@
           CHECK(string != nullptr);
         }
         if (string != nullptr) {
-          desired_load_kind = HLoadString::LoadKind::kBootImageLinkTimePcRelative;
+          if (runtime->GetHeap()->ObjectIsInBootImageSpace(string)) {
+            DCHECK(compiler_options.IsBootImageExtension());
+            desired_load_kind = HLoadString::LoadKind::kBootImageRelRo;
+          } else {
+            desired_load_kind = HLoadString::LoadKind::kBootImageLinkTimePcRelative;
+          }
         } else {
           desired_load_kind = HLoadString::LoadKind::kBssEntry;
         }
diff --git a/dex2oat/linker/image_test.h b/dex2oat/linker/image_test.h
index a0f1093..4c984d3 100644
--- a/dex2oat/linker/image_test.h
+++ b/dex2oat/linker/image_test.h
@@ -210,7 +210,6 @@
     dex_file_to_oat_index_map.emplace(dex_file, image_idx);
     ++image_idx;
   }
-  // TODO: compile_pic should be a test argument.
   std::unique_ptr<ImageWriter> writer(new ImageWriter(*compiler_options_,
                                                       kRequestedImageBase,
                                                       storage_mode,
diff --git a/dex2oat/linker/image_writer.cc b/dex2oat/linker/image_writer.cc
index 1f1bf33..ce7faba 100644
--- a/dex2oat/linker/image_writer.cc
+++ b/dex2oat/linker/image_writer.cc
@@ -53,7 +53,7 @@
 #include "gc/space/space-inl.h"
 #include "gc/verification.h"
 #include "handle_scope-inl.h"
-#include "image.h"
+#include "image-inl.h"
 #include "imt_conflict_table.h"
 #include "intern_table-inl.h"
 #include "jni/jni_internal.h"
@@ -1012,12 +1012,12 @@
 
     if (ref->IsClass()) {
       *result_ = *result_ ||
-          image_writer_->PruneAppImageClassInternal(ref->AsClass(), early_exit_, visited_);
+          image_writer_->PruneImageClassInternal(ref->AsClass(), early_exit_, visited_);
     } else {
       // Record the object visited in case of circular reference.
       visited_->emplace(ref);
       *result_ = *result_ ||
-          image_writer_->PruneAppImageClassInternal(klass, early_exit_, visited_);
+          image_writer_->PruneImageClassInternal(klass, early_exit_, visited_);
       ref->VisitReferences(*this, *this);
       // Clean up before exit for next call of this function.
       visited_->erase(ref);
@@ -1038,19 +1038,19 @@
 };
 
 
-bool ImageWriter::PruneAppImageClass(ObjPtr<mirror::Class> klass) {
+bool ImageWriter::PruneImageClass(ObjPtr<mirror::Class> klass) {
   bool early_exit = false;
   std::unordered_set<mirror::Object*> visited;
-  return PruneAppImageClassInternal(klass, &early_exit, &visited);
+  return PruneImageClassInternal(klass, &early_exit, &visited);
 }
 
-bool ImageWriter::PruneAppImageClassInternal(
+bool ImageWriter::PruneImageClassInternal(
     ObjPtr<mirror::Class> klass,
     bool* early_exit,
     std::unordered_set<mirror::Object*>* visited) {
   DCHECK(early_exit != nullptr);
   DCHECK(visited != nullptr);
-  DCHECK(compiler_options_.IsAppImage());
+  DCHECK(compiler_options_.IsAppImage() || compiler_options_.IsBootImageExtension());
   if (klass == nullptr || IsInBootImage(klass.Ptr())) {
     return false;
   }
@@ -1083,15 +1083,15 @@
     // Check interfaces since these wont be visited through VisitReferences.)
     ObjPtr<mirror::IfTable> if_table = klass->GetIfTable();
     for (size_t i = 0, num_interfaces = klass->GetIfTableCount(); i < num_interfaces; ++i) {
-      result = result || PruneAppImageClassInternal(if_table->GetInterface(i),
-                                                    &my_early_exit,
-                                                    visited);
+      result = result || PruneImageClassInternal(if_table->GetInterface(i),
+                                                 &my_early_exit,
+                                                 visited);
     }
   }
   if (klass->IsObjectArrayClass()) {
-    result = result || PruneAppImageClassInternal(klass->GetComponentType(),
-                                                  &my_early_exit,
-                                                  visited);
+    result = result || PruneImageClassInternal(klass->GetComponentType(),
+                                               &my_early_exit,
+                                               visited);
   }
   // Check static fields and their classes.
   if (klass->IsResolved() && klass->NumReferenceStaticFields() != 0) {
@@ -1104,14 +1104,10 @@
       mirror::Object* ref = klass->GetFieldObject<mirror::Object>(field_offset);
       if (ref != nullptr) {
         if (ref->IsClass()) {
-          result = result || PruneAppImageClassInternal(ref->AsClass(),
-                                                        &my_early_exit,
-                                                        visited);
+          result = result || PruneImageClassInternal(ref->AsClass(), &my_early_exit, visited);
         } else {
           mirror::Class* type = ref->GetClass();
-          result = result || PruneAppImageClassInternal(type,
-                                                        &my_early_exit,
-                                                        visited);
+          result = result || PruneImageClassInternal(type, &my_early_exit, visited);
           if (!result) {
             // For non-class case, also go through all the types mentioned by it's fields'
             // references recursively to decide whether to keep this class.
@@ -1126,9 +1122,7 @@
                                   sizeof(mirror::HeapReference<mirror::Object>));
     }
   }
-  result = result || PruneAppImageClassInternal(klass->GetSuperClass(),
-                                                &my_early_exit,
-                                                visited);
+  result = result || PruneImageClassInternal(klass->GetSuperClass(), &my_early_exit, visited);
   // Remove the class if the dex file is not in the set of dex files. This happens for classes that
   // are from uses-library if there is no profile. b/30688277
   ObjPtr<mirror::DexCache> dex_cache = klass->GetDexCache();
@@ -1163,11 +1157,13 @@
   if (!compiler_options_.IsImageClass(klass->GetDescriptor(&temp))) {
     return false;
   }
-  if (compiler_options_.IsAppImage()) {
-    // For app images, we need to prune boot loader classes that are not in the boot image since
-    // these may have already been loaded when the app image is loaded.
-    // Keep classes in the boot image space since we don't want to re-resolve these.
-    return !PruneAppImageClass(klass);
+  if (compiler_options_.IsAppImage() || compiler_options_.IsBootImageExtension()) {
+    // For app images and boot image extensions, we need to prune classes that
+    // are defined by the boot class path we're compiling against but not in
+    // the boot image spaces since these may have already been loaded at
+    // run time when this image is loaded. Keep classes in the boot image
+    // spaces we're compiling against since we don't want to re-resolve these.
+    return !PruneImageClass(klass);
   }
   return true;
 }
@@ -2398,14 +2394,22 @@
   return assigned;
 }
 
+static ObjPtr<ObjectArray<Object>> GetBootImageLiveObjects() REQUIRES_SHARED(Locks::mutator_lock_) {
+  gc::Heap* heap = Runtime::Current()->GetHeap();
+  DCHECK(!heap->GetBootImageSpaces().empty());
+  const ImageHeader& primary_header = heap->GetBootImageSpaces().front()->GetImageHeader();
+  return ObjPtr<ObjectArray<Object>>::DownCast(
+      primary_header.GetImageRoot<kWithReadBarrier>(ImageHeader::kBootImageLiveObjects));
+}
+
 void ImageWriter::CalculateNewObjectOffsets() {
   Thread* const self = Thread::Current();
   Runtime* const runtime = Runtime::Current();
   VariableSizedHandleScope handles(self);
   MutableHandle<ObjectArray<Object>> boot_image_live_objects = handles.NewHandle(
-      compiler_options_.IsAppImage()
-          ? nullptr
-          : AllocateBootImageLiveObjects(self, runtime));
+      compiler_options_.IsBootImage()
+          ? AllocateBootImageLiveObjects(self, runtime)
+          : (compiler_options_.IsBootImageExtension() ? GetBootImageLiveObjects() : nullptr));
   std::vector<Handle<ObjectArray<Object>>> image_roots;
   for (size_t i = 0, size = oat_filenames_.size(); i != size; ++i) {
     image_roots.push_back(handles.NewHandle(CreateImageRoots(i, boot_image_live_objects)));
@@ -3126,7 +3130,7 @@
   FixupClassVisitor visitor(this, copy);
   ObjPtr<mirror::Object>(orig)->VisitReferences(visitor, visitor);
 
-  if (kBitstringSubtypeCheckEnabled && compiler_options_.IsAppImage()) {
+  if (kBitstringSubtypeCheckEnabled && !compiler_options_.IsBootImage()) {
     // When we call SubtypeCheck::EnsureInitialize, it Assigns new bitstring
     // values to the parent of that class.
     //
@@ -3142,6 +3146,8 @@
     //
     // On startup, the class linker will then re-initialize all the app
     // image bitstrings. See also ClassLinker::AddImageSpace.
+    //
+    // FIXME: Deal with boot image extensions.
     MutexLock subtype_check_lock(Thread::Current(), *Locks::subtype_check_lock_);
     // Lock every time to prevent a dcheck failure when we suspend with the lock held.
     SubtypeCheck<mirror::Class*>::ForceUninitialize(copy);
@@ -3306,7 +3312,8 @@
 
 const uint8_t* ImageWriter::GetOatAddress(StubType type) const {
   DCHECK_LE(type, StubType::kLast);
-  // If we are compiling an app image, we need to use the stubs of the boot image.
+  // If we are compiling a boot image extension or app image,
+  // we need to use the stubs of the primary boot image.
   if (!compiler_options_.IsBootImage()) {
     // Use the current image pointers.
     const std::vector<gc::space::ImageSpace*>& image_spaces =
@@ -3607,7 +3614,9 @@
       oat_filenames_(oat_filenames),
       dex_file_oat_index_map_(dex_file_oat_index_map),
       dirty_image_objects_(dirty_image_objects) {
-  DCHECK(compiler_options.IsBootImage() || compiler_options.IsAppImage());
+  DCHECK(compiler_options.IsBootImage() ||
+         compiler_options.IsBootImageExtension() ||
+         compiler_options.IsAppImage());
   DCHECK_EQ(compiler_options.IsBootImage(), boot_image_begin_ == 0u);
   DCHECK_EQ(compiler_options.IsBootImage(), boot_image_size_ == 0u);
   CHECK_NE(image_begin, 0U);
diff --git a/dex2oat/linker/image_writer.h b/dex2oat/linker/image_writer.h
index 8990d32..5365186 100644
--- a/dex2oat/linker/image_writer.h
+++ b/dex2oat/linker/image_writer.h
@@ -561,17 +561,17 @@
   // Return true if klass is loaded by the boot class loader but not in the boot image.
   bool IsBootClassLoaderNonImageClass(mirror::Class* klass) REQUIRES_SHARED(Locks::mutator_lock_);
 
-  // Return true if klass depends on a boot class loader non image class. We want to prune these
-  // classes since we do not want any boot class loader classes in the image. This means that
-  // we also cannot have any classes which refer to these boot class loader non image classes.
-  // PruneAppImageClass also prunes if klass depends on a non-image class according to the compiler
-  // options.
-  bool PruneAppImageClass(ObjPtr<mirror::Class> klass) REQUIRES_SHARED(Locks::mutator_lock_);
+  // Return true if `klass` depends on a class defined by the boot class path
+  // we're compiling against but not present in the boot image spaces. We want
+  // to prune these classes since we cannot guarantee that they will not be
+  // already loaded at run time when loading this image. This means that we
+  // also cannot have any classes which refer to these non image classes.
+  bool PruneImageClass(ObjPtr<mirror::Class> klass) REQUIRES_SHARED(Locks::mutator_lock_);
 
   // early_exit is true if we had a cyclic dependency anywhere down the chain.
-  bool PruneAppImageClassInternal(ObjPtr<mirror::Class> klass,
-                                  bool* early_exit,
-                                  std::unordered_set<mirror::Object*>* visited)
+  bool PruneImageClassInternal(ObjPtr<mirror::Class> klass,
+                               bool* early_exit,
+                               std::unordered_set<mirror::Object*>* visited)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
   bool IsMultiImage() const {
diff --git a/dex2oat/linker/oat_writer.cc b/dex2oat/linker/oat_writer.cc
index 0a26084..b478e28 100644
--- a/dex2oat/linker/oat_writer.cc
+++ b/dex2oat/linker/oat_writer.cc
@@ -726,7 +726,7 @@
   relative_patcher_ = relative_patcher;
   SetMultiOatRelativePatcherAdjustment();
 
-  if (GetCompilerOptions().IsBootImage()) {
+  if (GetCompilerOptions().IsBootImage() || GetCompilerOptions().IsBootImageExtension()) {
     CHECK(image_writer_ != nullptr);
   }
   InstructionSet instruction_set = compiler_options_.GetInstructionSet();
@@ -1560,7 +1560,8 @@
     Thread* self = Thread::Current();
     ObjPtr<mirror::DexCache> dex_cache = class_linker_->FindDexCache(self, *dex_file_);
     ArtMethod* resolved_method;
-    if (writer_->GetCompilerOptions().IsBootImage()) {
+    if (writer_->GetCompilerOptions().IsBootImage() ||
+        writer_->GetCompilerOptions().IsBootImageExtension()) {
       resolved_method = class_linker_->LookupResolvedMethod(
           method.GetIndex(), dex_cache, /*class_loader=*/ nullptr);
       if (resolved_method == nullptr) {
@@ -1640,7 +1641,8 @@
         dex_cache_(nullptr),
         no_thread_suspension_("OatWriter patching") {
     patched_code_.reserve(16 * KB);
-    if (writer_->GetCompilerOptions().IsBootImage()) {
+    if (writer_->GetCompilerOptions().IsBootImage() ||
+        writer_->GetCompilerOptions().IsBootImageExtension()) {
       // If we're creating the image, the address space must be ready so that we can apply patches.
       CHECK(writer_->image_writer_->IsImageAddressSpaceReady());
     }
@@ -1889,24 +1891,20 @@
 
   uint32_t GetTargetOffset(const LinkerPatch& patch) REQUIRES_SHARED(Locks::mutator_lock_) {
     uint32_t target_offset = writer_->relative_patcher_->GetOffset(patch.TargetMethod());
-    // If there's no new compiled code, either we're compiling an app and the target method
-    // is in the boot image, or we need to point to the correct trampoline.
+    // If there's no new compiled code, we need to point to the correct trampoline.
     if (UNLIKELY(target_offset == 0)) {
       ArtMethod* target = GetTargetMethod(patch);
       DCHECK(target != nullptr);
-      const void* oat_code_offset =
-          target->GetEntryPointFromQuickCompiledCodePtrSize(pointer_size_);
-      if (oat_code_offset != nullptr) {
-        DCHECK(!writer_->GetCompilerOptions().IsBootImage());
-        DCHECK(!Runtime::Current()->GetClassLinker()->IsQuickResolutionStub(oat_code_offset));
-        DCHECK(!Runtime::Current()->GetClassLinker()->IsQuickToInterpreterBridge(oat_code_offset));
-        DCHECK(!Runtime::Current()->GetClassLinker()->IsQuickGenericJniStub(oat_code_offset));
-        target_offset = PointerToLowMemUInt32(oat_code_offset);
-      } else {
-        target_offset = target->IsNative()
-            ? writer_->oat_header_->GetQuickGenericJniTrampolineOffset()
-            : writer_->oat_header_->GetQuickToInterpreterBridgeOffset();
-      }
+      // TODO: Remove kCallRelative? This patch type is currently not in use.
+      // If we want to use it again, we should make sure that we either use it
+      // only for target methods that were actually compiled, or call the
+      // method dispatch thunk. Currently, ARM/ARM64 patchers would emit the
+      // thunk for far `target_offset` (so we could teach them to use the
+      // thunk for `target_offset == 0`) but x86/x86-64 patchers do not.
+      // (When this was originally implemented, every oat file contained
+      // trampolines, so we could just return their offset here. Now only
+      // the boot image contains them, so this is not always an option.)
+      LOG(FATAL) << "The target method was not compiled.";
     }
     return target_offset;
   }
@@ -1935,7 +1933,7 @@
         linker->LookupString(patch.TargetStringIndex(), GetDexCache(patch.TargetStringDexFile()));
     DCHECK(string != nullptr);
     DCHECK(writer_->GetCompilerOptions().IsBootImage() ||
-           Runtime::Current()->GetHeap()->ObjectIsInBootImageSpace(string));
+           writer_->GetCompilerOptions().IsBootImageExtension());
     return string;
   }
 
@@ -1951,7 +1949,8 @@
   }
 
   uint32_t GetTargetMethodOffset(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_) {
-    DCHECK(writer_->GetCompilerOptions().IsBootImage());
+    DCHECK(writer_->GetCompilerOptions().IsBootImage() ||
+           writer_->GetCompilerOptions().IsBootImageExtension());
     method = writer_->image_writer_->GetImageMethodAddress(method);
     size_t oat_index = writer_->image_writer_->GetOatIndexForDexFile(dex_file_);
     uintptr_t oat_data_begin = writer_->image_writer_->GetOatDataBegin(oat_index);
@@ -1961,33 +1960,14 @@
 
   uint32_t GetTargetObjectOffset(ObjPtr<mirror::Object> object)
       REQUIRES_SHARED(Locks::mutator_lock_) {
-    DCHECK(writer_->GetCompilerOptions().IsBootImage());
+    DCHECK(writer_->GetCompilerOptions().IsBootImage() ||
+           writer_->GetCompilerOptions().IsBootImageExtension());
     object = writer_->image_writer_->GetImageAddress(object.Ptr());
     size_t oat_index = writer_->image_writer_->GetOatIndexForDexFile(dex_file_);
     uintptr_t oat_data_begin = writer_->image_writer_->GetOatDataBegin(oat_index);
     // TODO: Clean up offset types. The target offset must be treated as signed.
     return static_cast<uint32_t>(reinterpret_cast<uintptr_t>(object.Ptr()) - oat_data_begin);
   }
-
-  void PatchObjectAddress(std::vector<uint8_t>* code, uint32_t offset, mirror::Object* object)
-      REQUIRES_SHARED(Locks::mutator_lock_) {
-    if (writer_->GetCompilerOptions().IsBootImage()) {
-      object = writer_->image_writer_->GetImageAddress(object);
-    } else {
-      // NOTE: We're using linker patches for app->boot references when the image can
-      // be relocated and therefore we need to emit .oat_patches. We're not using this
-      // for app->app references, so check that the object is in the image space.
-      DCHECK(Runtime::Current()->GetHeap()->FindSpaceFromObject(object, false)->IsImageSpace());
-    }
-    // Note: We only patch targeting Objects in image which is in the low 4gb.
-    uint32_t address = PointerToLowMemUInt32(object);
-    DCHECK_LE(offset + 4, code->size());
-    uint8_t* data = &(*code)[offset];
-    data[0] = address & 0xffu;
-    data[1] = (address >> 8) & 0xffu;
-    data[2] = (address >> 16) & 0xffu;
-    data[3] = (address >> 24) & 0xffu;
-  }
 };
 
 // Visit all methods from all classes in all dex files with the specified visitor.