Snap for 8073075 from 4a75f4a7162b32edc194077dec85e027b9e457ef to tm-release

Change-Id: I96978f1b2c1b5a3a08a64295f6cfdb4cac2d09d4
diff --git a/artd/binder/private/com/android/art/DexoptBcpExtArgs.aidl b/artd/binder/private/com/android/art/DexoptBcpExtArgs.aidl
index 43fd922..8253ead 100644
--- a/artd/binder/private/com/android/art/DexoptBcpExtArgs.aidl
+++ b/artd/binder/private/com/android/art/DexoptBcpExtArgs.aidl
@@ -44,7 +44,7 @@
     // manually.
     String[] bootClasspaths;
     int[] bootClasspathFds;
-    int profileFd = -1;
+    int[] profileFds;
     int dirtyImageObjectsFd = -1;
     // Output file descriptors
     int oatFd = -1;
diff --git a/artd/binder/private/com/android/art/DexoptSystemServerArgs.aidl b/artd/binder/private/com/android/art/DexoptSystemServerArgs.aidl
index bfb94e1..54aa867 100644
--- a/artd/binder/private/com/android/art/DexoptSystemServerArgs.aidl
+++ b/artd/binder/private/com/android/art/DexoptSystemServerArgs.aidl
@@ -60,7 +60,7 @@
     String dexPath;
     String oatLocation;
     String[] classloaderContext;
-    boolean isBootImageOnSystem;
+    String bootImage;
     boolean classloaderContextAsParent;
 
     // SECURITY: The server may accept the request to produce code for the specified architecture,
diff --git a/artd/libdexopt.cc b/artd/libdexopt.cc
index 853f950..b6982c1 100644
--- a/artd/libdexopt.cc
+++ b/artd/libdexopt.cc
@@ -43,11 +43,6 @@
 using android::base::Error;
 using android::base::Result;
 
-std::string GetBootImage() {
-  // Typically "/apex/com.android.art/javalib/boot.art".
-  return art::GetArtRoot() + "/javalib/boot.art";
-}
-
 std::string GetEnvironmentVariableOrDie(const char* name) {
   const char* value = getenv(name);
   LOG_ALWAYS_FATAL_IF(value == nullptr, "%s is not defined.", name);
@@ -206,8 +201,10 @@
 
   cmdline.emplace_back("--instruction-set=" + ToInstructionSetString(args.isa));
 
-  if (args.profileFd >= 0) {
-    cmdline.emplace_back(android::base::StringPrintf("--profile-file-fd=%d", args.profileFd));
+  if (!args.profileFds.empty()) {
+    for (int fd : args.profileFds) {
+      cmdline.emplace_back(android::base::StringPrintf("--profile-file-fd=%d", fd));
+    }
     cmdline.emplace_back("--compiler-filter=speed-profile");
   } else {
     cmdline.emplace_back("--compiler-filter=speed");
@@ -216,8 +213,7 @@
   // Compile as a single image for fewer files and slightly less memory overhead.
   cmdline.emplace_back("--single-image");
 
-  // Set boot-image and expectation of compiling boot classpath extensions.
-  cmdline.emplace_back("--boot-image=" + GetBootImage());
+  cmdline.emplace_back(android::base::StringPrintf("--base=0x%08x", ART_BASE_ADDRESS));
 
   if (args.dirtyImageObjectsFd >= 0) {
     cmdline.emplace_back(android::base::StringPrintf("--dirty-image-objects-fd=%d",
@@ -321,23 +317,7 @@
                          android::base::Join(args.classloaderFds, ':'));
   }
 
-  // Derive boot image
-  // b/197176583
-  // If the boot extension artifacts are not on /data, then boot extensions are not re-compiled
-  // and the artifacts must exist on /system.
-  std::vector<std::string> jar_paths = android::base::Split(GetDex2oatBootClasspath(), ":");
-  auto iter = std::find_if_not(jar_paths.begin(), jar_paths.end(), &LocationIsOnArtModule);
-  if (iter == jar_paths.end()) {
-    return Error() << "Missing BCP extension compatible JAR";
-  }
-  const std::string& first_boot_extension_compatible_jars = *iter;
-  // TODO(197176583): Support compiling against BCP extension in /system.
-  const std::string extension_image = GetBootImagePath(args.isBootImageOnSystem,
-                                                       first_boot_extension_compatible_jars);
-  if (extension_image.empty()) {
-    return Error() << "Can't identify the first boot extension compatible jar";
-  }
-  cmdline.emplace_back("--boot-image=" + GetBootImage() + ":" + extension_image);
+  cmdline.emplace_back("--boot-image=" + args.bootImage);
 
   AddDex2OatConcurrencyArguments(cmdline, args.threads, args.cpuSet);
 
diff --git a/artd/libdexopt_test.cc b/artd/libdexopt_test.cc
index e73c5d6..3cc340b 100644
--- a/artd/libdexopt_test.cc
+++ b/artd/libdexopt_test.cc
@@ -58,7 +58,7 @@
     default_bcp_ext_args_.bootClasspaths = android::base::Split(
         GetEnvironmentVariableOrDie("DEX2OATBOOTCLASSPATH"), ":");  // from art_artd_tests.xml
     default_bcp_ext_args_.bootClasspathFds = {21, 22};
-    default_bcp_ext_args_.profileFd = 30;
+    default_bcp_ext_args_.profileFds = {30, 31};
     default_bcp_ext_args_.dirtyImageObjectsFd = 31;
     default_bcp_ext_args_.imageFd = 90;
     default_bcp_ext_args_.vdexFd = 91;
@@ -90,7 +90,7 @@
     default_system_server_args_.compilerFilter = CompilerFilter::SPEED_PROFILE;
     default_system_server_args_.cpuSet = {0, 1};
     default_system_server_args_.threads = 42;
-    default_system_server_args_.isBootImageOnSystem = true;
+    default_system_server_args_.bootImage = "/path/to/boot.art";
     ASSERT_EQ(default_system_server_args_.bootClasspaths.size(),
               default_system_server_args_.bootClasspathFds.size());
   }
@@ -134,32 +134,35 @@
   {
     std::vector<std::string> cmdline = Dex2oatArgsFromBcpExtensionArgs(default_bcp_ext_args_);
 
-    EXPECT_THAT(cmdline, AllOf(
-        Contains("--dex-fd=10"),
-        Contains("--dex-fd=11"),
-        Contains("--dex-file=/path/to/foo.jar"),
-        Contains("--dex-file=/path/to/bar.jar"),
-        Contains(HasSubstr("-Xbootclasspath:")),
-        Contains("-Xbootclasspathfds:21:22"),
+    EXPECT_THAT(cmdline,
+                AllOf(Contains("--dex-fd=10"),
+                      Contains("--dex-fd=11"),
+                      Contains("--dex-file=/path/to/foo.jar"),
+                      Contains("--dex-file=/path/to/bar.jar"),
+                      Contains(HasSubstr("-Xbootclasspath:")),
+                      Contains("-Xbootclasspathfds:21:22"),
 
-        Contains("--profile-file-fd=30"),
-        Contains("--compiler-filter=speed-profile"),
+                      Contains("--profile-file-fd=30"),
+                      Contains("--profile-file-fd=31"),
+                      Contains("--compiler-filter=speed-profile"),
 
-        Contains("--image-fd=90"),
-        Contains("--output-vdex-fd=91"),
-        Contains("--oat-fd=92"),
-        Contains("--oat-location=/oat/location/bar.odex"),
+                      Contains("--image-fd=90"),
+                      Contains("--output-vdex-fd=91"),
+                      Contains("--oat-fd=92"),
+                      Contains("--oat-location=/oat/location/bar.odex"),
 
-        Contains("--dirty-image-objects-fd=31"),
-        Contains("--instruction-set=x86_64"),
-        Contains("--cpu-set=0,1"),
-        Contains("-j42")));
+                      Contains("--dirty-image-objects-fd=31"),
+                      Contains("--instruction-set=x86_64"),
+                      Contains("--cpu-set=0,1"),
+                      Contains("-j42"),
+
+                      Contains(HasSubstr("--base="))));
   }
 
   // No profile
   {
     auto args = default_bcp_ext_args_;
-    args.profileFd = -1;
+    args.profileFds = {};
     std::vector<std::string> cmdline = Dex2oatArgsFromBcpExtensionArgs(args);
 
     EXPECT_THAT(cmdline, AllOf(
@@ -224,29 +227,31 @@
   {
     std::vector<std::string> cmdline = Dex2oatArgsFromSystemServerArgs(default_system_server_args_);
 
-    EXPECT_THAT(cmdline, AllOf(
-        Contains("--dex-fd=10"),
-        Contains("--dex-file=/path/to/foo.jar"),
-        Contains(HasSubstr("-Xbootclasspath:")),
-        Contains("-Xbootclasspathfds:21:22:23"),
-        Contains("-Xbootclasspathimagefds:-1:31:-1"),
-        Contains("-Xbootclasspathvdexfds:-1:32:-1"),
-        Contains("-Xbootclasspathoatfds:-1:33:-1"),
+    EXPECT_THAT(cmdline,
+                AllOf(Contains("--dex-fd=10"),
+                      Contains("--dex-file=/path/to/foo.jar"),
+                      Contains(HasSubstr("-Xbootclasspath:")),
+                      Contains("-Xbootclasspathfds:21:22:23"),
+                      Contains("-Xbootclasspathimagefds:-1:31:-1"),
+                      Contains("-Xbootclasspathvdexfds:-1:32:-1"),
+                      Contains("-Xbootclasspathoatfds:-1:33:-1"),
 
-        Contains("--profile-file-fd=11"),
-        Contains("--compiler-filter=speed-profile"),
+                      Contains("--profile-file-fd=11"),
+                      Contains("--compiler-filter=speed-profile"),
 
-        Contains("--app-image-fd=90"),
-        Contains("--output-vdex-fd=91"),
-        Contains("--oat-fd=92"),
-        Contains("--oat-location=/oat/location/bar.odex"),
+                      Contains("--app-image-fd=90"),
+                      Contains("--output-vdex-fd=91"),
+                      Contains("--oat-fd=92"),
+                      Contains("--oat-location=/oat/location/bar.odex"),
 
-        Contains("--class-loader-context-fds=40:41"),
-        Contains("--class-loader-context=PCL[/cl/abc.jar:/cl/def.jar]"),
+                      Contains("--class-loader-context-fds=40:41"),
+                      Contains("--class-loader-context=PCL[/cl/abc.jar:/cl/def.jar]"),
 
-        Contains("--instruction-set=x86_64"),
-        Contains("--cpu-set=0,1"),
-        Contains("-j42")));
+                      Contains("--instruction-set=x86_64"),
+                      Contains("--cpu-set=0,1"),
+                      Contains("-j42"),
+
+                      Contains("--boot-image=/path/to/boot.art")));
   }
 
   // Test different compiler filters
diff --git a/compiler/optimizing/code_generator_arm64.cc b/compiler/optimizing/code_generator_arm64.cc
index 1002f2c..cc57f6a 100644
--- a/compiler/optimizing/code_generator_arm64.cc
+++ b/compiler/optimizing/code_generator_arm64.cc
@@ -1987,6 +1987,9 @@
   if (codegen_->CanUseImplicitSuspendCheck()) {
     __ Ldr(kImplicitSuspendCheckRegister, MemOperand(kImplicitSuspendCheckRegister));
     codegen_->RecordPcInfo(instruction, instruction->GetDexPc());
+    if (successor != nullptr) {
+      __ B(codegen_->GetLabelOf(successor));
+    }
     return;
   }
 
@@ -3583,9 +3586,7 @@
   if (info != nullptr && info->IsBackEdge(*block) && info->HasSuspendCheck()) {
     codegen_->MaybeIncrementHotness(/* is_frame_entry= */ false);
     GenerateSuspendCheck(info->GetSuspendCheck(), successor);
-    if (!codegen_->CanUseImplicitSuspendCheck()) {
-      return;  // `GenerateSuspendCheck()` emitted the jump.
-    }
+    return;  // `GenerateSuspendCheck()` emitted the jump.
   }
   if (block->IsEntryBlock() && (previous != nullptr) && previous->IsSuspendCheck()) {
     GenerateSuspendCheck(previous->AsSuspendCheck(), nullptr);
diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc
index 123165d..b8271d8 100644
--- a/dex2oat/dex2oat.cc
+++ b/dex2oat/dex2oat.cc
@@ -833,8 +833,7 @@
     // Set the compilation target's implicit checks options.
     switch (compiler_options_->GetInstructionSet()) {
       case InstructionSet::kArm64:
-        // TODO: Investigate implicit suspend check regressions. Bug: 209235730, 213121241.
-        compiler_options_->implicit_suspend_checks_ = false;
+        compiler_options_->implicit_suspend_checks_ = true;
         FALLTHROUGH_INTENDED;
       case InstructionSet::kArm:
       case InstructionSet::kThumb2:
@@ -1237,7 +1236,6 @@
           input_vdex_file_ = VdexFile::Open(input_vdex_,
                                             /* writable */ false,
                                             /* low_4gb */ false,
-                                            DoEagerUnquickeningOfVdex(),
                                             &error_msg);
         }
 
@@ -1246,8 +1244,8 @@
             ? ReplaceFileExtension(oat_filename, "vdex")
             : output_vdex_;
         if (vdex_filename == input_vdex_ && output_vdex_.empty()) {
-          update_input_vdex_ = true;
-          std::unique_ptr<File> vdex_file(OS::OpenFileReadWrite(vdex_filename.c_str()));
+          use_existing_vdex_ = true;
+          std::unique_ptr<File> vdex_file(OS::OpenFileForReading(vdex_filename.c_str()));
           vdex_files_.push_back(std::move(vdex_file));
         } else {
           std::unique_ptr<File> vdex_file(OS::CreateEmptyFile(vdex_filename.c_str()));
@@ -1289,7 +1287,6 @@
                                             "vdex",
                                             /* writable */ false,
                                             /* low_4gb */ false,
-                                            DoEagerUnquickeningOfVdex(),
                                             &error_msg);
           // If there's any problem with the passed vdex, just warn and proceed
           // without it.
@@ -1301,15 +1298,20 @@
 
       DCHECK_NE(output_vdex_fd_, -1);
       std::string vdex_location = ReplaceFileExtension(oat_location_, "vdex");
-      std::unique_ptr<File> vdex_file(new File(
-          DupCloexec(output_vdex_fd_), vdex_location, /* check_usage */ true));
+      if (input_vdex_file_ != nullptr && output_vdex_fd_ == input_vdex_fd_) {
+        use_existing_vdex_ = true;
+      }
+
+      std::unique_ptr<File> vdex_file(new File(DupCloexec(output_vdex_fd_),
+                                               vdex_location,
+                                               /* check_usage= */ true,
+                                               /* read_only_mode= */ use_existing_vdex_));
       if (!vdex_file->IsOpened()) {
         PLOG(ERROR) << "Failed to create vdex file: " << vdex_location;
         return false;
       }
-      if (input_vdex_file_ != nullptr && output_vdex_fd_ == input_vdex_fd_) {
-        update_input_vdex_ = true;
-      } else {
+
+      if (!use_existing_vdex_) {
         if (vdex_file->SetLength(0) != 0) {
           PLOG(ERROR) << "Truncating vdex file " << vdex_location << " failed.";
           vdex_file->Erase();
@@ -1321,26 +1323,6 @@
       oat_filenames_.push_back(oat_location_);
     }
 
-    // If we're updating in place a vdex file, be defensive and put an invalid vdex magic in case
-    // dex2oat gets killed.
-    // Note: we're only invalidating the magic data in the file, as dex2oat needs the rest of
-    // the information to remain valid.
-    if (update_input_vdex_) {
-      File* vdex_file = vdex_files_.back().get();
-      if (!vdex_file->PwriteFully(&VdexFile::VdexFileHeader::kVdexInvalidMagic,
-                                  arraysize(VdexFile::VdexFileHeader::kVdexInvalidMagic),
-                                  /*offset=*/ 0u)) {
-        PLOG(ERROR) << "Failed to invalidate vdex header. File: " << vdex_file->GetPath();
-        return false;
-      }
-
-      if (vdex_file->Flush() != 0) {
-        PLOG(ERROR) << "Failed to flush stream after invalidating header of vdex file."
-                    << " File: " << vdex_file->GetPath();
-        return false;
-      }
-    }
-
     if (dm_fd_ != -1 || !dm_file_location_.empty()) {
       std::string error_msg;
       if (dm_fd_ != -1) {
@@ -1389,9 +1371,12 @@
   void EraseOutputFiles() {
     for (auto& files : { &vdex_files_, &oat_files_ }) {
       for (size_t i = 0; i < files->size(); ++i) {
-        if ((*files)[i].get() != nullptr) {
-          (*files)[i]->Erase();
-          (*files)[i].reset();
+        auto& file = (*files)[i];
+        if (file != nullptr) {
+          if (!file->ReadOnlyMode()) {
+            file->Erase();
+          }
+          file.reset();
         }
       }
     }
@@ -1455,7 +1440,7 @@
         if (!oat_writers_[i]->WriteAndOpenDexFiles(
             vdex_files_[i].get(),
             verify,
-            update_input_vdex_,
+            use_existing_vdex_,
             copy_dex_files_,
             &opened_dex_files_map,
             &opened_dex_files)) {
@@ -1695,21 +1680,16 @@
       }
     }
 
-    // Ensure opened dex files are writable for dex-to-dex transformations.
-    for (MemMap& map : opened_dex_files_maps_) {
-      if (!map.Protect(PROT_READ | PROT_WRITE)) {
-        PLOG(ERROR) << "Failed to make .dex files writeable.";
-        return dex2oat::ReturnCode::kOther;
-      }
-    }
-
     // Setup VerifierDeps for compilation and report if we fail to parse the data.
-    if (!DoEagerUnquickeningOfVdex() && input_vdex_file_ != nullptr) {
+    // When we do profile guided optimizations, the compiler currently needs to run
+    // full verification.
+    if (!DoProfileGuidedOptimizations() && input_vdex_file_ != nullptr) {
       std::unique_ptr<verifier::VerifierDeps> verifier_deps(
           new verifier::VerifierDeps(dex_files, /*output_only=*/ false));
       if (!verifier_deps->ParseStoredData(dex_files, input_vdex_file_->GetVerifierDepsData())) {
         return dex2oat::ReturnCode::kOther;
       }
+      // We can do fast verification.
       callbacks_->SetVerifierDeps(verifier_deps.release());
     } else {
       // Create the main VerifierDeps, here instead of in the compiler since we want to aggregate
@@ -1792,7 +1772,7 @@
     // This means extract, no-vdex verify, and quicken, will use the individual compilation
     // mode (to reduce RAM used by the compiler).
     return compile_individually_ &&
-           (!IsImage() && !update_input_vdex_ &&
+           (!IsImage() && !use_existing_vdex_ &&
             compiler_options_->dex_files_for_oat_file_.size() > 1 &&
             !CompilerFilter::IsAotCompilationEnabled(compiler_options_->GetCompilerFilter()));
   }
@@ -2069,7 +2049,7 @@
       oat_writer->Initialize(driver_.get(), image_writer_.get(), dex_files);
     }
 
-    {
+    if (!use_existing_vdex_) {
       TimingLogger::ScopedTiming t2("dex2oat Write VDEX", timings_);
       DCHECK(IsBootImage() || IsBootImageExtension() || oat_files_.size() == 1u);
       verifier::VerifierDeps* verifier_deps = callbacks_->GetVerifierDeps();
@@ -2235,7 +2215,7 @@
   }
 
   bool FlushOutputFile(std::unique_ptr<File>* file) {
-    if (file->get() != nullptr) {
+    if ((file->get() != nullptr) && !file->get()->ReadOnlyMode()) {
       if (file->get()->Flush() != 0) {
         PLOG(ERROR) << "Failed to flush output file: " << file->get()->GetPath();
         return false;
@@ -2245,7 +2225,7 @@
   }
 
   bool FlushCloseOutputFile(File* file) {
-    if (file != nullptr) {
+    if ((file != nullptr) && !file->ReadOnlyMode()) {
       if (file->FlushCloseOrErase() != 0) {
         PLOG(ERROR) << "Failed to flush and close output file: " << file->GetPath();
         return false;
@@ -2325,16 +2305,6 @@
     return DoProfileGuidedOptimizations();
   }
 
-  bool MayInvalidateVdexMetadata() const {
-    // DexLayout can invalidate the vdex metadata if changing the class def order is enabled, so
-    // we need to unquicken the vdex file eagerly, before passing it to dexlayout.
-    return DoDexLayoutOptimizations();
-  }
-
-  bool DoEagerUnquickeningOfVdex() const {
-    return MayInvalidateVdexMetadata() && dm_file_ == nullptr;
-  }
-
   bool LoadProfile() {
     DCHECK(HasProfileInput());
     profile_load_attempted_ = true;
@@ -2948,7 +2918,7 @@
   std::string classpath_dir_;
 
   // Whether the given input vdex is also the output.
-  bool update_input_vdex_ = false;
+  bool use_existing_vdex_ = false;
 
   // By default, copy the dex to the vdex file only if dex files are
   // compressed in APK.
diff --git a/dex2oat/dex2oat_test.cc b/dex2oat/dex2oat_test.cc
index 557675d..b340c80 100644
--- a/dex2oat/dex2oat_test.cc
+++ b/dex2oat/dex2oat_test.cc
@@ -1812,7 +1812,6 @@
     std::unique_ptr<VdexFile> vdex(VdexFile::Open(vdex_location.c_str(),
                                                   /*writable=*/ false,
                                                   /*low_4gb=*/ false,
-                                                  /*unquicken=*/ false,
                                                   &error_msg));
     ASSERT_TRUE(vdex != nullptr);
     EXPECT_FALSE(vdex->HasDexSection()) << output_;
diff --git a/dex2oat/dex2oat_vdex_test.cc b/dex2oat/dex2oat_vdex_test.cc
index 1f486e6..895e9c2 100644
--- a/dex2oat/dex2oat_vdex_test.cc
+++ b/dex2oat/dex2oat_vdex_test.cc
@@ -73,7 +73,6 @@
     std::unique_ptr<VdexFile> vdex(VdexFile::Open(vdex_location.c_str(),
                                                   /*writable=*/ false,
                                                   /*low_4gb=*/ false,
-                                                  /*unquicken=*/ false,
                                                   &error_msg_));
     // Check the vdex doesn't have dex.
     if (vdex->HasDexSection()) {
diff --git a/dex2oat/driver/compiler_driver.cc b/dex2oat/driver/compiler_driver.cc
index 0f4638d..caf8753 100644
--- a/dex2oat/driver/compiler_driver.cc
+++ b/dex2oat/driver/compiler_driver.cc
@@ -487,7 +487,8 @@
       }
     } else if ((access_flags & kAccAbstract) != 0) {
       // Abstract methods don't have code.
-    } else if (annotations::MethodIsNeverCompile(dex_file, dex_file.GetClassDef(class_def_idx),
+    } else if (annotations::MethodIsNeverCompile(dex_file,
+                                                 dex_file.GetClassDef(class_def_idx),
                                                  method_idx)) {
       // Method is annotated with @NeverCompile and should not be compiled.
     } else {
diff --git a/dex2oat/linker/oat_writer.cc b/dex2oat/linker/oat_writer.cc
index 5ac2bbf..70260bc 100644
--- a/dex2oat/linker/oat_writer.cc
+++ b/dex2oat/linker/oat_writer.cc
@@ -683,21 +683,22 @@
 bool OatWriter::WriteAndOpenDexFiles(
     File* vdex_file,
     bool verify,
-    bool update_input_vdex,
+    bool use_existing_vdex,
     CopyOption copy_dex_files,
     /*out*/ std::vector<MemMap>* opened_dex_files_map,
     /*out*/ std::vector<std::unique_ptr<const DexFile>>* opened_dex_files) {
   CHECK(write_state_ == WriteState::kAddingDexFileSources);
 
+  // Reserve space for Vdex header, sections, and checksums.
   size_vdex_header_ = sizeof(VdexFile::VdexFileHeader) +
       VdexSection::kNumberOfSections * sizeof(VdexFile::VdexSectionHeader);
-  // Reserve space for Vdex header, sections, and checksums.
-  vdex_size_ = size_vdex_header_ + oat_dex_files_.size() * sizeof(VdexFile::VdexChecksum);
+  size_vdex_checksums_ = oat_dex_files_.size() * sizeof(VdexFile::VdexChecksum);
+  vdex_size_ = size_vdex_header_ + size_vdex_checksums_;
 
   // Write DEX files into VDEX, mmap and open them.
   std::vector<MemMap> dex_files_map;
   std::vector<std::unique_ptr<const DexFile>> dex_files;
-  if (!WriteDexFiles(vdex_file, update_input_vdex, copy_dex_files, &dex_files_map) ||
+  if (!WriteDexFiles(vdex_file, use_existing_vdex, copy_dex_files, &dex_files_map) ||
       !OpenDexFiles(vdex_file, verify, &dex_files_map, &dex_files)) {
     return false;
   }
@@ -3103,7 +3104,7 @@
 }
 
 bool OatWriter::WriteDexFiles(File* file,
-                              bool update_input_vdex,
+                              bool use_existing_vdex,
                               CopyOption copy_dex_files,
                               /*out*/ std::vector<MemMap>* opened_dex_files_map) {
   TimingLogger::ScopedTiming split("Write Dex files", timings_);
@@ -3136,8 +3137,8 @@
     if (profile_compilation_info_ != nullptr ||
         compact_dex_level_ != CompactDexLevel::kCompactDexLevelNone) {
       for (OatDexFile& oat_dex_file : oat_dex_files_) {
-        // update_input_vdex disables compact dex and layout.
-        CHECK(!update_input_vdex)
+        // use_existing_vdex should not be used with compact dex and layout.
+        CHECK(!use_existing_vdex)
             << "We should never update the input vdex when doing dexlayout or compact dex";
         if (!LayoutDexFile(&oat_dex_file)) {
           return false;
@@ -3202,7 +3203,7 @@
     // Extend the file and include the full page at the end as we need to write
     // additional data there and do not want to mmap that page twice.
     size_t page_aligned_size = RoundUp(vdex_size_with_dex_files, kPageSize);
-    if (!update_input_vdex) {
+    if (!use_existing_vdex) {
       if (file->SetLength(page_aligned_size) != 0) {
         PLOG(ERROR) << "Failed to resize vdex file " << file->GetPath();
         return false;
@@ -3212,7 +3213,7 @@
     std::string error_msg;
     MemMap dex_files_map = MemMap::MapFile(
         page_aligned_size,
-        PROT_READ | PROT_WRITE,
+        use_existing_vdex ? PROT_READ : PROT_READ | PROT_WRITE,
         MAP_SHARED,
         file->Fd(),
         /*start=*/ 0u,
@@ -3233,7 +3234,7 @@
       vdex_size_ = RoundUp(vdex_size_, 4u);
       size_dex_file_alignment_ += vdex_size_ - old_vdex_size;
       // Write the actual dex file.
-      if (!WriteDexFile(file, &oat_dex_file, update_input_vdex)) {
+      if (!WriteDexFile(file, &oat_dex_file, use_existing_vdex)) {
         return false;
       }
     }
@@ -3241,27 +3242,25 @@
     // Write shared dex file data section and fix up the dex file headers.
     if (shared_data_size != 0u) {
       DCHECK_EQ(RoundUp(vdex_size_, 4u), vdex_dex_shared_data_offset_);
-      if (!update_input_vdex) {
-        memset(vdex_begin_ + vdex_size_, 0, vdex_dex_shared_data_offset_ - vdex_size_);
-      }
+      DCHECK(!use_existing_vdex);
+      memset(vdex_begin_ + vdex_size_, 0, vdex_dex_shared_data_offset_ - vdex_size_);
       size_dex_file_alignment_ += vdex_dex_shared_data_offset_ - vdex_size_;
       vdex_size_ = vdex_dex_shared_data_offset_;
 
       if (dex_container_ != nullptr) {
-        CHECK(!update_input_vdex) << "Update input vdex should have empty dex container";
+        CHECK(!use_existing_vdex) << "Use existing vdex should have empty dex container";
         CHECK(compact_dex_level_ != CompactDexLevel::kCompactDexLevelNone);
         DexContainer::Section* const section = dex_container_->GetDataSection();
         DCHECK_EQ(shared_data_size, section->Size());
         memcpy(vdex_begin_ + vdex_size_, section->Begin(), shared_data_size);
         section->Clear();
         dex_container_.reset();
-      } else if (!update_input_vdex) {
-        // If we are not updating the input vdex, write out the shared data section.
+      } else {
         memcpy(vdex_begin_ + vdex_size_, raw_dex_file_shared_data_begin, shared_data_size);
       }
       vdex_size_ += shared_data_size;
       size_dex_file_ += shared_data_size;
-      if (!update_input_vdex) {
+      if (!use_existing_vdex) {
         // Fix up the dex headers to have correct offsets to the data section.
         for (OatDexFile& oat_dex_file : oat_dex_files_) {
           DexFile::Header* header =
@@ -3283,6 +3282,14 @@
     vdex_dex_shared_data_offset_ = vdex_size_;
   }
 
+  if (use_existing_vdex) {
+    // If we re-use an existing vdex, artificially set the verifier deps size,
+    // so the compiler has a correct computation of the vdex size.
+    size_t actual_size = file->GetLength();
+    size_verifier_deps_ = actual_size - vdex_size_;
+    vdex_size_ = actual_size;
+  }
+
   return true;
 }
 
@@ -3297,22 +3304,22 @@
 
 bool OatWriter::WriteDexFile(File* file,
                              OatDexFile* oat_dex_file,
-                             bool update_input_vdex) {
+                             bool use_existing_vdex) {
   DCHECK_EQ(vdex_size_, oat_dex_file->dex_file_offset_);
   if (oat_dex_file->source_.IsZipEntry()) {
-    DCHECK(!update_input_vdex);
+    DCHECK(!use_existing_vdex);
     if (!WriteDexFile(file, oat_dex_file, oat_dex_file->source_.GetZipEntry())) {
       return false;
     }
   } else if (oat_dex_file->source_.IsRawFile()) {
-    DCHECK(!update_input_vdex);
+    DCHECK(!use_existing_vdex);
     if (!WriteDexFile(file, oat_dex_file, oat_dex_file->source_.GetRawFile())) {
       return false;
     }
   } else {
     DCHECK(oat_dex_file->source_.IsRawData());
     const uint8_t* raw_data = oat_dex_file->source_.GetRawData();
-    if (!WriteDexFile(oat_dex_file, raw_data, update_input_vdex)) {
+    if (!WriteDexFile(oat_dex_file, raw_data, use_existing_vdex)) {
       return false;
     }
   }
@@ -3447,14 +3454,14 @@
 
 bool OatWriter::WriteDexFile(OatDexFile* oat_dex_file,
                              const uint8_t* dex_file,
-                             bool update_input_vdex) {
+                             bool use_existing_vdex) {
   // Note: The raw data has already been checked to contain the header
   // and all the data that the header specifies as the file size.
   DCHECK(dex_file != nullptr);
   DCHECK(ValidateDexFileHeader(dex_file, oat_dex_file->GetLocation()));
   DCHECK_EQ(oat_dex_file->dex_file_size_, AsUnalignedDexFileHeader(dex_file)->file_size_);
 
-  if (update_input_vdex) {
+  if (use_existing_vdex) {
     // The vdex already contains the dex code, no need to write it again.
   } else {
     uint8_t* raw_output = vdex_begin_ + oat_dex_file->dex_file_offset_;
@@ -3763,7 +3770,6 @@
   for (size_t i = 0, size = oat_dex_files_.size(); i != size; ++i) {
     OatDexFile* oat_dex_file = &oat_dex_files_[i];
     checksums_data[i] = oat_dex_file->dex_file_location_checksum_;
-    size_vdex_checksums_ += sizeof(VdexFile::VdexChecksum);
   }
 
   // Write sections.
diff --git a/dex2oat/linker/oat_writer.h b/dex2oat/linker/oat_writer.h
index 87174eb..ad14d60 100644
--- a/dex2oat/linker/oat_writer.h
+++ b/dex2oat/linker/oat_writer.h
@@ -157,11 +157,11 @@
   // Write raw dex files to the vdex file, mmap the file and open the dex files from it.
   // The `verify` setting dictates whether the dex file verifier should check the dex files.
   // This is generally the case, and should only be false for tests.
-  // If `update_input_vdex` is true, then this method won't actually write the dex files,
+  // If `use_existing_vdex` is true, then this method won't actually write the dex files,
   // and the compiler will just re-use the existing vdex file.
   bool WriteAndOpenDexFiles(File* vdex_file,
                             bool verify,
-                            bool update_input_vdex,
+                            bool use_existing_vdex,
                             CopyOption copy_dex_files,
                             /*out*/ std::vector<MemMap>* opened_dex_files_map,
                             /*out*/ std::vector<std::unique_ptr<const DexFile>>* opened_dex_files);
@@ -276,12 +276,12 @@
   // If `update_input_vdex` is true, then this method won't actually write the dex files,
   // and the compiler will just re-use the existing vdex file.
   bool WriteDexFiles(File* file,
-                     bool update_input_vdex,
+                     bool use_existing_vdex,
                      CopyOption copy_dex_files,
                      /*out*/ std::vector<MemMap>* opened_dex_files_map);
   bool WriteDexFile(File* file,
                     OatDexFile* oat_dex_file,
-                    bool update_input_vdex);
+                    bool use_existing_vdex);
   bool LayoutDexFile(OatDexFile* oat_dex_file);
   bool WriteDexFile(File* file,
                     OatDexFile* oat_dex_file,
@@ -291,7 +291,7 @@
                     File* dex_file);
   bool WriteDexFile(OatDexFile* oat_dex_file,
                     const uint8_t* dex_file,
-                    bool update_input_vdex);
+                    bool use_existing_vdex);
   bool OpenDexFiles(File* file,
                     bool verify,
                     /*inout*/ std::vector<MemMap>* opened_dex_files_map,
diff --git a/dexlayout/dexdiag.cc b/dexlayout/dexdiag.cc
index a9ea27d..9aa0353 100644
--- a/dexlayout/dexdiag.cc
+++ b/dexlayout/dexdiag.cc
@@ -330,7 +330,6 @@
   std::unique_ptr<VdexFile> vdex(VdexFile::Open(vdex_name,
                                                 /*writable=*/ false,
                                                 /*low_4gb=*/ false,
-                                                /*unquicken= */ false,
                                                 &error_msg /*out*/));
   if (vdex == nullptr) {
     std::cerr << "Could not open vdex file "
diff --git a/dexlayout/dexlayout.h b/dexlayout/dexlayout.h
index 9bb7b8b..bdc7863 100644
--- a/dexlayout/dexlayout.h
+++ b/dexlayout/dexlayout.h
@@ -99,7 +99,10 @@
 
   // Setting this to false disables class def layout entirely, which is stronger than strictly
   // necessary to ensure the partial order w.r.t. class derivation. TODO: Re-enable (b/68317550).
+  // This should never be set for a device build, as changing class defs ids
+  // conflict with profiles and verification passed by Play.
   static constexpr bool kChangeClassDefOrder = false;
+  static_assert(!(kIsTargetBuild && kChangeClassDefOrder), "Never set class reordering on target");
 
   DexLayout(Options& options,
             ProfileCompilationInfo* info,
diff --git a/libartbase/Android.bp b/libartbase/Android.bp
index 06c8cfc..1f034e6 100644
--- a/libartbase/Android.bp
+++ b/libartbase/Android.bp
@@ -236,13 +236,11 @@
     static: {
         whole_static_libs: [
             "libc++fs",
-            "libprocinfo",
         ],
     },
     shared: {
         static_libs: [
             "libc++fs",
-            "libprocinfo",
         ],
     },
 }
diff --git a/libartbase/base/common_art_test.cc b/libartbase/base/common_art_test.cc
index 20bc87c..593ea25 100644
--- a/libartbase/base/common_art_test.cc
+++ b/libartbase/base/common_art_test.cc
@@ -50,7 +50,6 @@
 #include "dex/primitive.h"
 #include "gtest/gtest.h"
 #include "nativehelper/scoped_local_ref.h"
-#include "procinfo/process.h"
 
 namespace art {
 
@@ -680,12 +679,16 @@
 std::vector<pid_t> GetPidByName(const std::string& process_name) {
   std::vector<pid_t> results;
   for (pid_t pid : android::base::AllPids{}) {
-    android::procinfo::ProcessInfo process_info;
-    std::string error;
-    if (!android::procinfo::GetProcessInfo(pid, &process_info, &error)) {
+    std::string cmdline;
+    if (!android::base::ReadFileToString(StringPrintf("/proc/%d/cmdline", pid), &cmdline)) {
       continue;
     }
-    if (process_info.name == process_name) {
+    // Take the first argument.
+    size_t pos = cmdline.find('\0');
+    if (pos != std::string::npos) {
+      cmdline.resize(pos);
+    }
+    if (cmdline == process_name) {
       results.push_back(pid);
     }
   }
diff --git a/libartbase/base/common_art_test.h b/libartbase/base/common_art_test.h
index a33c632..6124ed9 100644
--- a/libartbase/base/common_art_test.h
+++ b/libartbase/base/common_art_test.h
@@ -287,8 +287,8 @@
 template <typename Param>
 using CommonArtTestWithParam = CommonArtTestBase<testing::TestWithParam<Param>>;
 
-// Returns a list of PIDs of the processes whose process name (the name of the binary without path)
-// fully matches the given name.
+// Returns a list of PIDs of the processes whose process name (the first commandline argument) fully
+// matches the given name.
 std::vector<pid_t> GetPidByName(const std::string& process_name);
 
 #define TEST_DISABLED_FOR_TARGET() \
diff --git a/libartbase/base/file_utils.cc b/libartbase/base/file_utils.cc
index bc08f30..07402a3 100644
--- a/libartbase/base/file_utils.cc
+++ b/libartbase/base/file_utils.cc
@@ -283,65 +283,33 @@
   return GetAndroidDir(kArtApexDataEnvVar, kArtApexDataDefaultPath, /*must_exist=*/false);
 }
 
-static std::string GetFirstBootClasspathExtensionJar(const std::string& android_root) {
-  DCHECK(kIsTargetBuild);
-
-  // This method finds the first non-APEX DEX file in the boot class path as defined by the
-  // DEX2OATBOOTCLASSPATH environment variable. This corresponds to the first boot classpath
-  // extension (see IMAGE SECTION documentation in image.h). When on-device signing is used the
-  // boot class extensions are compiled together as a single image with a name derived from the
-  // first extension. This first boot classpath extension is usually
-  // '/system/framework/framework.jar'.
-  //
-  // DEX2OATBOOTCLASSPATH is generated at build time by in the init.environ.rc.in:
-  //   ${ANDROID_BUILD_TOP}/system/core/rootdir/Android.mk
-  // and initialized on Android by init in init.environ.rc:
-  //   ${ANDROID_BUILD_TOP}/system/core/rootdir/init.environ.rc.in.
-  // It is used by installd too.
-  const char* bcp = getenv("DEX2OATBOOTCLASSPATH");
-  const std::string kDefaultBcpExtensionJar = android_root + "/framework/framework.jar";
-  if (bcp != nullptr) {
-    for (std::string_view component : SplitString(bcp, ':')) {
-      if (component.empty()) {
-        continue;
-      }
-      if (!LocationIsOnApex(component)) {
-        return std::string{component};
-      }
-    }
-  }
-  return kDefaultBcpExtensionJar;
-}
-
 std::string GetDefaultBootImageLocation(const std::string& android_root,
                                         bool deny_art_apex_data_files) {
   constexpr static const char* kJavalibBootArt = "javalib/boot.art";
   constexpr static const char* kEtcBootImageProf = "etc/boot-image.prof";
 
-  // Boot image consists of two parts:
-  //  - the primary boot image in the ART APEX (contains the Core Libraries)
-  //  - the boot image extensions (contains framework libraries) on the system partition, or
-  //    in the ART APEX data directory, if an update for the ART module has been been installed.
+  // If an update for the ART module has been been installed, a single boot image for the entire
+  // bootclasspath is in the ART APEX data directory.
   if (kIsTargetBuild && !deny_art_apex_data_files) {
-    // If the ART APEX has been updated, the compiled boot image extension will be in the ART APEX
-    // data directory (assuming there is space and we trust the artifacts there). Otherwise, for a factory installed ART APEX it is
-    // under $ANDROID_ROOT/framework/.
-    const std::string first_extension_jar{GetFirstBootClasspathExtensionJar(android_root)};
-    const std::string boot_extension_image = GetApexDataBootImage(first_extension_jar);
-    const std::string boot_extension_filename =
-        GetSystemImageFilename(boot_extension_image.c_str(), kRuntimeISA);
-    if (OS::FileExists(boot_extension_filename.c_str(), /*check_file_type=*/true)) {
-      return StringPrintf("%s/%s:%s!%s/%s",
+    const std::string boot_image =
+        GetApexDataDalvikCacheDirectory(InstructionSet::kNone) + "/boot.art";
+    const std::string boot_image_filename = GetSystemImageFilename(boot_image.c_str(), kRuntimeISA);
+    if (OS::FileExists(boot_image_filename.c_str(), /*check_file_type=*/true)) {
+      return StringPrintf("%s!%s/%s!%s/%s",
+                          boot_image.c_str(),
                           kAndroidArtApexDefaultPath,
-                          kJavalibBootArt,
-                          boot_extension_image.c_str(),
+                          kEtcBootImageProf,
                           android_root.c_str(),
                           kEtcBootImageProf);
     } else if (errno == EACCES) {
       // Additional warning for potential SELinux misconfiguration.
-      PLOG(ERROR) << "Default boot image check failed, could not stat: " << boot_extension_image;
+      PLOG(ERROR) << "Default boot image check failed, could not stat: " << boot_image_filename;
     }
   }
+  // Boot image consists of two parts:
+  //  - the primary boot image in the ART APEX (contains the Core Libraries)
+  //  - the boot image extensions (contains framework libraries) on the system partition
+  // TODO(b/211973309): Update this once the primary boot image is moved.
   return StringPrintf("%s/%s:%s/framework/boot-framework.art!%s/%s",
                       kAndroidArtApexDefaultPath,
                       kJavalibBootArt,
@@ -358,18 +326,6 @@
   return GetDefaultBootImageLocation(android_root, /*deny_art_apex_data_files=*/false);
 }
 
-std::string GetBootImagePath(bool on_system, const std::string& jar_path) {
-  if (on_system) {
-    const std::string jar_name = android::base::Basename(jar_path);
-    const std::string image_name = ReplaceFileExtension(jar_name, "art");
-    // Typically "/system/framework/boot-framework.art".
-    return StringPrintf("%s/framework/boot-%s", GetAndroidRoot().c_str(), image_name.c_str());
-  } else {
-    // Typically "/data/misc/apexdata/com.android.art/dalvik-cache/boot-framework.art".
-    return GetApexDataBootImage(jar_path);
-  }
-}
-
 static /*constinit*/ std::string_view dalvik_cache_sub_dir = "dalvik-cache";
 
 void OverrideDalvikCacheSubDirectory(std::string sub_dir) {
diff --git a/libartbase/base/file_utils.h b/libartbase/base/file_utils.h
index 709ac12..97abc73 100644
--- a/libartbase/base/file_utils.h
+++ b/libartbase/base/file_utils.h
@@ -77,9 +77,6 @@
 std::string GetDefaultBootImageLocation(const std::string& android_root,
                                         bool deny_art_apex_data_files);
 
-// Returns the boot image path of the provided jar, on /system or /data.
-std::string GetBootImagePath(bool on_system, const std::string& jar_path);
-
 // Allows the name to be used for the dalvik cache directory (normally "dalvik-cache") to be
 // overridden with a new value.
 void OverrideDalvikCacheSubDirectory(std::string sub_dir);
diff --git a/odrefresh/odr_artifacts.h b/odrefresh/odr_artifacts.h
index 66d76f0..30932e2 100644
--- a/odrefresh/odr_artifacts.h
+++ b/odrefresh/odr_artifacts.h
@@ -28,7 +28,7 @@
 // A grouping of odrefresh generated artifacts.
 class OdrArtifacts {
  public:
-  static OdrArtifacts ForBootImageExtension(const std::string& image_path) {
+  static OdrArtifacts ForBootImage(const std::string& image_path) {
     return OdrArtifacts(image_path, "oat");
   }
 
diff --git a/odrefresh/odr_artifacts_test.cc b/odrefresh/odr_artifacts_test.cc
index 76f2c28..b47f2a7 100644
--- a/odrefresh/odr_artifacts_test.cc
+++ b/odrefresh/odr_artifacts_test.cc
@@ -27,7 +27,7 @@
 
 static constexpr const char* kOdrefreshArtifactDirectory = "/test/dir";
 
-TEST(OdrArtifactsTest, ForBootImageExtension) {
+TEST(OdrArtifactsTest, ForBootImage) {
   ScopedUnsetEnvironmentVariable no_env("ART_APEX_DATA");
   setenv("ART_APEX_DATA", kOdrefreshArtifactDirectory, /* overwrite */ 1);
 
@@ -37,7 +37,7 @@
   const std::string image_filename =
       GetSystemImageFilename(image_location.c_str(), InstructionSet::kArm64);
 
-  const auto artifacts = OdrArtifacts::ForBootImageExtension(image_filename);
+  const auto artifacts = OdrArtifacts::ForBootImage(image_filename);
   CHECK_EQ(std::string(kOdrefreshArtifactDirectory) + "/dalvik-cache/arm64/boot-framework.art",
            artifacts.ImagePath());
   CHECK_EQ(std::string(kOdrefreshArtifactDirectory) + "/dalvik-cache/arm64/boot-framework.oat",
diff --git a/odrefresh/odr_config.h b/odrefresh/odr_config.h
index 9a8b6ba..7b89a83 100644
--- a/odrefresh/odr_config.h
+++ b/odrefresh/odr_config.h
@@ -71,7 +71,7 @@
   time_t max_execution_seconds_ = kMaximumExecutionSeconds;
   time_t max_child_process_seconds_ = kMaxChildProcessSeconds;
   std::string standalone_system_server_jars_;
-  bool compilation_os_mode_;
+  bool compilation_os_mode_ = false;
 
   // Staging directory for artifacts. The directory must exist and will be automatically removed
   // after compilation. If empty, use the default directory.
@@ -87,7 +87,7 @@
 
   const std::string& GetApexInfoListFile() const { return apex_info_list_file_; }
 
-  std::vector<InstructionSet> GetBootExtensionIsas() const {
+  std::vector<InstructionSet> GetBootClasspathIsas() const {
     const auto [isa32, isa64] = GetPotentialInstructionSets();
     switch (zygote_kind_) {
       case ZygoteKind::kZygote32:
diff --git a/odrefresh/odrefresh.cc b/odrefresh/odrefresh.cc
index d3cffc6..857e8ba 100644
--- a/odrefresh/odrefresh.cc
+++ b/odrefresh/odrefresh.cc
@@ -112,6 +112,8 @@
 
 constexpr mode_t kFileMode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
 
+constexpr const char* kFirstBootImageBasename = "boot.art";
+
 void EraseFiles(const std::vector<std::unique_ptr<File>>& files) {
   for (auto& file : files) {
     file->Erase(/*unlink=*/true);
@@ -208,7 +210,7 @@
 }
 
 // Returns a rewritten path based on ANDROID_ROOT if the path starts with "/system/".
-std::string AndroidRootRewrite(const std::string &path) {
+std::string AndroidRootRewrite(const std::string& path) {
   if (StartsWith(path, "/system/")) {
     return Concatenate({GetAndroidRoot(), path.substr(7)});
   } else {
@@ -414,20 +416,30 @@
   return true;
 }
 
+std::string GetBootImageComponentBasename(const std::string& jar_path, bool is_first_jar) {
+  if (is_first_jar) {
+    return kFirstBootImageBasename;
+  }
+  const std::string jar_name = android::base::Basename(jar_path);
+  return "boot-" + ReplaceFileExtension(jar_name, "art");
+}
+
 void PrepareCompiledBootClasspathFdsIfAny(
     /*inout*/ DexoptSystemServerArgs& dexopt_args,
     /*inout*/ std::vector<std::unique_ptr<File>>& output_files,
     const std::vector<std::string>& bcp_jars,
     const InstructionSet isa,
-    bool on_system) {
+    const std::string& artifact_dir) {
   std::vector<int> bcp_image_fds;
   std::vector<int> bcp_oat_fds;
   std::vector<int> bcp_vdex_fds;
   std::vector<std::unique_ptr<File>> opened_files;
   bool added_any = false;
-  for (const std::string& jar : bcp_jars) {
-    std::string image_path = GetBootImagePath(on_system, jar);
-    image_path = image_path.empty() ? "" : GetSystemImageFilename(image_path.c_str(), isa);
+  for (size_t i = 0; i < bcp_jars.size(); i++) {
+    const std::string& jar = bcp_jars[i];
+    std::string image_path =
+        artifact_dir + "/" + GetBootImageComponentBasename(jar, /*is_first_jar=*/i == 0);
+    image_path = GetSystemImageFilename(image_path.c_str(), isa);
     std::unique_ptr<File> image_file(OS::OpenFileForReading(image_path.c_str()));
     if (image_file && image_file->IsValid()) {
       bcp_image_fds.push_back(image_file->Fd());
@@ -527,6 +539,10 @@
   }
 }
 
+std::string GetArtBootImageDir() { return GetArtRoot() + "/javalib"; }
+
+std::string GetSystemBootImageDir() { return GetAndroidRoot() + "/framework"; }
+
 }  // namespace
 
 OnDeviceRefresh::OnDeviceRefresh(const OdrConfig& config)
@@ -545,12 +561,9 @@
       exec_utils_{std::move(exec_utils)},
       odr_dexopt_{std::move(odr_dexopt)} {
   for (const std::string& jar : android::base::Split(config_.GetDex2oatBootClasspath(), ":")) {
-    // Boot class path extensions are those not in the ART APEX. Updatable APEXes should not
-    // have DEX files in the DEX2OATBOOTCLASSPATH. At the time of writing i18n is a non-updatable
-    // APEX and so does appear in the DEX2OATBOOTCLASSPATH.
-    if (!LocationIsOnArtModule(jar)) {
-      boot_extension_compilable_jars_.emplace_back(jar);
-    }
+    // Updatable APEXes should not have DEX files in the DEX2OATBOOTCLASSPATH. At the time of
+    // writing i18n is a non-updatable APEX and so does appear in the DEX2OATBOOTCLASSPATH.
+    boot_classpath_compilable_jars_.emplace_back(jar);
   }
 
   all_systemserver_jars_ = android::base::Split(config_.GetSystemServerClasspath(), ":");
@@ -566,9 +579,7 @@
   }
 }
 
-time_t OnDeviceRefresh::GetExecutionTimeUsed() const {
-  return time(nullptr) - start_time_;
-}
+time_t OnDeviceRefresh::GetExecutionTimeUsed() const { return time(nullptr) - start_time_; }
 
 time_t OnDeviceRefresh::GetExecutionTimeRemaining() const {
   return std::max(static_cast<time_t>(0),
@@ -589,8 +600,8 @@
   // We are only interested in active APEXes that contain compilable JARs.
   std::unordered_set<std::string_view> relevant_apexes;
   relevant_apexes.reserve(info_list->getApexInfo().size());
-  for (const std::vector<std::string>* jar_list : { &boot_extension_compilable_jars_,
-      &all_systemserver_jars_, &boot_classpath_jars_}) {
+  for (const std::vector<std::string>* jar_list :
+       {&boot_classpath_compilable_jars_, &all_systemserver_jars_, &boot_classpath_jars_}) {
     for (auto& jar : *jar_list) {
       std::string_view apex = ApexNameFromLocation(jar);
       if (!apex.empty()) {
@@ -606,8 +617,9 @@
   std::copy_if(info_list->getApexInfo().begin(),
                info_list->getApexInfo().end(),
                std::back_inserter(filtered_info_list),
-               [&](const apex::ApexInfo& info) { return info.getIsActive()
-                     && relevant_apexes.count(info.getModuleName()) != 0; });
+               [&](const apex::ApexInfo& info) {
+                 return info.getIsActive() && relevant_apexes.count(info.getModuleName()) != 0;
+               });
   return filtered_info_list;
 }
 
@@ -652,15 +664,15 @@
   }
 
   std::optional<std::vector<art_apex::Component>> bcp_compilable_components =
-      GenerateBootExtensionCompilableComponents();
+      GenerateBootClasspathCompilableComponents();
   if (!bcp_compilable_components.has_value()) {
-    return Errorf("No boot classpath extension compilable components.");
+    return Errorf("No boot classpath compilable components.");
   }
 
   std::optional<std::vector<art_apex::SystemServerComponent>> system_server_components =
       GenerateSystemServerComponents();
   if (!system_server_components.has_value()) {
-    return Errorf("No system_server extension components.");
+    return Errorf("No system_server components.");
   }
 
   std::ofstream out(cache_info_filename_.c_str());
@@ -697,9 +709,9 @@
   return GenerateComponents(boot_classpath_jars_);
 }
 
-std::vector<art_apex::Component> OnDeviceRefresh::GenerateBootExtensionCompilableComponents()
+std::vector<art_apex::Component> OnDeviceRefresh::GenerateBootClasspathCompilableComponents()
     const {
-  return GenerateComponents(boot_extension_compilable_jars_);
+  return GenerateComponents(boot_classpath_compilable_jars_);
 }
 
 std::vector<art_apex::SystemServerComponent> OnDeviceRefresh::GenerateSystemServerComponents()
@@ -712,16 +724,37 @@
       });
 }
 
-std::string OnDeviceRefresh::GetBootImageExtensionImage(bool on_system) const {
-  CHECK(!boot_extension_compilable_jars_.empty());
-  const std::string leading_jar = boot_extension_compilable_jars_[0];
-  return GetBootImagePath(on_system, leading_jar);
+std::string OnDeviceRefresh::GetBootImage(bool on_system) const {
+  if (on_system) {
+    // Typically "/apex/com.android.art/javalib/boot.art".
+    // TODO(b/211973309): Update this once the primary boot image is moved.
+    return GetArtBootImageDir() + "/" + kFirstBootImageBasename;
+  } else {
+    // Typically "/data/misc/apexdata/com.android.art/dalvik-cache/boot.art".
+    return config_.GetArtifactDirectory() + "/" + kFirstBootImageBasename;
+  }
 }
 
-std::string OnDeviceRefresh::GetBootImageExtensionImagePath(bool on_system,
-                                                            const InstructionSet isa) const {
-  // Typically "/data/misc/apexdata/com.android.art/dalvik-cache/<isa>/boot-framework.art".
-  return GetSystemImageFilename(GetBootImageExtensionImage(on_system).c_str(), isa);
+std::string OnDeviceRefresh::GetBootImagePath(bool on_system, const InstructionSet isa) const {
+  // Typically "/data/misc/apexdata/com.android.art/dalvik-cache/<isa>/boot.art".
+  return GetSystemImageFilename(GetBootImage(on_system).c_str(), isa);
+}
+
+std::string OnDeviceRefresh::GetSystemBootImageExtension() const {
+  std::string art_root = GetArtRoot() + "/";
+  // Find the first boot extension jar.
+  auto it = std::find_if_not(
+      boot_classpath_compilable_jars_.begin(),
+      boot_classpath_compilable_jars_.end(),
+      [&](const std::string& jar) { return android::base::StartsWith(jar, art_root); });
+  CHECK(it != boot_classpath_compilable_jars_.end());
+  // Typically "/system/framework/boot-framework.art".
+  return GetSystemBootImageDir() + "/" + GetBootImageComponentBasename(*it, /*is_first_jar=*/false);
+}
+
+std::string OnDeviceRefresh::GetSystemBootImageExtensionPath(const InstructionSet isa) const {
+  // Typically "/system/framework/<isa>/boot-framework.art".
+  return GetSystemImageFilename(GetSystemBootImageExtension().c_str(), isa);
 }
 
 std::string OnDeviceRefresh::GetSystemServerImagePath(bool on_system,
@@ -752,14 +785,27 @@
   return RemoveDirectory(config_.GetArtifactDirectory());
 }
 
-WARN_UNUSED bool OnDeviceRefresh::BootExtensionArtifactsExist(
+WARN_UNUSED bool OnDeviceRefresh::BootClasspathArtifactsExist(
     bool on_system,
     const InstructionSet isa,
     /*out*/ std::string* error_msg,
     /*out*/ std::vector<std::string>* checked_artifacts) const {
-  const std::string apexdata_image_location = GetBootImageExtensionImagePath(on_system, isa);
-  const OdrArtifacts artifacts = OdrArtifacts::ForBootImageExtension(apexdata_image_location);
-  return ArtifactsExist(artifacts, /*check_art_file=*/true, error_msg, checked_artifacts);
+  std::string path = GetBootImagePath(on_system, isa);
+  OdrArtifacts artifacts = OdrArtifacts::ForBootImage(path);
+  if (!ArtifactsExist(artifacts, /*check_art_file=*/true, error_msg, checked_artifacts)) {
+    return false;
+  }
+  // There is a split between the primary boot image and the extension on /system, so they need to
+  // be checked separately. This does not apply to the boot image on /data.
+  if (on_system) {
+    std::string extension_path = GetSystemBootImageExtensionPath(isa);
+    OdrArtifacts extension_artifacts = OdrArtifacts::ForBootImage(extension_path);
+    if (!ArtifactsExist(
+            extension_artifacts, /*check_art_file=*/true, error_msg, checked_artifacts)) {
+      return false;
+    }
+  }
+  return true;
 }
 
 WARN_UNUSED bool OnDeviceRefresh::SystemServerArtifactsExist(
@@ -781,7 +827,7 @@
   return jars_missing_artifacts->empty();
 }
 
-WARN_UNUSED bool OnDeviceRefresh::CheckBootExtensionArtifactsAreUpToDate(
+WARN_UNUSED bool OnDeviceRefresh::CheckBootClasspathArtifactsAreUpToDate(
     OdrMetrics& metrics,
     const InstructionSet isa,
     const apex::ApexInfo& art_apex_info,
@@ -792,11 +838,11 @@
 
     // ART is not updated, so we can use the artifacts on /system. Check if they exist.
     std::string error_msg;
-    if (BootExtensionArtifactsExist(/*on_system=*/true, isa, &error_msg)) {
+    if (BootClasspathArtifactsExist(/*on_system=*/true, isa, &error_msg)) {
       return true;
     }
 
-    LOG(INFO) << "Incomplete boot extension artifacts on /system. " << error_msg;
+    LOG(INFO) << "Incomplete boot classpath artifacts on /system. " << error_msg;
     LOG(INFO) << "Checking cache.";
   }
 
@@ -857,7 +903,7 @@
   // The boot class components may change unexpectedly, for example an OTA could update
   // framework.jar.
   const std::vector<art_apex::Component> expected_bcp_compilable_components =
-      GenerateBootExtensionCompilableComponents();
+      GenerateBootClasspathCompilableComponents();
   if (expected_bcp_compilable_components.size() != 0 &&
       (!cache_info->hasDex2oatBootClasspath() ||
        !cache_info->getFirstDex2oatBootClasspath()->hasComponent())) {
@@ -878,8 +924,8 @@
 
   // Cache info looks good, check all compilation artifacts exist.
   std::string error_msg;
-  if (!BootExtensionArtifactsExist(/*on_system=*/false, isa, &error_msg, checked_artifacts)) {
-    LOG(INFO) << "Incomplete boot extension artifacts. " << error_msg;
+  if (!BootClasspathArtifactsExist(/*on_system=*/false, isa, &error_msg, checked_artifacts)) {
+    LOG(INFO) << "Incomplete boot classpath artifacts. " << error_msg;
     metrics.SetTrigger(OdrMetrics::Trigger::kMissingArtifacts);
     return false;
   }
@@ -1165,7 +1211,7 @@
 
   // Clean-up helper used to simplify clean-ups and handling failures there.
   auto cleanup_and_compile_all = [&, this]() {
-    compilation_options->compile_boot_extensions_for_isas = config_.GetBootExtensionIsas();
+    compilation_options->compile_boot_classpath_for_isas = config_.GetBootClasspathIsas();
     compilation_options->system_server_jars_to_compile = AllSystemServerJars();
     return RemoveArtifactsDirectory() ? ExitCode::kCompilationRequired : ExitCode::kCleanupFailed;
   };
@@ -1215,11 +1261,11 @@
   InstructionSet system_server_isa = config_.GetSystemServerIsa();
   std::vector<std::string> checked_artifacts;
 
-  for (const InstructionSet isa : config_.GetBootExtensionIsas()) {
-    if (!CheckBootExtensionArtifactsAreUpToDate(
+  for (const InstructionSet isa : config_.GetBootClasspathIsas()) {
+    if (!CheckBootClasspathArtifactsAreUpToDate(
             metrics, isa, art_apex_info.value(), cache_info, &checked_artifacts)) {
-      compilation_options->compile_boot_extensions_for_isas.push_back(isa);
-      // system_server artifacts are invalid without valid boot extension artifacts.
+      compilation_options->compile_boot_classpath_for_isas.push_back(isa);
+      // system_server artifacts are invalid without valid boot classpath artifacts.
       if (isa == system_server_isa) {
         compilation_options->system_server_jars_to_compile = AllSystemServerJars();
       }
@@ -1234,7 +1280,7 @@
                                           &checked_artifacts);
   }
 
-  bool compilation_required = (!compilation_options->compile_boot_extensions_for_isas.empty() ||
+  bool compilation_required = (!compilation_options->compile_boot_classpath_for_isas.empty() ||
                                !compilation_options->system_server_jars_to_compile.empty());
 
   // If partial compilation is disabled, we should compile everything regardless of what's in
@@ -1269,7 +1315,7 @@
   return compilation_required ? ExitCode::kCompilationRequired : ExitCode::kOkay;
 }
 
-WARN_UNUSED bool OnDeviceRefresh::CompileBootExtensionArtifacts(
+WARN_UNUSED bool OnDeviceRefresh::CompileBootClasspathArtifacts(
     const InstructionSet isa,
     const std::string& staging_dir,
     OdrMetrics& metrics,
@@ -1281,10 +1327,17 @@
   dexopt_args.isa = InstructionSetToAidlIsa(isa);
 
   std::vector<std::unique_ptr<File>> readonly_files_raii;
-  const std::string boot_profile_file(GetAndroidRoot() + "/etc/boot-image.prof");
+  dexopt_args.profileFds.resize(2);
+  const std::string art_boot_profile_file = GetArtRoot() + "/etc/boot-image.prof";
   if (!PrepareDex2OatProfileIfExists(
-          &dexopt_args.profileFd, &readonly_files_raii, boot_profile_file)) {
-    LOG(ERROR) << "Missing expected profile for boot extension: " << boot_profile_file;
+          &dexopt_args.profileFds[0], &readonly_files_raii, art_boot_profile_file)) {
+    LOG(ERROR) << "Missing expected ART boot profile: " << art_boot_profile_file;
+    return false;
+  }
+  const std::string framework_boot_profile_file = GetAndroidRoot() + "/etc/boot-image.prof";
+  if (!PrepareDex2OatProfileIfExists(
+          &dexopt_args.profileFds[1], &readonly_files_raii, framework_boot_profile_file)) {
+    LOG(ERROR) << "Missing expected framework boot profile: " << framework_boot_profile_file;
     return false;
   }
 
@@ -1297,8 +1350,8 @@
     LOG(WARNING) << "Missing dirty objects file : " << QuotePath(dirty_image_objects_file);
   }
 
-  // Add boot extensions to compile.
-  for (const std::string& component : boot_extension_compilable_jars_) {
+  // Add boot classpath jars to compile.
+  for (const std::string& component : boot_classpath_compilable_jars_) {
     std::string actual_path = AndroidRootRewrite(component);
     std::unique_ptr<File> file(OS::OpenFileForReading(actual_path.c_str()));
     dexopt_args.dexPaths.emplace_back(component);
@@ -1312,10 +1365,8 @@
     return false;
   }
 
-  const std::string image_location = GetBootImageExtensionImagePath(/*on_system=*/false, isa);
-  const OdrArtifacts artifacts = OdrArtifacts::ForBootImageExtension(image_location);
-  CHECK_EQ(GetApexDataOatFilename(boot_extension_compilable_jars_.front().c_str(), isa),
-           artifacts.OatPath());
+  const std::string image_location = GetBootImagePath(/*on_system=*/false, isa);
+  const OdrArtifacts artifacts = OdrArtifacts::ForBootImage(image_location);
 
   dexopt_args.oatLocation = artifacts.OatPath();
   const std::pair<const std::string, int*> location_kind_pairs[] = {
@@ -1356,7 +1407,7 @@
   }
 
   const time_t timeout = GetSubprocessTimeout();
-  LOG(INFO) << "Compiling boot extensions (" << isa << "): " << dexopt_args.toString()
+  LOG(INFO) << "Compiling boot classpath (" << isa << "): " << dexopt_args.toString()
             << " [timeout " << timeout << "s]";
   if (config_.GetDryRun()) {
     LOG(INFO) << "Compilation skipped (dry-run).";
@@ -1364,6 +1415,9 @@
   }
 
   bool timed_out = false;
+  // NOTE: The method `DexoptBcpExtension` actually compiles the entire boot classpath. We don't
+  // rename this method because it will eventually go away.
+  // TODO(b/211977683): Call dex2oat directly.
   int dex2oat_exit_code =
       odr_dexopt_->DexoptBcpExtension(dexopt_args, timeout, &timed_out, error_msg);
 
@@ -1471,13 +1525,19 @@
       return false;
     }
     std::string unused_error_msg;
-    // If the boot extension artifacts are not on /data, then boot extensions are not re-compiled
+    // If the boot classpath artifacts are not on /data, then the boot classpath are not re-compiled
     // and the artifacts must exist on /system.
     bool boot_image_on_system =
-        !BootExtensionArtifactsExist(/*on_system=*/false, isa, &unused_error_msg);
+        !BootClasspathArtifactsExist(/*on_system=*/false, isa, &unused_error_msg);
     PrepareCompiledBootClasspathFdsIfAny(
-        dexopt_args, readonly_files_raii, bcp_jars, isa, boot_image_on_system);
-    dexopt_args.isBootImageOnSystem = boot_image_on_system;
+        dexopt_args,
+        readonly_files_raii,
+        bcp_jars,
+        isa,
+        boot_image_on_system ? GetSystemBootImageDir() : config_.GetArtifactDirectory());
+    dexopt_args.bootImage = boot_image_on_system ? GetBootImage(/*on_system=*/true) + ":" +
+                                                       GetSystemBootImageExtension() :
+                                                   GetBootImage(/*on_system=*/false);
 
     dexopt_args.classloaderContext = classloader_context;
     if (!classloader_context.empty()) {
@@ -1575,16 +1635,16 @@
 
   uint32_t dex2oat_invocation_count = 0;
   uint32_t total_dex2oat_invocation_count =
-      compilation_options.compile_boot_extensions_for_isas.size() +
+      compilation_options.compile_boot_classpath_for_isas.size() +
       compilation_options.system_server_jars_to_compile.size();
   ReportNextBootAnimationProgress(dex2oat_invocation_count, total_dex2oat_invocation_count);
   auto advance_animation_progress = [&]() {
     ReportNextBootAnimationProgress(++dex2oat_invocation_count, total_dex2oat_invocation_count);
   };
 
-  const auto& bcp_instruction_sets = config_.GetBootExtensionIsas();
+  const auto& bcp_instruction_sets = config_.GetBootClasspathIsas();
   DCHECK(!bcp_instruction_sets.empty() && bcp_instruction_sets.size() <= 2);
-  for (const InstructionSet isa : compilation_options.compile_boot_extensions_for_isas) {
+  for (const InstructionSet isa : compilation_options.compile_boot_classpath_for_isas) {
     auto stage = (isa == bcp_instruction_sets.front()) ? OdrMetrics::Stage::kPrimaryBootClasspath :
                                                          OdrMetrics::Stage::kSecondaryBootClasspath;
     metrics.SetStage(stage);
@@ -1595,7 +1655,7 @@
       return ExitCode::kCompilationFailed;
     }
 
-    if (!CompileBootExtensionArtifacts(
+    if (!CompileBootClasspathArtifacts(
             isa, staging_dir, metrics, advance_animation_progress, &error_msg)) {
       LOG(ERROR) << "Compilation of BCP failed: " << error_msg;
       if (!config_.GetDryRun() && !RemoveDirectory(staging_dir)) {
diff --git a/odrefresh/odrefresh.h b/odrefresh/odrefresh.h
index bc6a68f..d749b2b 100644
--- a/odrefresh/odrefresh.h
+++ b/odrefresh/odrefresh.h
@@ -41,10 +41,10 @@
 
 struct CompilationOptions {
   // If true, update the cache info only and do not compile anything.
-  bool update_cache_info_only;
+  bool update_cache_info_only = false;
 
-  // If not empty, compile the bootclasspath extensions for ISAs in the list.
-  std::vector<InstructionSet> compile_boot_extensions_for_isas;
+  // If not empty, compile the bootclasspath jars for ISAs in the list.
+  std::vector<InstructionSet> compile_boot_classpath_for_isas;
 
   // If not empty, compile the system server jars in the list.
   std::set<std::string> system_server_jars_to_compile;
@@ -60,8 +60,7 @@
                   std::unique_ptr<ExecUtils> exec_utils,
                   std::unique_ptr<OdrDexopt> odr_dexopt);
 
-  // Returns the exit code, a list of ISAs that boot extensions should be compiled for, and a
-  // boolean indicating whether the system server should be compiled.
+  // Returns the exit code and specifies what should be compiled in `compilation_options`.
   WARN_UNUSED ExitCode
   CheckArtifactsAreUpToDate(OdrMetrics& metrics,
                             /*out*/ CompilationOptions* compilation_options) const;
@@ -94,13 +93,23 @@
 
   std::vector<com::android::art::Component> GenerateBootClasspathComponents() const;
 
-  std::vector<com::android::art::Component> GenerateBootExtensionCompilableComponents() const;
+  std::vector<com::android::art::Component> GenerateBootClasspathCompilableComponents() const;
 
   std::vector<com::android::art::SystemServerComponent> GenerateSystemServerComponents() const;
 
-  std::string GetBootImageExtensionImage(bool on_system) const;
+  // Returns the symbolic boot image location (without ISA).
+  std::string GetBootImage(bool on_system) const;
 
-  std::string GetBootImageExtensionImagePath(bool on_system, const InstructionSet isa) const;
+  // Returns the real boot image location (with ISA).
+  std::string GetBootImagePath(bool on_system, const InstructionSet isa) const;
+
+  // Returns the symbolic boot image extension location (without ISA). Note that this only applies
+  // to boot images on /system.
+  std::string GetSystemBootImageExtension() const;
+
+  // Returns the real boot image location extension (with ISA). Note that this only applies to boot
+  // images on /system.
+  std::string GetSystemBootImageExtensionPath(const InstructionSet isa) const;
 
   std::string GetSystemServerImagePath(bool on_system, const std::string& jar_path) const;
 
@@ -113,10 +122,10 @@
   // artifacts to fs-verity.
   android::base::Result<void> RefreshExistingArtifacts() const;
 
-  // Checks whether all boot extension artifacts are present. Returns true if all are present, false
+  // Checks whether all boot classpath artifacts are present. Returns true if all are present, false
   // otherwise.
   // If `checked_artifacts` is present, adds checked artifacts to `checked_artifacts`.
-  WARN_UNUSED bool BootExtensionArtifactsExist(
+  WARN_UNUSED bool BootClasspathArtifactsExist(
       bool on_system,
       const InstructionSet isa,
       /*out*/ std::string* error_msg,
@@ -132,10 +141,10 @@
       /*out*/ std::set<std::string>* jars_missing_artifacts,
       /*out*/ std::vector<std::string>* checked_artifacts = nullptr) const;
 
-  // Checks whether all boot extension artifacts are up to date. Returns true if all are present,
+  // Checks whether all boot classpath artifacts are up to date. Returns true if all are present,
   // false otherwise.
   // If `checked_artifacts` is present, adds checked artifacts to `checked_artifacts`.
-  WARN_UNUSED bool CheckBootExtensionArtifactsAreUpToDate(
+  WARN_UNUSED bool CheckBootClasspathArtifactsAreUpToDate(
       OdrMetrics& metrics,
       const InstructionSet isa,
       const com::android::apex::ApexInfo& art_apex_info,
@@ -153,7 +162,7 @@
       /*out*/ std::set<std::string>* jars_to_compile,
       /*out*/ std::vector<std::string>* checked_artifacts) const;
 
-  WARN_UNUSED bool CompileBootExtensionArtifacts(const InstructionSet isa,
+  WARN_UNUSED bool CompileBootClasspathArtifacts(const InstructionSet isa,
                                                  const std::string& staging_dir,
                                                  OdrMetrics& metrics,
                                                  const std::function<void()>& on_dex2oat_success,
@@ -172,8 +181,8 @@
   // Path to cache information file that is used to speed up artifact checking.
   const std::string cache_info_filename_;
 
-  // List of boot extension components that should be compiled.
-  std::vector<std::string> boot_extension_compilable_jars_;
+  // List of boot classpath components that should be compiled.
+  std::vector<std::string> boot_classpath_compilable_jars_;
 
   // Set of system_server components in SYSTEMSERVERCLASSPATH that should be compiled.
   std::unordered_set<std::string> systemserver_classpath_jars_;
diff --git a/odrefresh/odrefresh_main.cc b/odrefresh/odrefresh_main.cc
index 8262805..f93ee93 100644
--- a/odrefresh/odrefresh_main.cc
+++ b/odrefresh/odrefresh_main.cc
@@ -185,16 +185,14 @@
 NO_RETURN void UsageHelp(const char* argv0) {
   std::string name(android::base::Basename(argv0));
   UsageMsg("Usage: %s [OPTION...] ACTION", name.c_str());
-  UsageMsg("On-device refresh tool for boot class path extensions and system server");
+  UsageMsg("On-device refresh tool for boot classpath and system server");
   UsageMsg("following an update of the ART APEX.");
   UsageMsg("");
   UsageMsg("Valid ACTION choices are:");
   UsageMsg("");
   UsageMsg("--check          Check compilation artifacts are up-to-date based on metadata.");
-  UsageMsg("--compile        Compile boot class path extensions and system_server jars");
-  UsageMsg("                 when necessary.");
-  UsageMsg("--force-compile  Unconditionally compile the boot class path extensions and");
-  UsageMsg("                 system_server jars.");
+  UsageMsg("--compile        Compile boot classpath and system_server jars when necessary.");
+  UsageMsg("--force-compile  Unconditionally compile the bootclass path and system_server jars.");
   UsageMsg("--help           Display this help information.");
   UsageMsg("");
   UsageMsg("Available OPTIONs are:");
@@ -270,8 +268,8 @@
     }
     return odr.Compile(metrics,
                        CompilationOptions{
-                         .compile_boot_extensions_for_isas = config.GetBootExtensionIsas(),
-                         .system_server_jars_to_compile = odr.AllSystemServerJars(),
+                           .compile_boot_classpath_for_isas = config.GetBootClasspathIsas(),
+                           .system_server_jars_to_compile = odr.AllSystemServerJars(),
                        });
   } else if (action == "--help") {
     UsageHelp(argv[0]);
diff --git a/odrefresh/odrefresh_test.cc b/odrefresh/odrefresh_test.cc
index 7494084..370fd0d 100644
--- a/odrefresh/odrefresh_test.cc
+++ b/odrefresh/odrefresh_test.cc
@@ -159,8 +159,12 @@
 
     std::string system_etc_dir = Concatenate({android_root_path, "/etc"});
     ASSERT_TRUE(EnsureDirectoryExists(system_etc_dir));
-    boot_profile_file_ = system_etc_dir + "/boot-image.prof";
-    CreateEmptyFile(boot_profile_file_);
+    framework_profile_ = system_etc_dir + "/boot-image.prof";
+    CreateEmptyFile(framework_profile_);
+    std::string art_etc_dir = Concatenate({android_art_root_path, "/etc"});
+    ASSERT_TRUE(EnsureDirectoryExists(art_etc_dir));
+    art_profile_ = art_etc_dir + "/boot-image.prof";
+    CreateEmptyFile(art_profile_);
 
     framework_dir_ = android_root_path + "/framework";
     framework_jar_ = framework_dir_ + "/framework.jar";
@@ -169,8 +173,8 @@
     services_foo_jar_ = framework_dir_ + "/services-foo.jar";
     services_bar_jar_ = framework_dir_ + "/services-bar.jar";
     std::string services_jar_prof = framework_dir_ + "/services.jar.prof";
-    std::string javalib_dir = android_art_root_path + "/javalib";
-    std::string boot_art = javalib_dir + "/boot.art";
+    art_javalib_dir_ = android_art_root_path + "/javalib";
+    core_oj_jar_ = art_javalib_dir_ + "/core-oj.jar";
 
     // Create placeholder files.
     ASSERT_TRUE(EnsureDirectoryExists(framework_dir_ + "/x86_64"));
@@ -180,16 +184,16 @@
     CreateEmptyFile(services_foo_jar_);
     CreateEmptyFile(services_bar_jar_);
     CreateEmptyFile(services_jar_prof);
-    ASSERT_TRUE(EnsureDirectoryExists(javalib_dir));
-    CreateEmptyFile(boot_art);
+    ASSERT_TRUE(EnsureDirectoryExists(art_javalib_dir_));
+    CreateEmptyFile(core_oj_jar_);
 
     std::string apex_info_filename = Concatenate({temp_dir_path, "/apex-info-list.xml"});
     WriteFakeApexInfoList(apex_info_filename);
     config_.SetApexInfoListFile(apex_info_filename);
 
     config_.SetArtBinDir(Concatenate({temp_dir_path, "/bin"}));
-    config_.SetBootClasspath(framework_jar_);
-    config_.SetDex2oatBootclasspath(framework_jar_);
+    config_.SetBootClasspath(Concatenate({core_oj_jar_, ":", framework_jar_}));
+    config_.SetDex2oatBootclasspath(Concatenate({core_oj_jar_, ":", framework_jar_}));
     config_.SetSystemServerClasspath(Concatenate({location_provider_jar_, ":", services_jar_}));
     config_.SetStandaloneSystemServerJars(Concatenate({services_foo_jar_, ":", services_bar_jar_}));
     config_.SetIsa(InstructionSet::kX86_64);
@@ -230,6 +234,7 @@
   std::unique_ptr<ScopedUnsetEnvironmentVariable> art_apex_data_env_;
   OdrConfig config_;
   std::unique_ptr<OdrMetrics> metrics_;
+  std::string core_oj_jar_;
   std::string framework_jar_;
   std::string location_provider_jar_;
   std::string services_jar_;
@@ -237,9 +242,32 @@
   std::string services_bar_jar_;
   std::string dalvik_cache_dir_;
   std::string framework_dir_;
-  std::string boot_profile_file_;
+  std::string art_javalib_dir_;
+  std::string framework_profile_;
+  std::string art_profile_;
 };
 
+TEST_F(OdRefreshTest, BootClasspathJars) {
+  auto [odrefresh, mock_odr_dexopt] = CreateOdRefresh();
+
+  EXPECT_CALL(*mock_odr_dexopt,
+              DoDexoptBcpExtension(AllOf(
+                  Field(&DexoptBcpExtArgs::dexPaths, ElementsAre(core_oj_jar_, framework_jar_)),
+                  AllOf(Field(&DexoptBcpExtArgs::dexFds,
+                              ElementsAre(FdOf(core_oj_jar_), FdOf(framework_jar_))),
+                        Field(&DexoptBcpExtArgs::profileFds,
+                              ElementsAre(FdOf(art_profile_), FdOf(framework_profile_))),
+                        Field(&DexoptBcpExtArgs::oatLocation,
+                              Eq(dalvik_cache_dir_ + "/x86_64/boot.oat"))))))
+      .WillOnce(Return(0));
+
+  EXPECT_EQ(odrefresh->Compile(*metrics_,
+                               CompilationOptions{
+                                   .compile_boot_classpath_for_isas = {InstructionSet::kX86_64},
+                               }),
+            ExitCode::kCompilationSuccess);
+}
+
 TEST_F(OdRefreshTest, AllSystemServerJars) {
   auto [odrefresh, mock_odr_dexopt] = CreateOdRefresh();
 
@@ -442,7 +470,7 @@
   EXPECT_EQ(
       odrefresh->Compile(*metrics_,
                          CompilationOptions{
-                             .compile_boot_extensions_for_isas = {InstructionSet::kX86_64},
+                             .compile_boot_classpath_for_isas = {InstructionSet::kX86_64},
                              .system_server_jars_to_compile = odrefresh->AllSystemServerJars(),
                          }),
       ExitCode::kCompilationSuccess);
@@ -453,21 +481,20 @@
     auto [odrefresh, mock_odr_dexopt] = CreateOdRefresh();
 
     // Boot image is on /data.
-    OdrArtifacts artifacts =
-        OdrArtifacts::ForBootImageExtension(dalvik_cache_dir_ + "/x86_64/boot-framework.art");
+    OdrArtifacts artifacts = OdrArtifacts::ForBootImage(dalvik_cache_dir_ + "/x86_64/boot.art");
     auto file1 = ScopedCreateEmptyFile(artifacts.ImagePath());
     auto file2 = ScopedCreateEmptyFile(artifacts.VdexPath());
     auto file3 = ScopedCreateEmptyFile(artifacts.OatPath());
 
-    EXPECT_CALL(
-        *mock_odr_dexopt,
-        DoDexoptSystemServer(AllOf(Field(&DexoptSystemServerArgs::isBootImageOnSystem, Eq(false)),
-                                   Field(&DexoptSystemServerArgs::bootClasspathImageFds,
-                                         Contains(FdOf(artifacts.ImagePath()))),
-                                   Field(&DexoptSystemServerArgs::bootClasspathVdexFds,
-                                         Contains(FdOf(artifacts.VdexPath()))),
-                                   Field(&DexoptSystemServerArgs::bootClasspathOatFds,
-                                         Contains(FdOf(artifacts.OatPath()))))))
+    EXPECT_CALL(*mock_odr_dexopt,
+                DoDexoptSystemServer(AllOf(
+                    Field(&DexoptSystemServerArgs::bootImage, Eq(dalvik_cache_dir_ + "/boot.art")),
+                    Field(&DexoptSystemServerArgs::bootClasspathImageFds,
+                          Contains(FdOf(artifacts.ImagePath()))),
+                    Field(&DexoptSystemServerArgs::bootClasspathVdexFds,
+                          Contains(FdOf(artifacts.VdexPath()))),
+                    Field(&DexoptSystemServerArgs::bootClasspathOatFds,
+                          Contains(FdOf(artifacts.OatPath()))))))
         .Times(odrefresh->AllSystemServerJars().size())
         .WillRepeatedly(Return(0));
     EXPECT_EQ(
@@ -483,20 +510,23 @@
 
     // Boot image is on /system.
     OdrArtifacts artifacts =
-        OdrArtifacts::ForBootImageExtension(framework_dir_ + "/x86_64/boot-framework.art");
+        OdrArtifacts::ForBootImage(framework_dir_ + "/x86_64/boot-framework.art");
     auto file1 = ScopedCreateEmptyFile(artifacts.ImagePath());
     auto file2 = ScopedCreateEmptyFile(artifacts.VdexPath());
     auto file3 = ScopedCreateEmptyFile(artifacts.OatPath());
 
-    EXPECT_CALL(
-        *mock_odr_dexopt,
-        DoDexoptSystemServer(AllOf(Field(&DexoptSystemServerArgs::isBootImageOnSystem, Eq(true)),
-                                   Field(&DexoptSystemServerArgs::bootClasspathImageFds,
-                                         Contains(FdOf(artifacts.ImagePath()))),
-                                   Field(&DexoptSystemServerArgs::bootClasspathVdexFds,
-                                         Contains(FdOf(artifacts.VdexPath()))),
-                                   Field(&DexoptSystemServerArgs::bootClasspathOatFds,
-                                         Contains(FdOf(artifacts.OatPath()))))))
+    EXPECT_CALL(*mock_odr_dexopt,
+                DoDexoptSystemServer(
+                    AllOf(Field(&DexoptSystemServerArgs::bootImage,
+                                Eq(android::base::StringPrintf("%s/boot.art:%s/boot-framework.art",
+                                                               art_javalib_dir_.c_str(),
+                                                               framework_dir_.c_str()))),
+                          Field(&DexoptSystemServerArgs::bootClasspathImageFds,
+                                Contains(FdOf(artifacts.ImagePath()))),
+                          Field(&DexoptSystemServerArgs::bootClasspathVdexFds,
+                                Contains(FdOf(artifacts.VdexPath()))),
+                          Field(&DexoptSystemServerArgs::bootClasspathOatFds,
+                                Contains(FdOf(artifacts.OatPath()))))))
         .Times(odrefresh->AllSystemServerJars().size())
         .WillRepeatedly(Return(0));
     EXPECT_EQ(
diff --git a/perfetto_hprof/Android.bp b/perfetto_hprof/Android.bp
index b2c9dad..a81a4fa 100644
--- a/perfetto_hprof/Android.bp
+++ b/perfetto_hprof/Android.bp
@@ -52,7 +52,6 @@
     shared_libs: [
         "libbase",
         "liblog",
-        "libdexfile",
     ],
     static_libs: [
         "libperfetto_client_experimental",
@@ -77,6 +76,7 @@
     shared_libs: [
         "libart",
         "libartbase",
+        "libdexfile",
     ],
     apex_available: [
         "com.android.art",
@@ -93,6 +93,7 @@
     shared_libs: [
         "libartd",
         "libartbased",
+        "libdexfiled",
     ],
     apex_available: [
         "com.android.art.debug",
diff --git a/runtime/arch/arm/quick_entrypoints_arm.S b/runtime/arch/arm/quick_entrypoints_arm.S
index 6145d9a..a3db9f6 100644
--- a/runtime/arch/arm/quick_entrypoints_arm.S
+++ b/runtime/arch/arm/quick_entrypoints_arm.S
@@ -1286,10 +1286,11 @@
     bx     lr
 END art_quick_test_suspend
 
+    .extern artImplicitSuspendFromCode
 ENTRY art_quick_implicit_suspend
     mov    r0, rSELF
     SETUP_SAVE_REFS_ONLY_FRAME r1             @ save callee saves for stack crawl
-    bl     artTestSuspendFromCode             @ (Thread*)
+    bl     artImplicitSuspendFromCode         @ (Thread*)
     RESTORE_SAVE_REFS_ONLY_FRAME
     REFRESH_MARKING_REGISTER
     bx     lr
diff --git a/runtime/arch/arm64/quick_entrypoints_arm64.S b/runtime/arch/arm64/quick_entrypoints_arm64.S
index 3683fdd..7fb6ff0 100644
--- a/runtime/arch/arm64/quick_entrypoints_arm64.S
+++ b/runtime/arch/arm64/quick_entrypoints_arm64.S
@@ -1624,12 +1624,12 @@
     /*
      * Redirection point from implicit suspend check fault handler.
      */
-    .extern artTestSuspendFromCode
+    .extern artImplicitSuspendFromCode
 ENTRY art_quick_implicit_suspend
                                         // Save callee saves for stack crawl.
     SETUP_SAVE_EVERYTHING_FRAME RUNTIME_SAVE_EVERYTHING_FOR_SUSPEND_CHECK_METHOD_OFFSET
     mov    x0, xSELF
-    bl     artTestSuspendFromCode       // (Thread*)
+    bl     artImplicitSuspendFromCode   // (Thread*)
     RESTORE_SAVE_EVERYTHING_FRAME
     REFRESH_MARKING_REGISTER
     REFRESH_SUSPEND_CHECK_REGISTER
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index 3f5b09f..d2ab81e 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -3672,8 +3672,8 @@
     // Check if the native method is annotated with @FastNative or @CriticalNative.
     access_flags |= annotations::GetNativeMethodAnnotationAccessFlags(
         dex_file, dst->GetClassDef(), dex_method_idx);
-  }
-  if (annotations::MethodIsNeverCompile(dex_file, dst->GetClassDef(), dex_method_idx)) {
+  } else if ((access_flags & kAccAbstract) == 0u &&
+             annotations::MethodIsNeverCompile(dex_file, dst->GetClassDef(), dex_method_idx)) {
     access_flags |= kAccCompileDontBother;
   }
   dst->SetAccessFlags(access_flags);
diff --git a/runtime/entrypoints/quick/quick_thread_entrypoints.cc b/runtime/entrypoints/quick/quick_thread_entrypoints.cc
index d8c1ee2..93422cf 100644
--- a/runtime/entrypoints/quick/quick_thread_entrypoints.cc
+++ b/runtime/entrypoints/quick/quick_thread_entrypoints.cc
@@ -22,11 +22,17 @@
 namespace art {
 
 extern "C" void artTestSuspendFromCode(Thread* self) REQUIRES_SHARED(Locks::mutator_lock_) {
-  // Called when suspend count check value is 0 and thread->suspend_count_ != 0
+  // Called when there is a pending checkpoint or suspend request.
   ScopedQuickEntrypointChecks sqec(self);
   self->CheckSuspend();
 }
 
+extern "C" void artImplicitSuspendFromCode(Thread* self) REQUIRES_SHARED(Locks::mutator_lock_) {
+  // Called when there is a pending checkpoint or suspend request.
+  ScopedQuickEntrypointChecks sqec(self);
+  self->CheckSuspend(/*implicit=*/ true);
+}
+
 extern "C" void artCompileOptimized(ArtMethod* method, Thread* self)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   ScopedQuickEntrypointChecks sqec(self);
diff --git a/runtime/jit/jit.h b/runtime/jit/jit.h
index c0c4536..fc7f071 100644
--- a/runtime/jit/jit.h
+++ b/runtime/jit/jit.h
@@ -273,6 +273,10 @@
     return options_->GetThreadPoolPthreadPriority();
   }
 
+  int GetZygoteThreadPoolPthreadPriority() const {
+    return options_->GetZygoteThreadPoolPthreadPriority();
+  }
+
   uint16_t HotMethodThreshold() const {
     return options_->GetOptimizeThreshold();
   }
diff --git a/runtime/oat.h b/runtime/oat.h
index e44187e..36ef459 100644
--- a/runtime/oat.h
+++ b/runtime/oat.h
@@ -32,8 +32,8 @@
 class PACKED(4) OatHeader {
  public:
   static constexpr std::array<uint8_t, 4> kOatMagic { { 'o', 'a', 't', '\n' } };
-  // Last oat version changed reason: ARM64: Disable implicit suspend checks.
-  static constexpr std::array<uint8_t, 4> kOatVersion { { '2', '1', '8', '\0' } };
+  // Last oat version changed reason: ARM64: Enable implicit suspend checks; madvise().
+  static constexpr std::array<uint8_t, 4> kOatVersion { { '2', '1', '9', '\0' } };
 
   static constexpr const char* kDex2OatCmdLineKey = "dex2oat-cmdline";
   static constexpr const char* kDebuggableKey = "debuggable";
diff --git a/runtime/oat_file.cc b/runtime/oat_file.cc
index 54e3712..c80feaf 100644
--- a/runtime/oat_file.cc
+++ b/runtime/oat_file.cc
@@ -190,10 +190,6 @@
   }
 
  private:
-  // Returns true if we want to remove quickened opcodes before loading the VDEX file, false
-  // otherwise.
-  bool ShouldUnquickenVDex() const;
-
   DISALLOW_COPY_AND_ASSIGN(OatFileBase);
 };
 
@@ -280,23 +276,6 @@
   return ret.release();
 }
 
-bool OatFileBase::ShouldUnquickenVDex() const {
-  // We sometimes load oat files without a runtime (eg oatdump) and don't want to do anything in
-  // that case. If we are debuggable there are no -quick opcodes to unquicken. If the runtime is not
-  // debuggable we don't care whether there are -quick opcodes or not so no need to do anything.
-  Runtime* runtime = Runtime::Current();
-  return (runtime != nullptr && runtime->IsJavaDebuggable()) &&
-         // Note: This is called before `OatFileBase::Setup()` where we validate the
-         // oat file contents. Check that we have at least a valid header, including
-         // oat file version, to avoid parsing the key-value store for a different
-         // version (out-of-date oat file) which can lead to crashes. b/179221298.
-         // TODO: While this is a poor workaround and the correct solution would be
-         // to postpone the unquickening check until after `OatFileBase::Setup()`,
-         // we prefer to avoid larger rewrites because quickening is deprecated and
-         // should be removed completely anyway. b/170086509
-         (GetOatHeader().IsValid() && !IsDebuggable());
-}
-
 bool OatFileBase::LoadVdex(const std::string& vdex_filename,
                            bool writable,
                            bool low_4gb,
@@ -307,7 +286,6 @@
                                   vdex_filename,
                                   writable,
                                   low_4gb,
-                                  ShouldUnquickenVDex(),
                                   error_msg);
   if (vdex_.get() == nullptr) {
     *error_msg = StringPrintf("Failed to load vdex file '%s' %s",
@@ -338,7 +316,6 @@
           vdex_filename,
           writable,
           low_4gb,
-          ShouldUnquickenVDex(),
           error_msg);
       if (vdex_.get() == nullptr) {
         *error_msg = "Failed opening vdex file.";
diff --git a/runtime/oat_file_assistant.cc b/runtime/oat_file_assistant.cc
index 414b24e..c303a7b 100644
--- a/runtime/oat_file_assistant.cc
+++ b/runtime/oat_file_assistant.cc
@@ -877,7 +877,6 @@
                                 filename_,
                                 /*writable=*/ false,
                                 /*low_4gb=*/ false,
-                                /*unquicken=*/ false,
                                 &error_msg);
         }
       }
@@ -885,7 +884,6 @@
       vdex = VdexFile::Open(filename_,
                             /*writable=*/ false,
                             /*low_4gb=*/ false,
-                            /*unquicken=*/ false,
                             &error_msg);
     }
     if (vdex == nullptr) {
diff --git a/runtime/oat_file_manager.cc b/runtime/oat_file_manager.cc
index f172ee0..aaa6600 100644
--- a/runtime/oat_file_manager.cc
+++ b/runtime/oat_file_manager.cc
@@ -497,7 +497,6 @@
     vdex_file = VdexFile::Open(vdex_path,
                                /* writable= */ false,
                                /* low_4gb= */ false,
-                               /* unquicken= */ false,
                                &error_msg);
     if (vdex_file == nullptr) {
       LOG(WARNING) << "Failed to open vdex " << vdex_path << ": " << error_msg;
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index 483e2ab..54e9d38 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -709,7 +709,9 @@
     // Ensure that the threads in the JIT pool have been created with the right
     // priority.
     if (kIsDebugBuild && jit->GetThreadPool() != nullptr) {
-      jit->GetThreadPool()->CheckPthreadPriority(jit->GetThreadPoolPthreadPriority());
+      jit->GetThreadPool()->CheckPthreadPriority(
+          IsZygote() ? jit->GetZygoteThreadPoolPthreadPriority()
+                     : jit->GetThreadPoolPthreadPriority());
     }
   }
   // Reset all stats.
@@ -1661,8 +1663,7 @@
   // Change the implicit checks flags based on runtime architecture.
   switch (kRuntimeISA) {
     case InstructionSet::kArm64:
-      // TODO: Investigate implicit suspend check regressions. Bug: 209235730, 213121241.
-      implicit_suspend_checks_ = false;
+      implicit_suspend_checks_ = true;
       FALLTHROUGH_INTENDED;
     case InstructionSet::kArm:
     case InstructionSet::kThumb2:
diff --git a/runtime/thread-inl.h b/runtime/thread-inl.h
index fc8e6cb..3c1e7a0 100644
--- a/runtime/thread-inl.h
+++ b/runtime/thread-inl.h
@@ -46,7 +46,7 @@
   PoisonObjectPointers();
 }
 
-inline void Thread::CheckSuspend() {
+inline void Thread::CheckSuspend(bool implicit) {
   DCHECK_EQ(Thread::Current(), this);
   while (true) {
     StateAndFlags state_and_flags = GetStateAndFlags(std::memory_order_relaxed);
@@ -55,12 +55,18 @@
     } else if (state_and_flags.IsFlagSet(ThreadFlag::kCheckpointRequest)) {
       RunCheckpointFunction();
     } else if (state_and_flags.IsFlagSet(ThreadFlag::kSuspendRequest)) {
-      FullSuspendCheck();
+      FullSuspendCheck(implicit);
+      implicit = false;  // We do not need to `MadviseAwayAlternateSignalStack()` anymore.
     } else {
       DCHECK(state_and_flags.IsFlagSet(ThreadFlag::kEmptyCheckpointRequest));
       RunEmptyCheckpoint();
     }
   }
+  if (implicit) {
+    // For implicit suspend check we want to `madvise()` away
+    // the alternate signal stack to avoid wasting memory.
+    MadviseAwayAlternateSignalStack();
+  }
 }
 
 inline void Thread::CheckEmptyCheckpointFromWeakRefAccess(BaseMutex* cond_var_mutex) {
diff --git a/runtime/thread.cc b/runtime/thread.cc
index 7988f88..25d493f 100644
--- a/runtime/thread.cc
+++ b/runtime/thread.cc
@@ -1861,12 +1861,17 @@
   }
 }
 
-void Thread::FullSuspendCheck() {
+void Thread::FullSuspendCheck(bool implicit) {
   ScopedTrace trace(__FUNCTION__);
   VLOG(threads) << this << " self-suspending";
   // Make thread appear suspended to other threads, release mutator_lock_.
   // Transition to suspended and back to runnable, re-acquire share on mutator_lock_.
   ScopedThreadSuspension(this, ThreadState::kSuspended);  // NOLINT
+  if (implicit) {
+    // For implicit suspend check we want to `madvise()` away
+    // the alternate signal stack to avoid wasting memory.
+    MadviseAwayAlternateSignalStack();
+  }
   VLOG(threads) << this << " self-reviving";
 }
 
diff --git a/runtime/thread.h b/runtime/thread.h
index 3c358d8..1085a56 100644
--- a/runtime/thread.h
+++ b/runtime/thread.h
@@ -230,7 +230,7 @@
   void AllowThreadSuspension() REQUIRES_SHARED(Locks::mutator_lock_);
 
   // Process pending thread suspension request and handle if pending.
-  void CheckSuspend() REQUIRES_SHARED(Locks::mutator_lock_);
+  void CheckSuspend(bool implicit = false) REQUIRES_SHARED(Locks::mutator_lock_);
 
   // Process a pending empty checkpoint if pending.
   void CheckEmptyCheckpointFromWeakRefAccess(BaseMutex* cond_var_mutex);
@@ -367,7 +367,7 @@
 
   // Called when thread detected that the thread_suspend_count_ was non-zero. Gives up share of
   // mutator_lock_ and waits until it is resumed and thread_suspend_count_ is zero.
-  void FullSuspendCheck()
+  void FullSuspendCheck(bool implicit = false)
       REQUIRES(!Locks::thread_suspend_count_lock_)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
@@ -1479,6 +1479,7 @@
 
   void SetUpAlternateSignalStack();
   void TearDownAlternateSignalStack();
+  void MadviseAwayAlternateSignalStack();
 
   ALWAYS_INLINE void TransitionToSuspendedAndRunCheckpoints(ThreadState new_state)
       REQUIRES(!Locks::thread_suspend_count_lock_, !Roles::uninterruptible_)
diff --git a/runtime/thread_android.cc b/runtime/thread_android.cc
index f333400..fadfc09 100644
--- a/runtime/thread_android.cc
+++ b/runtime/thread_android.cc
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
+#include <signal.h>
+#include <sys/mman.h>
+
 #include "thread.h"
 
 namespace art {
@@ -26,4 +29,12 @@
   // Bionic does this for us.
 }
 
+void Thread::MadviseAwayAlternateSignalStack() {
+  stack_t old_ss;
+  int result = sigaltstack(nullptr, &old_ss);
+  CHECK_EQ(result, 0);
+  result = madvise(old_ss.ss_sp, old_ss.ss_size, MADV_FREE);
+  CHECK_EQ(result, 0);
+}
+
 }  // namespace art
diff --git a/runtime/thread_linux.cc b/runtime/thread_linux.cc
index 3ed4276..afce796 100644
--- a/runtime/thread_linux.cc
+++ b/runtime/thread_linux.cc
@@ -70,4 +70,8 @@
   delete[] allocated_signal_stack;
 }
 
+void Thread::MadviseAwayAlternateSignalStack() {
+  // We do not `madvise()` away the alternate signal stack on host.
+}
+
 }  // namespace art
diff --git a/runtime/vdex_file.cc b/runtime/vdex_file.cc
index 1bf6493..77647fb 100644
--- a/runtime/vdex_file.cc
+++ b/runtime/vdex_file.cc
@@ -75,7 +75,6 @@
                                                   const std::string& vdex_filename,
                                                   bool writable,
                                                   bool low_4gb,
-                                                  bool unquicken,
                                                   std::string* error_msg) {
   ScopedTrace trace(("VdexFile::OpenAtAddress " + vdex_filename).c_str());
   if (!OS::FileExists(vdex_filename.c_str())) {
@@ -109,7 +108,6 @@
                        vdex_filename,
                        writable,
                        low_4gb,
-                       unquicken,
                        error_msg);
 }
 
@@ -121,7 +119,6 @@
                                                   const std::string& vdex_filename,
                                                   bool writable,
                                                   bool low_4gb,
-                                                  bool unquicken,
                                                   std::string* error_msg) {
   if (mmap_addr != nullptr && mmap_size < vdex_length) {
     *error_msg = StringPrintf("Insufficient pre-allocated space to mmap vdex: %zu and %zu",
@@ -130,7 +127,6 @@
     return nullptr;
   }
   CHECK(!mmap_reuse || mmap_addr != nullptr);
-  CHECK(!(writable && unquicken)) << "We don't want to be writing unquickened files out to disk!";
   // Start as PROT_WRITE so we can mprotect back to it if we want to.
   MemMap mmap = MemMap::MapFileAtAddress(
       mmap_addr,
diff --git a/runtime/vdex_file.h b/runtime/vdex_file.h
index 35be4fb..3ccbfa5 100644
--- a/runtime/vdex_file.h
+++ b/runtime/vdex_file.h
@@ -194,7 +194,6 @@
                                                  const std::string& vdex_filename,
                                                  bool writable,
                                                  bool low_4gb,
-                                                 bool unquicken,
                                                  std::string* error_msg);
 
   // Returns nullptr if the vdex file cannot be opened or is not valid.
@@ -207,14 +206,12 @@
                                                  const std::string& vdex_filename,
                                                  bool writable,
                                                  bool low_4gb,
-                                                 bool unquicken,
                                                  std::string* error_msg);
 
   // Returns nullptr if the vdex file cannot be opened or is not valid.
   static std::unique_ptr<VdexFile> Open(const std::string& vdex_filename,
                                         bool writable,
                                         bool low_4gb,
-                                        bool unquicken,
                                         std::string* error_msg) {
     return OpenAtAddress(nullptr,
                          0,
@@ -222,7 +219,6 @@
                          vdex_filename,
                          writable,
                          low_4gb,
-                         unquicken,
                          error_msg);
   }
 
@@ -232,7 +228,6 @@
                                         const std::string& vdex_filename,
                                         bool writable,
                                         bool low_4gb,
-                                        bool unquicken,
                                         std::string* error_msg) {
     return OpenAtAddress(nullptr,
                          0,
@@ -242,7 +237,6 @@
                          vdex_filename,
                          writable,
                          low_4gb,
-                         unquicken,
                          error_msg);
   }
 
diff --git a/runtime/vdex_file_test.cc b/runtime/vdex_file_test.cc
index 9d92b42..565487e 100644
--- a/runtime/vdex_file_test.cc
+++ b/runtime/vdex_file_test.cc
@@ -36,12 +36,11 @@
                                                   tmp.GetFilename(),
                                                   /*writable=*/false,
                                                   /*low_4gb=*/false,
-                                                  /*unquicken=*/false,
                                                   &error_msg);
   EXPECT_TRUE(vdex == nullptr);
 
   vdex = VdexFile::Open(
-      tmp.GetFilename(), /*writable=*/false, /*low_4gb=*/false, /*unquicken=*/ false, &error_msg);
+      tmp.GetFilename(), /*writable=*/false, /*low_4gb=*/false, &error_msg);
   EXPECT_TRUE(vdex == nullptr);
 }
 
diff --git a/test/706-checker-scheduler/src/Main.java b/test/706-checker-scheduler/src/Main.java
index dc07a08..1b8377d 100644
--- a/test/706-checker-scheduler/src/Main.java
+++ b/test/706-checker-scheduler/src/Main.java
@@ -610,10 +610,10 @@
   /// CHECK:     beq
 
   /// CHECK-START-ARM64: void Main.testCrossItersDependencies() disassembly (after)
-  /// CHECK:     ldr
   /// CHECK:     sub
   /// CHECK:     add
   /// CHECK:     add
+  /// CHECK:     ldr
   /// CHECK:     b
   private static void testCrossItersDependencies() {
     int[] data = {1, 2, 3, 0};
diff --git a/test/dexpreopt/dexpreopt_test.cc b/test/dexpreopt/dexpreopt_test.cc
index 2dd5662..5315937 100644
--- a/test/dexpreopt/dexpreopt_test.cc
+++ b/test/dexpreopt/dexpreopt_test.cc
@@ -28,12 +28,16 @@
 #include <iterator>
 #include <string>
 #include <unordered_set>
+#include <utility>
 #include <vector>
 
+#include "android-base/properties.h"
 #include "android-base/result.h"
+#include "android-base/stringprintf.h"
 #include "android-base/strings.h"
 #include "arch/instruction_set.h"
 #include "base/common_art_test.h"
+#include "base/file_utils.h"
 #include "base/os.h"
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
@@ -44,6 +48,9 @@
 
 using ::testing::IsSubsetOf;
 
+constexpr const char* kZygote32 = "zygote";
+constexpr const char* kZygote64 = "zygote64";
+
 std::vector<std::string> GetListFromEnv(const std::string& name) {
   const char* env_value = getenv(name.c_str());
   if (env_value == nullptr || strlen(env_value) == 0) {
@@ -52,7 +59,74 @@
   return android::base::Split(env_value, ":");
 }
 
-android::base::Result<std::vector<std::string>> GetSystemServerArtifacts() {
+android::base::Result<std::vector<std::pair<std::string, InstructionSet>>> GetZygoteNamesAndIsas() {
+  std::vector<std::pair<std::string, InstructionSet>> names_and_isas;
+
+  // Possible values are: "zygote32", "zygote64", "zygote32_64", "zygote64_32".
+  std::string zygote_kinds = android::base::GetProperty("ro.zygote", {});
+  if (zygote_kinds.empty()) {
+    return Errorf("Unable to get Zygote kinds");
+  }
+
+  switch (kRuntimeISA) {
+    case InstructionSet::kArm:
+    case InstructionSet::kArm64:
+      if (zygote_kinds.find("32") != std::string::npos) {
+        names_and_isas.push_back(std::make_pair(kZygote32, InstructionSet::kArm));
+      }
+      if (zygote_kinds.find("64") != std::string::npos) {
+        names_and_isas.push_back(std::make_pair(kZygote64, InstructionSet::kArm64));
+      }
+      break;
+    case InstructionSet::kX86:
+    case InstructionSet::kX86_64:
+      if (zygote_kinds.find("32") != std::string::npos) {
+        names_and_isas.push_back(std::make_pair(kZygote32, InstructionSet::kX86));
+      }
+      if (zygote_kinds.find("64") != std::string::npos) {
+        names_and_isas.push_back(std::make_pair(kZygote64, InstructionSet::kX86_64));
+      }
+      break;
+    default:
+      return Errorf("Unknown runtime ISA: {}", GetInstructionSetString(kRuntimeISA));
+  }
+
+  return names_and_isas;
+}
+
+android::base::Result<std::vector<std::string>> GetZygoteExpectedArtifacts(InstructionSet isa) {
+  std::vector<std::string> jars = GetListFromEnv("DEX2OATBOOTCLASSPATH");
+  if (jars.empty()) {
+    return Errorf("Environment variable `DEX2OATBOOTCLASSPATH` is not defined or empty");
+  }
+  std::string art_root = GetArtRoot();
+  std::string android_root = GetAndroidRoot();
+  std::vector<std::string> artifacts;
+  for (size_t i = 0; i < jars.size(); i++) {
+    const std::string& jar = jars[i];
+    std::string basename =
+        i == 0 ? "boot.oat" : "boot-" + ReplaceFileExtension(android::base::Basename(jar), "oat");
+    // TODO(b/211973309): Update this once the primary boot image is moved.
+    std::string dir = android::base::StartsWith(jar, art_root) ? art_root + "/javalib" :
+                                                                 android_root + "/framework";
+    std::string oat_file = android::base::StringPrintf(
+        "%s/%s/%s", dir.c_str(), GetInstructionSetString(isa), basename.c_str());
+
+    if (!OS::FileExists(oat_file.c_str())) {
+      if (errno == EACCES) {
+        return ErrnoErrorf("Failed to stat() {}", oat_file);
+      }
+      // Dexpreopting is probably disabled. No need to report missing artifacts here because
+      // artifact generation is already checked at build time.
+      continue;
+    }
+
+    artifacts.push_back(oat_file);
+  }
+  return artifacts;
+}
+
+android::base::Result<std::vector<std::string>> GetSystemServerExpectedArtifacts() {
   std::vector<std::string> jars = GetListFromEnv("SYSTEMSERVERCLASSPATH");
   if (jars.empty()) {
     return Errorf("Environment variable `SYSTEMSERVERCLASSPATH` is not defined or empty");
@@ -85,31 +159,76 @@
   return artifacts;
 }
 
+android::base::Result<std::vector<std::string>> GetMappedFiles(pid_t pid,
+                                                               const std::string& extension,
+                                                               uint16_t flags) {
+  std::vector<android::procinfo::MapInfo> maps;
+  if (!android::procinfo::ReadProcessMaps(pid, &maps)) {
+    return ErrnoErrorf("Failed to get mapped memory regions of pid {}", pid);
+  }
+  std::vector<std::string> files;
+  for (const android::procinfo::MapInfo& map : maps) {
+    if ((map.flags & flags) && android::base::EndsWith(map.name, extension)) {
+      files.push_back(map.name);
+    }
+  }
+  return files;
+}
+
+android::base::Result<std::vector<std::string>> GetZygoteMappedOatFiles(
+    const std::string& zygote_name) {
+  std::vector<pid_t> pids = art::GetPidByName(zygote_name);
+  if (pids.empty()) {
+    return Errorf("Unable to find Zygote process: {}", zygote_name);
+  }
+  // OAT files in boot images may not be mmaped with PROT_EXEC if they don't contain executable
+  // code. Checking PROT_READ is sufficient because an OAT file will be unmapped if the runtime
+  // rejects it.
+  return GetMappedFiles(pids[0], ".oat", PROT_READ);
+}
+
 android::base::Result<std::vector<std::string>> GetSystemServerArtifactsMappedOdexes() {
   std::vector<pid_t> pids = art::GetPidByName("system_server");
   if (pids.size() != 1) {
     return Errorf("There should be exactly one `system_server` process, found {}", pids.size());
   }
-  pid_t pid = pids[0];
-  std::vector<android::procinfo::MapInfo> maps;
-  if (!android::procinfo::ReadProcessMaps(pid, &maps)) {
-    return ErrnoErrorf("Failed to get mapped memory regions of `system_server`");
-  }
-  std::vector<std::string> odexes;
-  for (const android::procinfo::MapInfo& map : maps) {
-    if ((map.flags & PROT_EXEC) && android::base::EndsWith(map.name, ".odex")) {
-      odexes.push_back(map.name);
+  // Unlike boot images, app images don't get unmapped if the runtime rejects them in some cases
+  // (e.g., CLC mismatch). Therefore, we need to check the PROT_EXEC flag to ensure that they are
+  // valid.
+  // The ODEX files always contain executable code because system server jars are compiled with the
+  // "speed" filter.
+  return GetMappedFiles(pids[0], ".odex", PROT_EXEC);
+}
+
+TEST(DexpreoptTest, ForZygote) {
+  android::base::Result<std::vector<std::pair<std::string, InstructionSet>>> zygote_names_and_isas =
+      GetZygoteNamesAndIsas();
+  ASSERT_RESULT_OK(zygote_names_and_isas);
+
+  for (const auto& [zygote_name, isa] : *zygote_names_and_isas) {
+    android::base::Result<std::vector<std::string>> expected_artifacts =
+        GetZygoteExpectedArtifacts(isa);
+    ASSERT_RESULT_OK(expected_artifacts);
+
+    if (expected_artifacts->empty()) {
+      // Skip the test if dexpreopting is disabled.
+      return;
     }
+
+    android::base::Result<std::vector<std::string>> mapped_oat_files =
+        GetZygoteMappedOatFiles(zygote_name);
+    ASSERT_RESULT_OK(mapped_oat_files);
+
+    EXPECT_THAT(expected_artifacts.value(), IsSubsetOf(mapped_oat_files.value()));
   }
-  return odexes;
 }
 
 TEST(DexpreoptTest, ForSystemServer) {
-  android::base::Result<std::vector<std::string>> system_server_artifacts =
-      GetSystemServerArtifacts();
-  ASSERT_RESULT_OK(system_server_artifacts);
+  android::base::Result<std::vector<std::string>> expected_artifacts =
+      GetSystemServerExpectedArtifacts();
+  ASSERT_RESULT_OK(expected_artifacts);
 
-  if (system_server_artifacts->empty()) {
+  if (expected_artifacts->empty()) {
     // Skip the test if dexpreopting is disabled.
     return;
   }
@@ -118,7 +237,7 @@
       GetSystemServerArtifactsMappedOdexes();
   ASSERT_RESULT_OK(mapped_odexes);
 
-  EXPECT_THAT(system_server_artifacts.value(), IsSubsetOf(mapped_odexes.value()));
+  EXPECT_THAT(expected_artifacts.value(), IsSubsetOf(mapped_odexes.value()));
 }
 
 }  // namespace art
diff --git a/test/odsign/src/com/android/tests/odsign/ArtifactsSignedTest.java b/test/odsign/src/com/android/tests/odsign/ArtifactsSignedTest.java
index 25a46e3..f29baa7 100644
--- a/test/odsign/src/com/android/tests/odsign/ArtifactsSignedTest.java
+++ b/test/odsign/src/com/android/tests/odsign/ArtifactsSignedTest.java
@@ -43,9 +43,9 @@
     // Verifying that they are generated for the correct architectures is currently out of
     // scope for this test.
     private static final String[] REQUIRED_ARTIFACT_NAMES = {
-        "boot-framework.art",
-        "boot-framework.oat",
-        "boot-framework.vdex",
+        "boot.art",
+        "boot.oat",
+        "boot.vdex",
         "system@framework@services.jar@classes.vdex",
         "system@framework@services.jar@classes.odex",
         "system@framework@services.jar@classes.art",
diff --git a/test/odsign/test-src/com/android/tests/odsign/OdsignTestUtils.java b/test/odsign/test-src/com/android/tests/odsign/OdsignTestUtils.java
index 101e5e2..f00ff14 100644
--- a/test/odsign/test-src/com/android/tests/odsign/OdsignTestUtils.java
+++ b/test/odsign/test-src/com/android/tests/odsign/OdsignTestUtils.java
@@ -106,7 +106,7 @@
         final String zygotePid = result.getStdout().trim().split("\\s+")[0];
         assertTrue(!zygotePid.isEmpty());
 
-        final String grepPattern = ART_APEX_DALVIK_CACHE_DIRNAME + ".*boot-framework";
+        final String grepPattern = ART_APEX_DALVIK_CACHE_DIRNAME + ".*boot";
         return Optional.of(getMappedArtifacts(zygotePid, grepPattern));
     }
 
diff --git a/test/odsign/test-src/com/android/tests/odsign/OnDeviceSigningHostTest.java b/test/odsign/test-src/com/android/tests/odsign/OnDeviceSigningHostTest.java
index bcce9a0..9c1949c 100644
--- a/test/odsign/test-src/com/android/tests/odsign/OnDeviceSigningHostTest.java
+++ b/test/odsign/test-src/com/android/tests/odsign/OnDeviceSigningHostTest.java
@@ -118,8 +118,6 @@
     private String getSystemServerIsa(String mappedArtifact) {
         // Artifact path for system server artifacts has the form:
         //    ART_APEX_DALVIK_CACHE_DIRNAME + "/<arch>/system@framework@some.jar@classes.odex"
-        // `mappedArtifacts` may include other artifacts, such as boot-framework.oat that are not
-        // prefixed by the architecture.
         String[] pathComponents = mappedArtifact.split("/");
         return pathComponents[pathComponents.length - 2];
     }
@@ -161,13 +159,13 @@
 
     private void verifyZygoteLoadedArtifacts(String zygoteName, Set<String> mappedArtifacts)
             throws Exception {
-        final String bootExtensionName = "boot-framework";
+        final String bootImageStem = "boot";
 
-        assertTrue("Expect 3 boot-framework artifacts", mappedArtifacts.size() == 3);
+        assertTrue("Expect 3 bootclasspath artifacts", mappedArtifacts.size() == 3);
 
         String allArtifacts = mappedArtifacts.stream().collect(Collectors.joining(","));
         for (String extension : OdsignTestUtils.BCP_ARTIFACT_EXTENSIONS) {
-            final String artifact = bootExtensionName + extension;
+            final String artifact = bootImageStem + extension;
             final boolean found = mappedArtifacts.stream().anyMatch(a -> a.endsWith(artifact));
             assertTrue(zygoteName + " " + artifact + " not found: '" + allArtifacts + "'", found);
         }