Merge "Use OnceKey in art.go"
diff --git a/Android.mk b/Android.mk
index 1a7ed43..a98bedc 100644
--- a/Android.mk
+++ b/Android.mk
@@ -463,19 +463,6 @@
 include $(BUILD_PHONY_PACKAGE)
 endif
 
-# Create dummy hidden API lists which are normally generated by the framework
-# but which we do not have in the master-art manifest.
-# We need to execute this now to ensure Makefile rules depending on these files can
-# be constructed.
-define build-art-hiddenapi
-$(shell if [ ! -d frameworks/base ]; then \
-  mkdir -p ${TARGET_OUT_COMMON_INTERMEDIATES}/PACKAGING; \
-	touch ${TARGET_OUT_COMMON_INTERMEDIATES}/PACKAGING/hiddenapi-flags.csv; \
-  fi;)
-endef
-
-$(eval $(call build-art-hiddenapi))
-
 ########################################################################
 # "m build-art" for quick minimal build
 .PHONY: build-art
diff --git a/build/Android.common_test.mk b/build/Android.common_test.mk
index 12eae89..55b8ae2 100644
--- a/build/Android.common_test.mk
+++ b/build/Android.common_test.mk
@@ -127,7 +127,6 @@
     LOCAL_MODULE_TAGS := tests
     LOCAL_JAVA_LIBRARIES := $(TARGET_TEST_CORE_JARS)
     LOCAL_MODULE_PATH := $(3)
-    LOCAL_DEX_PREOPT_IMAGE_LOCATION := $(TARGET_CORE_IMG_OUT)
     ifneq ($(wildcard $(LOCAL_PATH)/$(2)/main.list),)
       LOCAL_MIN_SDK_VERSION := 19
       LOCAL_DX_FLAGS := --multi-dex --main-dex-list=$(LOCAL_PATH)/$(2)/main.list --minimal-main-dex
@@ -143,7 +142,6 @@
     LOCAL_DEX_PREOPT := false
     LOCAL_ADDITIONAL_DEPENDENCIES := art/build/Android.common_test.mk $(4)
     LOCAL_JAVA_LIBRARIES := $(HOST_TEST_CORE_JARS)
-    LOCAL_DEX_PREOPT_IMAGE := $(HOST_CORE_IMG_LOCATION)
     ifneq ($(wildcard $(LOCAL_PATH)/$(2)/main.list),)
       LOCAL_MIN_SDK_VERSION := 19
       LOCAL_DX_FLAGS := --multi-dex --main-dex-list=$(LOCAL_PATH)/$(2)/main.list --minimal-main-dex
diff --git a/build/apex/Android.bp b/build/apex/Android.bp
index e3e5b6e..735755f 100644
--- a/build/apex/Android.bp
+++ b/build/apex/Android.bp
@@ -13,11 +13,15 @@
 ]
 // - Base requirements (libraries).
 art_runtime_base_native_shared_libs = [
+    "libadbconnection",
     "libart",
     "libart-compiler",
+    "libdexfile_external",
+    "libnativebridge",
+    "libnativehelper",
+    "libnativeloader",
     "libopenjdkjvm",
     "libopenjdkjvmti",
-    "libadbconnection",
 ]
 bionic_native_shared_libs = [
     "libc",
@@ -41,11 +45,11 @@
 
 // - Debug variants (libraries).
 art_runtime_debug_native_shared_libs = [
+    "libadbconnectiond",
     "libartd",
     "libartd-compiler",
     "libopenjdkjvmd",
     "libopenjdkjvmtid",
-    "libadbconnectiond",
 ]
 libcore_debug_native_shared_libs = [
     "libopenjdkd",
@@ -85,7 +89,21 @@
 art_tools_device_binaries = art_tools_common_binaries + art_tools_device_only_binaries
 art_tools_host_binaries = art_tools_common_binaries + art_tools_host_only_binaries
 
-// Libcore native libraries.
+// Libraries needed to use com.android.runtime.host for zipapex run-tests
+art_runtime_host_run_test_libs = [
+    "libartd-disassembler"
+]
+
+// Core Java libraries.
+libcore_java_libs = [
+    "core-oj",
+    "core-libart",
+    "okhttp",
+    "bouncycastle",
+    "apache-xml",
+]
+
+// Native libraries that support the core Java libraries.
 libcore_native_shared_libs = [
     "libjavacore",
     "libopenjdk",
@@ -94,13 +112,12 @@
     "libziparchive"
 ]
 
-// Java libraries
-libcore_target_java_libs = [
-    "core-oj",
-    "core-libart",
-    "okhttp",
-    "bouncycastle",
-    "apache-xml",
+// Temporary library includes for b/123591866 as all libraries are moved into the main art-apex.
+art_runtime_libraries_zipapex = [
+    "libnativebridge",
+    "libnativeloader",
+    "libnativehelper",
+    "libcutils",
 ]
 
 apex_key {
@@ -126,7 +143,7 @@
     name: "com.android.runtime.release",
     compile_multilib: "both",
     manifest: "manifest.json",
-    java_libs: libcore_target_java_libs,
+    java_libs: libcore_java_libs,
     native_shared_libs: art_runtime_base_native_shared_libs
         + bionic_native_shared_libs
         + libcore_native_shared_libs,
@@ -156,7 +173,7 @@
     name: "com.android.runtime.debug",
     compile_multilib: "both",
     manifest: "manifest.json",
-    java_libs: libcore_target_java_libs,
+    java_libs: libcore_java_libs,
     native_shared_libs: art_runtime_base_native_shared_libs
         + art_runtime_debug_native_shared_libs
         + bionic_native_shared_libs
@@ -188,19 +205,21 @@
 // because binaries have different multilib classes and 'multilib: {}' isn't
 // supported by target: { ... }.
 // See b/120617876 for more information.
-art_apex {
+art_apex_test {
     name: "com.android.runtime.host",
     compile_multilib: "both",
     payload_type: "zip",
     host_supported: true,
     device_supported: false,
     manifest: "manifest.json",
-    java_libs: libcore_target_java_libs,
+    java_libs: libcore_java_libs,
     ignore_system_library_special_case: true,
     native_shared_libs: art_runtime_base_native_shared_libs
         + art_runtime_debug_native_shared_libs
         + libcore_native_shared_libs
-        + libcore_debug_native_shared_libs,
+        + libcore_debug_native_shared_libs
+        + art_runtime_libraries_zipapex
+        + art_runtime_host_run_test_libs,
     multilib: {
         both: {
             // TODO: Add logic to create a `dalvikvm` symlink to `dalvikvm32` or `dalvikvm64`
diff --git a/build/apex/art_apex_test.py b/build/apex/art_apex_test.py
index 1abc466..84013f6 100755
--- a/build/apex/art_apex_test.py
+++ b/build/apex/art_apex_test.py
@@ -353,6 +353,10 @@
     self._checker.check_library('libartpalette.so')
     self._checker.check_no_library('libartpalette-system.so')
     self._checker.check_library('libdexfile.so')
+    self._checker.check_library('libdexfile_external.so')
+    self._checker.check_library('libnativebridge.so')
+    self._checker.check_library('libnativehelper.so')
+    self._checker.check_library('libnativeloader.so')
     self._checker.check_library('libopenjdkjvm.so')
     self._checker.check_library('libopenjdkjvmti.so')
     self._checker.check_library('libprofile.so')
diff --git a/build/apex/ld.config.txt b/build/apex/ld.config.txt
index 9bf2ae5..3cfda65 100644
--- a/build/apex/ld.config.txt
+++ b/build/apex/ld.config.txt
@@ -30,6 +30,7 @@
 namespace.platform.asan.search.paths = /data/asan/system/${LIB}
 namespace.platform.links = default
 namespace.platform.link.default.shared_libs  = libart.so:libartd.so
+namespace.platform.link.default.shared_libs += libdexfile_external.so
 namespace.platform.link.default.shared_libs += libnativebridge.so
 namespace.platform.link.default.shared_libs += libnativehelper.so
 namespace.platform.link.default.shared_libs += libnativeloader.so
diff --git a/build/art.go b/build/art.go
index 2ec1917..4db8da2 100644
--- a/build/art.go
+++ b/build/art.go
@@ -300,11 +300,11 @@
 	// changes this to 'prefer32' on all host binaries. Since HOST_PREFER_32_BIT is
 	// only used for testing we can just disable the module.
 	// See b/120617876 for more information.
-	android.RegisterModuleType("art_apex", artApexBundleFactory)
+	android.RegisterModuleType("art_apex_test", artTestApexBundleFactory)
 }
 
-func artApexBundleFactory() android.Module {
-	module := apex.ApexBundleFactory()
+func artTestApexBundleFactory() android.Module {
+	module := apex.ApexBundleFactory( /*testApex*/ true)
 	android.AddLoadHook(module, func(ctx android.LoadHookContext) {
 		if envTrue(ctx, "HOST_PREFER_32_BIT") {
 			type props struct {
diff --git a/cmdline/cmdline.h b/cmdline/cmdline.h
index 81a2179..90be30b 100644
--- a/cmdline/cmdline.h
+++ b/cmdline/cmdline.h
@@ -23,13 +23,14 @@
 #include <fstream>
 #include <iostream>
 #include <string>
+#include <string_view>
 
 #include "android-base/stringprintf.h"
 
 #include "base/file_utils.h"
 #include "base/logging.h"
 #include "base/mutex.h"
-#include "base/stringpiece.h"
+#include "base/string_view_cpp20.h"
 #include "noop_compiler_callbacks.h"
 #include "runtime.h"
 
@@ -151,14 +152,15 @@
 
     std::string error_msg;
     for (int i = 0; i < argc; i++) {
-      const StringPiece option(argv[i]);
-      if (option.starts_with("--boot-image=")) {
-        boot_image_location_ = option.substr(strlen("--boot-image=")).data();
-      } else if (option.starts_with("--instruction-set=")) {
-        StringPiece instruction_set_str = option.substr(strlen("--instruction-set=")).data();
-        instruction_set_ = GetInstructionSetFromString(instruction_set_str.data());
+      const char* const raw_option = argv[i];
+      const std::string_view option(raw_option);
+      if (StartsWith(option, "--boot-image=")) {
+        boot_image_location_ = raw_option + strlen("--boot-image=");
+      } else if (StartsWith(option, "--instruction-set=")) {
+        const char* const instruction_set_str = raw_option + strlen("--instruction-set=");
+        instruction_set_ = GetInstructionSetFromString(instruction_set_str);
         if (instruction_set_ == InstructionSet::kNone) {
-          fprintf(stderr, "Unsupported instruction set %s\n", instruction_set_str.data());
+          fprintf(stderr, "Unsupported instruction set %s\n", instruction_set_str);
           PrintUsage();
           return false;
         }
@@ -170,8 +172,8 @@
         }
         ++i;
         runtime_args_.push_back(argv[i]);
-      } else if (option.starts_with("--output=")) {
-        output_name_ = option.substr(strlen("--output=")).ToString();
+      } else if (StartsWith(option, "--output=")) {
+        output_name_ = std::string(option.substr(strlen("--output=")));
         const char* filename = output_name_.c_str();
         out_.reset(new std::ofstream(filename));
         if (!out_->good()) {
@@ -181,7 +183,7 @@
         }
         os_ = out_.get();
       } else {
-        ParseStatus parse_status = ParseCustom(option, &error_msg);
+        ParseStatus parse_status = ParseCustom(raw_option, option.length(), &error_msg);
 
         if (parse_status == kParseUnknownArgument) {
           fprintf(stderr, "Unknown argument %s\n", option.data());
@@ -315,7 +317,8 @@
   }
 
  protected:
-  virtual ParseStatus ParseCustom(const StringPiece& option ATTRIBUTE_UNUSED,
+  virtual ParseStatus ParseCustom(const char* raw_option ATTRIBUTE_UNUSED,
+                                  size_t raw_option_length ATTRIBUTE_UNUSED,
                                   std::string* error_msg ATTRIBUTE_UNUSED) {
     return kParseUnknownArgument;
   }
diff --git a/compiler/driver/compiler_driver.cc b/compiler/driver/compiler_driver.cc
index 56333b6..f39d8fc 100644
--- a/compiler/driver/compiler_driver.cc
+++ b/compiler/driver/compiler_driver.cc
@@ -1768,42 +1768,6 @@
   }
 }
 
-// Returns true if any of the given dex files define a class from the boot classpath.
-static bool DexFilesRedefineBootClasses(
-    const std::vector<const DexFile*>& dex_files,
-    TimingLogger* timings) {
-  TimingLogger::ScopedTiming t("Fast Verify: Boot Class Redefinition Check", timings);
-
-  ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
-  Thread* self = Thread::Current();
-  ScopedObjectAccess soa(self);
-
-  bool foundRedefinition = false;
-  for (const DexFile* dex_file : dex_files) {
-    for (ClassAccessor accessor : dex_file->GetClasses()) {
-      const char* descriptor = accessor.GetDescriptor();
-      StackHandleScope<1> hs_class(self);
-      Handle<mirror::Class> klass =
-          hs_class.NewHandle(class_linker->FindSystemClass(self, descriptor));
-      if (klass == nullptr) {
-        self->ClearException();
-      } else {
-        LOG(WARNING) << "Redefinition of boot class " << descriptor
-            << " App dex file: " <<  accessor.GetDexFile().GetLocation()
-            << " Boot dex file: " << klass->GetDexFile().GetLocation();
-        foundRedefinition = true;
-        if (!VLOG_IS_ON(verifier)) {
-          // If we are not in verbose mode, return early.
-          // Otherwise continue and log all the collisions for easier debugging.
-          return true;
-        }
-      }
-    }
-  }
-
-  return foundRedefinition;
-}
-
 bool CompilerDriver::FastVerify(jobject jclass_loader,
                                 const std::vector<const DexFile*>& dex_files,
                                 TimingLogger* timings,
@@ -1816,21 +1780,14 @@
   }
   TimingLogger::ScopedTiming t("Fast Verify", timings);
 
-  // We cannot do fast verification if the app redefines classes from the boot classpath.
-  // Vdex does not record resolution chains for boot classes and we might wrongfully
-  // resolve a class to the app when it should have been resolved to the boot classpath
-  // (e.g. if we verified against the SDK and the app redefines a boot class which is not
-  // in the SDK.)
-  if (DexFilesRedefineBootClasses(dex_files, timings)) {
-    LOG(WARNING) << "Found redefinition of boot classes. Not doing fast verification.";
-    return false;
-  }
-
   ScopedObjectAccess soa(Thread::Current());
   StackHandleScope<2> hs(soa.Self());
   Handle<mirror::ClassLoader> class_loader(
       hs.NewHandle(soa.Decode<mirror::ClassLoader>(jclass_loader)));
-  if (!verifier_deps->ValidateDependencies(class_loader, soa.Self())) {
+  std::string error_msg;
+
+  if (!verifier_deps->ValidateDependencies(class_loader, soa.Self(), &error_msg)) {
+    LOG(WARNING) << "Fast verification failed: " << error_msg;
     return false;
   }
 
@@ -1928,10 +1885,10 @@
     // Merge all VerifierDeps into the main one.
     verifier::VerifierDeps* verifier_deps = Thread::Current()->GetVerifierDeps();
     for (ThreadPoolWorker* worker : parallel_thread_pool_->GetWorkers()) {
-      verifier::VerifierDeps* thread_deps = worker->GetThread()->GetVerifierDeps();
-      worker->GetThread()->SetVerifierDeps(nullptr);
-      verifier_deps->MergeWith(*thread_deps, GetCompilerOptions().GetDexFilesForOatFile());
-      delete thread_deps;
+      std::unique_ptr<verifier::VerifierDeps> thread_deps(worker->GetThread()->GetVerifierDeps());
+      worker->GetThread()->SetVerifierDeps(nullptr);  // We just took ownership.
+      verifier_deps->MergeWith(std::move(thread_deps),
+                               GetCompilerOptions().GetDexFilesForOatFile());
     }
     Thread::Current()->SetVerifierDeps(nullptr);
   }
diff --git a/compiler/exception_test.cc b/compiler/exception_test.cc
index d5ceafe..f978cc6 100644
--- a/compiler/exception_test.cc
+++ b/compiler/exception_test.cc
@@ -80,7 +80,7 @@
     stack_maps.BeginMethod(4 * sizeof(void*), 0u, 0u, 0u);
     stack_maps.BeginStackMapEntry(kDexPc, native_pc_offset);
     stack_maps.EndStackMapEntry();
-    stack_maps.EndMethod();
+    stack_maps.EndMethod(code_size);
     ScopedArenaVector<uint8_t> stack_map = stack_maps.Encode();
 
     const size_t stack_maps_size = stack_map.size();
diff --git a/compiler/jit/jit_compiler.cc b/compiler/jit/jit_compiler.cc
index 4d7ae9b..2c20b32 100644
--- a/compiler/jit/jit_compiler.cc
+++ b/compiler/jit/jit_compiler.cc
@@ -22,7 +22,7 @@
 #include "arch/instruction_set_features.h"
 #include "art_method-inl.h"
 #include "base/logging.h"  // For VLOG
-#include "base/stringpiece.h"
+#include "base/string_view_cpp20.h"
 #include "base/systrace.h"
 #include "base/time_utils.h"
 #include "base/timing_logger.h"
@@ -71,19 +71,19 @@
     DCHECK_EQ(instruction_set, kRuntimeISA);
   }
   std::unique_ptr<const InstructionSetFeatures> instruction_set_features;
-  for (const StringPiece option : runtime->GetCompilerOptions()) {
+  for (const std::string& option : runtime->GetCompilerOptions()) {
     VLOG(compiler) << "JIT compiler option " << option;
     std::string error_msg;
-    if (option.starts_with("--instruction-set-variant=")) {
-      StringPiece str = option.substr(strlen("--instruction-set-variant=")).data();
+    if (StartsWith(option, "--instruction-set-variant=")) {
+      const char* str = option.c_str() + strlen("--instruction-set-variant=");
       VLOG(compiler) << "JIT instruction set variant " << str;
       instruction_set_features = InstructionSetFeatures::FromVariant(
-          instruction_set, str.as_string(), &error_msg);
+          instruction_set, str, &error_msg);
       if (instruction_set_features == nullptr) {
         LOG(WARNING) << "Error parsing " << option << " message=" << error_msg;
       }
-    } else if (option.starts_with("--instruction-set-features=")) {
-      StringPiece str = option.substr(strlen("--instruction-set-features=")).data();
+    } else if (StartsWith(option, "--instruction-set-features=")) {
+      const char* str = option.c_str() + strlen("--instruction-set-features=");
       VLOG(compiler) << "JIT instruction set features " << str;
       if (instruction_set_features == nullptr) {
         instruction_set_features = InstructionSetFeatures::FromVariant(
@@ -93,7 +93,7 @@
         }
       }
       instruction_set_features =
-          instruction_set_features->AddFeaturesFromString(str.as_string(), &error_msg);
+          instruction_set_features->AddFeaturesFromString(str, &error_msg);
       if (instruction_set_features == nullptr) {
         LOG(WARNING) << "Error parsing " << option << " message=" << error_msg;
       }
diff --git a/compiler/optimizing/code_generator.cc b/compiler/optimizing/code_generator.cc
index 9e2f5cd..122f27b 100644
--- a/compiler/optimizing/code_generator.cc
+++ b/compiler/optimizing/code_generator.cc
@@ -440,7 +440,7 @@
   // Finalize instructions in assember;
   Finalize(allocator);
 
-  GetStackMapStream()->EndMethod();
+  GetStackMapStream()->EndMethod(GetAssembler()->CodeSize());
 }
 
 void CodeGenerator::Finalize(CodeAllocator* allocator) {
diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h
index c70674b..4670b3f 100644
--- a/compiler/optimizing/nodes.h
+++ b/compiler/optimizing/nodes.h
@@ -2355,10 +2355,6 @@
   // 2) Their inputs are identical.
   bool Equals(const HInstruction* other) const;
 
-  // TODO: Remove this indirection when the [[pure]] attribute proposal (n3744)
-  // is adopted and implemented by our C++ compiler(s). Fow now, we need to hide
-  // the virtual function because the __attribute__((__pure__)) doesn't really
-  // apply the strong requirement for virtual functions, preventing optimizations.
   InstructionKind GetKind() const { return GetPackedField<InstructionKindField>(); }
 
   virtual size_t ComputeHashCode() const {
diff --git a/compiler/optimizing/optimizing_compiler.cc b/compiler/optimizing/optimizing_compiler.cc
index e8f8d32..4f43b71 100644
--- a/compiler/optimizing/optimizing_compiler.cc
+++ b/compiler/optimizing/optimizing_compiler.cc
@@ -1171,7 +1171,8 @@
 }
 
 static ScopedArenaVector<uint8_t> CreateJniStackMap(ScopedArenaAllocator* allocator,
-                                                    const JniCompiledMethod& jni_compiled_method) {
+                                                    const JniCompiledMethod& jni_compiled_method,
+                                                    size_t code_size) {
   // StackMapStream is quite large, so allocate it using the ScopedArenaAllocator
   // to stay clear of the frame size limit.
   std::unique_ptr<StackMapStream> stack_map_stream(
@@ -1181,7 +1182,7 @@
       jni_compiled_method.GetCoreSpillMask(),
       jni_compiled_method.GetFpSpillMask(),
       /* num_dex_registers= */ 0);
-  stack_map_stream->EndMethod();
+  stack_map_stream->EndMethod(code_size);
   return stack_map_stream->Encode();
 }
 
@@ -1239,8 +1240,8 @@
   MaybeRecordStat(compilation_stats_.get(), MethodCompilationStat::kCompiledNativeStub);
 
   ScopedArenaAllocator stack_map_allocator(&arena_stack);  // Will hold the stack map.
-  ScopedArenaVector<uint8_t> stack_map = CreateJniStackMap(&stack_map_allocator,
-                                                           jni_compiled_method);
+  ScopedArenaVector<uint8_t> stack_map = CreateJniStackMap(
+      &stack_map_allocator, jni_compiled_method, jni_compiled_method.GetCode().size());
   return CompiledMethod::SwapAllocCompiledMethod(
       GetCompilerDriver()->GetCompiledMethodStorage(),
       jni_compiled_method.GetInstructionSet(),
@@ -1290,8 +1291,8 @@
     ArenaStack arena_stack(runtime->GetJitArenaPool());
     // StackMapStream is large and it does not fit into this frame, so we need helper method.
     ScopedArenaAllocator stack_map_allocator(&arena_stack);  // Will hold the stack map.
-    ScopedArenaVector<uint8_t> stack_map = CreateJniStackMap(&stack_map_allocator,
-                                                             jni_compiled_method);
+    ScopedArenaVector<uint8_t> stack_map = CreateJniStackMap(
+        &stack_map_allocator, jni_compiled_method, jni_compiled_method.GetCode().size());
     uint8_t* stack_map_data = nullptr;
     uint8_t* roots_data = nullptr;
     uint32_t data_size = code_cache->ReserveData(self,
diff --git a/compiler/optimizing/stack_map_stream.cc b/compiler/optimizing/stack_map_stream.cc
index 60ca61c..e87f3c8 100644
--- a/compiler/optimizing/stack_map_stream.cc
+++ b/compiler/optimizing/stack_map_stream.cc
@@ -54,9 +54,10 @@
   num_dex_registers_ = num_dex_registers;
 }
 
-void StackMapStream::EndMethod() {
+void StackMapStream::EndMethod(size_t code_size) {
   DCHECK(in_method_) << "Mismatched Begin/End calls";
   in_method_ = false;
+  packed_code_size_ = StackMap::PackNativePc(code_size, instruction_set_);
 
   // Read the stack masks now. The compiler might have updated them.
   for (size_t i = 0; i < lazy_stack_masks_.size(); i++) {
@@ -66,6 +67,10 @@
           stack_masks_.Dedup(stack_mask->GetRawStorage(), stack_mask->GetNumberOfBits());
     }
   }
+
+  for (size_t i = 0; i < stack_maps_.size(); i++) {
+    DCHECK_LE(stack_maps_[i][StackMap::kPackedNativePc], packed_code_size_);
+  }
 }
 
 void StackMapStream::BeginStackMapEntry(uint32_t dex_pc,
@@ -296,6 +301,7 @@
 
   ScopedArenaVector<uint8_t> buffer(allocator_->Adapter(kArenaAllocStackMapStream));
   BitMemoryWriter<ScopedArenaVector<uint8_t>> out(&buffer);
+  out.WriteVarint(packed_code_size_);
   out.WriteVarint(packed_frame_size_);
   out.WriteVarint(core_spill_mask_);
   out.WriteVarint(fp_spill_mask_);
diff --git a/compiler/optimizing/stack_map_stream.h b/compiler/optimizing/stack_map_stream.h
index 01c6bf9..164e902 100644
--- a/compiler/optimizing/stack_map_stream.h
+++ b/compiler/optimizing/stack_map_stream.h
@@ -62,7 +62,7 @@
                    size_t core_spill_mask,
                    size_t fp_spill_mask,
                    uint32_t num_dex_registers);
-  void EndMethod();
+  void EndMethod(size_t code_size);
 
   void BeginStackMapEntry(uint32_t dex_pc,
                           uint32_t native_pc_offset,
@@ -99,6 +99,7 @@
 
   ScopedArenaAllocator* allocator_;
   const InstructionSet instruction_set_;
+  uint32_t packed_code_size_ = 0;
   uint32_t packed_frame_size_ = 0;
   uint32_t core_spill_mask_ = 0;
   uint32_t fp_spill_mask_ = 0;
diff --git a/compiler/optimizing/stack_map_test.cc b/compiler/optimizing/stack_map_test.cc
index d28f09f..cbd844f 100644
--- a/compiler/optimizing/stack_map_test.cc
+++ b/compiler/optimizing/stack_map_test.cc
@@ -61,7 +61,7 @@
   stream.AddDexRegisterEntry(Kind::kConstant, -2);       // Short location.
   stream.EndStackMapEntry();
 
-  stream.EndMethod();
+  stream.EndMethod(64 * kPcAlign);
   ScopedArenaVector<uint8_t> memory = stream.Encode();
 
   CodeInfo code_info(memory.data());
@@ -147,7 +147,7 @@
   stream.AddDexRegisterEntry(Kind::kInFpuRegisterHigh, 1);  // Short location.
   stream.EndStackMapEntry();
 
-  stream.EndMethod();
+  stream.EndMethod(256 * kPcAlign);
   ScopedArenaVector<uint8_t> memory = stream.Encode();
 
   CodeInfo code_info(memory.data());
@@ -317,7 +317,7 @@
   stream.EndInlineInfoEntry();
   stream.EndStackMapEntry();
 
-  stream.EndMethod();
+  stream.EndMethod(64 * kPcAlign);
   ScopedArenaVector<uint8_t> memory = stream.Encode();
 
   CodeInfo code_info(memory.data());
@@ -372,7 +372,7 @@
   stream.AddDexRegisterEntry(Kind::kConstant, -2);       // Large location.
   stream.EndStackMapEntry();
 
-  stream.EndMethod();
+  stream.EndMethod(64 * kPcAlign);
   ScopedArenaVector<uint8_t> memory = stream.Encode();
 
   CodeInfo code_info(memory.data());
@@ -431,7 +431,7 @@
   stream.AddDexRegisterEntry(Kind::kConstant, -2);   // Large location.
   stream.EndStackMapEntry();
 
-  stream.EndMethod();
+  stream.EndMethod(66 * kPcAlign);
   ScopedArenaVector<uint8_t> memory = stream.Encode();
 
   CodeInfo ci(memory.data());
@@ -479,7 +479,7 @@
   stream.AddDexRegisterEntry(Kind::kNone, 0);
   stream.EndStackMapEntry();
 
-  stream.EndMethod();
+  stream.EndMethod(68 * kPcAlign);
   ScopedArenaVector<uint8_t> memory = stream.Encode();
 
   CodeInfo code_info(memory.data());
@@ -578,7 +578,7 @@
 
   stream.EndStackMapEntry();
 
-  stream.EndMethod();
+  stream.EndMethod(78 * kPcAlign);
   ScopedArenaVector<uint8_t> memory = stream.Encode();
 
   CodeInfo ci(memory.data());
@@ -722,7 +722,7 @@
   stream.BeginStackMapEntry(0, 8 * kPcAlign, 0x3, &sp_mask);
   stream.EndStackMapEntry();
 
-  stream.EndMethod();
+  stream.EndMethod(8 * kPcAlign);
   ScopedArenaVector<uint8_t> memory = stream.Encode();
 
   CodeInfo code_info(memory.data());
@@ -746,7 +746,7 @@
   stream.AddDexRegisterEntry(Kind::kConstant, -2);
   stream.EndStackMapEntry();
 
-  stream.EndMethod();
+  stream.EndMethod(64 * kPcAlign);
   ScopedArenaVector<uint8_t> memory = stream.Encode();
 
   std::vector<uint8_t> out;
diff --git a/compiler/verifier_deps_test.cc b/compiler/verifier_deps_test.cc
index 092e931..5c6b815 100644
--- a/compiler/verifier_deps_test.cc
+++ b/compiler/verifier_deps_test.cc
@@ -449,6 +449,28 @@
            has_unverified_classes;
   }
 
+  // Load the dex file again with a new class loader, decode the VerifierDeps
+  // in `buffer`, allow the caller to modify the deps and then run validation.
+  template<typename Fn>
+  bool RunValidation(Fn fn, const std::vector<uint8_t>& buffer, std::string* error_msg) {
+    ScopedObjectAccess soa(Thread::Current());
+
+    jobject second_loader = LoadDex("VerifierDeps");
+    const auto& second_dex_files = GetDexFiles(second_loader);
+
+    VerifierDeps decoded_deps(second_dex_files, ArrayRef<const uint8_t>(buffer));
+    VerifierDeps::DexFileDeps* decoded_dex_deps =
+        decoded_deps.GetDexFileDeps(*second_dex_files.front());
+
+    // Let the test modify the dependencies.
+    fn(*decoded_dex_deps);
+
+    StackHandleScope<1> hs(soa.Self());
+    Handle<mirror::ClassLoader> new_class_loader =
+        hs.NewHandle<mirror::ClassLoader>(soa.Decode<mirror::ClassLoader>(second_loader));
+    return decoded_deps.ValidateDependencies(new_class_loader, soa.Self(), error_msg);
+  }
+
   std::unique_ptr<verifier::VerifierDeps> verifier_deps_;
   std::vector<const DexFile*> dex_files_;
   const DexFile* primary_dex_file_;
@@ -1177,8 +1199,9 @@
 }
 
 TEST_F(VerifierDepsTest, VerifyDeps) {
-  VerifyDexFile();
+  std::string error_msg;
 
+  VerifyDexFile();
   ASSERT_EQ(1u, NumberOfCompiledDexFiles());
   ASSERT_TRUE(HasEachKindOfRecord());
 
@@ -1186,249 +1209,166 @@
   // the existing `class_loader_` may contain erroneous classes,
   // that ClassLinker::FindClass won't return.
 
-  ScopedObjectAccess soa(Thread::Current());
-  StackHandleScope<1> hs(soa.Self());
-  MutableHandle<mirror::ClassLoader> new_class_loader(hs.NewHandle<mirror::ClassLoader>(nullptr));
-  {
-    new_class_loader.Assign(soa.Decode<mirror::ClassLoader>(LoadDex("VerifierDeps")));
-    ASSERT_TRUE(verifier_deps_->ValidateDependencies(new_class_loader, soa.Self()));
-  }
-
   std::vector<uint8_t> buffer;
   verifier_deps_->Encode(dex_files_, &buffer);
   ASSERT_FALSE(buffer.empty());
 
-  {
-    VerifierDeps decoded_deps(dex_files_, ArrayRef<const uint8_t>(buffer));
-    new_class_loader.Assign(soa.Decode<mirror::ClassLoader>(LoadDex("VerifierDeps")));
-    ASSERT_TRUE(decoded_deps.ValidateDependencies(new_class_loader, soa.Self()));
-  }
+  // Check that dependencies are satisfied after decoding `buffer`.
+  ASSERT_TRUE(RunValidation([](VerifierDeps::DexFileDeps&) {}, buffer, &error_msg))
+      << error_msg;
 
-  // Fiddle with the dependencies to make sure we catch any change and fail to verify.
+  // Mess with the dependencies to make sure we catch any change and fail to verify.
+  ASSERT_FALSE(RunValidation([](VerifierDeps::DexFileDeps& deps) {
+        deps.assignable_types_.insert(*deps.unassignable_types_.begin());
+      }, buffer, &error_msg));
 
-  {
-    // Mess up with the assignable_types.
-    VerifierDeps decoded_deps(dex_files_, ArrayRef<const uint8_t>(buffer));
-    VerifierDeps::DexFileDeps* deps = decoded_deps.GetDexFileDeps(*primary_dex_file_);
-    deps->assignable_types_.insert(*deps->unassignable_types_.begin());
-    new_class_loader.Assign(soa.Decode<mirror::ClassLoader>(LoadDex("VerifierDeps")));
-    ASSERT_FALSE(decoded_deps.ValidateDependencies(new_class_loader, soa.Self()));
-  }
+  // Mess with the unassignable_types.
+  ASSERT_FALSE(RunValidation([](VerifierDeps::DexFileDeps& deps) {
+        deps.unassignable_types_.insert(*deps.assignable_types_.begin());
+      }, buffer, &error_msg));
 
-  {
-    // Mess up with the unassignable_types.
-    VerifierDeps decoded_deps(dex_files_, ArrayRef<const uint8_t>(buffer));
-    VerifierDeps::DexFileDeps* deps = decoded_deps.GetDexFileDeps(*primary_dex_file_);
-    deps->unassignable_types_.insert(*deps->assignable_types_.begin());
-    new_class_loader.Assign(soa.Decode<mirror::ClassLoader>(LoadDex("VerifierDeps")));
-    ASSERT_FALSE(decoded_deps.ValidateDependencies(new_class_loader, soa.Self()));
-  }
+  // Mess with classes.
+  ASSERT_FALSE(RunValidation([](VerifierDeps::DexFileDeps& deps) {
+        for (const auto& entry : deps.classes_) {
+          if (entry.IsResolved()) {
+            deps.classes_.insert(VerifierDeps::ClassResolution(
+                entry.GetDexTypeIndex(), VerifierDeps::kUnresolvedMarker));
+            return;
+          }
+        }
+        LOG(FATAL) << "Could not find any resolved classes";
+        UNREACHABLE();
+      }, buffer, &error_msg));
+  ASSERT_FALSE(RunValidation([](VerifierDeps::DexFileDeps& deps) {
+        for (const auto& entry : deps.classes_) {
+          if (!entry.IsResolved()) {
+            deps.classes_.insert(VerifierDeps::ClassResolution(
+                entry.GetDexTypeIndex(), VerifierDeps::kUnresolvedMarker - 1));
+            return;
+          }
+        }
+        LOG(FATAL) << "Could not find any unresolved classes";
+        UNREACHABLE();
+      }, buffer, &error_msg));
+  ASSERT_FALSE(RunValidation([](VerifierDeps::DexFileDeps& deps) {
+        for (const auto& entry : deps.classes_) {
+          if (entry.IsResolved()) {
+            deps.classes_.insert(VerifierDeps::ClassResolution(
+                entry.GetDexTypeIndex(), entry.GetAccessFlags() - 1));
+            return;
+          }
+        }
+        LOG(FATAL) << "Could not find any resolved classes";
+        UNREACHABLE();
+      }, buffer, &error_msg));
 
-  // Mess up with classes.
-  {
-    VerifierDeps decoded_deps(dex_files_, ArrayRef<const uint8_t>(buffer));
-    VerifierDeps::DexFileDeps* deps = decoded_deps.GetDexFileDeps(*primary_dex_file_);
-    bool found = false;
-    for (const auto& entry : deps->classes_) {
-      if (entry.IsResolved()) {
-        deps->classes_.insert(VerifierDeps::ClassResolution(
-            entry.GetDexTypeIndex(), VerifierDeps::kUnresolvedMarker));
-        found = true;
-        break;
-      }
-    }
-    ASSERT_TRUE(found);
-    new_class_loader.Assign(soa.Decode<mirror::ClassLoader>(LoadDex("VerifierDeps")));
-    ASSERT_FALSE(decoded_deps.ValidateDependencies(new_class_loader, soa.Self()));
-  }
+  // Mess with fields.
+  ASSERT_FALSE(RunValidation([](VerifierDeps::DexFileDeps& deps) {
+        for (const auto& entry : deps.fields_) {
+          if (entry.IsResolved()) {
+            deps.fields_.insert(VerifierDeps::FieldResolution(entry.GetDexFieldIndex(),
+                                                              VerifierDeps::kUnresolvedMarker,
+                                                              entry.GetDeclaringClassIndex()));
+            return;
+          }
+        }
+        LOG(FATAL) << "Could not find any resolved fields";
+        UNREACHABLE();
+      }, buffer, &error_msg));
+  ASSERT_FALSE(RunValidation([](VerifierDeps::DexFileDeps& deps) {
+        for (const auto& entry : deps.fields_) {
+          if (!entry.IsResolved()) {
+            constexpr dex::StringIndex kStringIndexZero(0);  // We know there is a class there.
+            deps.fields_.insert(VerifierDeps::FieldResolution(0 /* we know there is a field there */,
+                                                              VerifierDeps::kUnresolvedMarker - 1,
+                                                              kStringIndexZero));
+            return;
+          }
+        }
+        LOG(FATAL) << "Could not find any unresolved fields";
+        UNREACHABLE();
+      }, buffer, &error_msg));
+  ASSERT_FALSE(RunValidation([](VerifierDeps::DexFileDeps& deps) {
+        for (const auto& entry : deps.fields_) {
+          if (entry.IsResolved()) {
+            deps.fields_.insert(VerifierDeps::FieldResolution(entry.GetDexFieldIndex(),
+                                                              entry.GetAccessFlags() - 1,
+                                                              entry.GetDeclaringClassIndex()));
+            return;
+          }
+        }
+        LOG(FATAL) << "Could not find any resolved fields";
+        UNREACHABLE();
+      }, buffer, &error_msg));
+  ASSERT_FALSE(RunValidation([](VerifierDeps::DexFileDeps& deps) {
+        for (const auto& entry : deps.fields_) {
+          constexpr dex::StringIndex kNewTypeIndex(0);
+          if (entry.GetDeclaringClassIndex() != kNewTypeIndex) {
+            deps.fields_.insert(VerifierDeps::FieldResolution(entry.GetDexFieldIndex(),
+                                                              entry.GetAccessFlags(),
+                                                              kNewTypeIndex));
+            return;
+          }
+        }
+        LOG(FATAL) << "Could not find any suitable fields";
+        UNREACHABLE();
+      }, buffer, &error_msg));
 
-  {
-    VerifierDeps decoded_deps(dex_files_, ArrayRef<const uint8_t>(buffer));
-    VerifierDeps::DexFileDeps* deps = decoded_deps.GetDexFileDeps(*primary_dex_file_);
-    bool found = false;
-    for (const auto& entry : deps->classes_) {
-      if (!entry.IsResolved()) {
-        deps->classes_.insert(VerifierDeps::ClassResolution(
-            entry.GetDexTypeIndex(), VerifierDeps::kUnresolvedMarker - 1));
-        found = true;
-        break;
-      }
-    }
-    ASSERT_TRUE(found);
-    new_class_loader.Assign(soa.Decode<mirror::ClassLoader>(LoadDex("VerifierDeps")));
-    ASSERT_FALSE(decoded_deps.ValidateDependencies(new_class_loader, soa.Self()));
-  }
-
-  {
-    VerifierDeps decoded_deps(dex_files_, ArrayRef<const uint8_t>(buffer));
-    VerifierDeps::DexFileDeps* deps = decoded_deps.GetDexFileDeps(*primary_dex_file_);
-    bool found = false;
-    for (const auto& entry : deps->classes_) {
-      if (entry.IsResolved()) {
-        deps->classes_.insert(VerifierDeps::ClassResolution(
-            entry.GetDexTypeIndex(), entry.GetAccessFlags() - 1));
-        found = true;
-        break;
-      }
-    }
-    ASSERT_TRUE(found);
-    new_class_loader.Assign(soa.Decode<mirror::ClassLoader>(LoadDex("VerifierDeps")));
-    ASSERT_FALSE(decoded_deps.ValidateDependencies(new_class_loader, soa.Self()));
-  }
-
-  // Mess up with fields.
-  {
-    VerifierDeps decoded_deps(dex_files_, ArrayRef<const uint8_t>(buffer));
-    VerifierDeps::DexFileDeps* deps = decoded_deps.GetDexFileDeps(*primary_dex_file_);
-    bool found = false;
-    for (const auto& entry : deps->fields_) {
-      if (entry.IsResolved()) {
-        deps->fields_.insert(VerifierDeps::FieldResolution(entry.GetDexFieldIndex(),
-                                                           VerifierDeps::kUnresolvedMarker,
-                                                           entry.GetDeclaringClassIndex()));
-        found = true;
-        break;
-      }
-    }
-    ASSERT_TRUE(found);
-    new_class_loader.Assign(soa.Decode<mirror::ClassLoader>(LoadDex("VerifierDeps")));
-    ASSERT_FALSE(decoded_deps.ValidateDependencies(new_class_loader, soa.Self()));
-  }
-
-  {
-    VerifierDeps decoded_deps(dex_files_, ArrayRef<const uint8_t>(buffer));
-    VerifierDeps::DexFileDeps* deps = decoded_deps.GetDexFileDeps(*primary_dex_file_);
-    bool found = false;
-    for (const auto& entry : deps->fields_) {
-      if (!entry.IsResolved()) {
-        constexpr dex::StringIndex kStringIndexZero(0);  // We know there is a class there.
-        deps->fields_.insert(VerifierDeps::FieldResolution(0 /* we know there is a field there */,
-                                                           VerifierDeps::kUnresolvedMarker - 1,
-                                                           kStringIndexZero));
-        found = true;
-        break;
-      }
-    }
-    ASSERT_TRUE(found);
-    new_class_loader.Assign(soa.Decode<mirror::ClassLoader>(LoadDex("VerifierDeps")));
-    ASSERT_FALSE(decoded_deps.ValidateDependencies(new_class_loader, soa.Self()));
-  }
-
-  {
-    VerifierDeps decoded_deps(dex_files_, ArrayRef<const uint8_t>(buffer));
-    VerifierDeps::DexFileDeps* deps = decoded_deps.GetDexFileDeps(*primary_dex_file_);
-    bool found = false;
-    for (const auto& entry : deps->fields_) {
-      if (entry.IsResolved()) {
-        deps->fields_.insert(VerifierDeps::FieldResolution(entry.GetDexFieldIndex(),
-                                                           entry.GetAccessFlags() - 1,
-                                                           entry.GetDeclaringClassIndex()));
-        found = true;
-        break;
-      }
-    }
-    ASSERT_TRUE(found);
-    new_class_loader.Assign(soa.Decode<mirror::ClassLoader>(LoadDex("VerifierDeps")));
-    ASSERT_FALSE(decoded_deps.ValidateDependencies(new_class_loader, soa.Self()));
-  }
-
-  {
-    VerifierDeps decoded_deps(dex_files_, ArrayRef<const uint8_t>(buffer));
-    VerifierDeps::DexFileDeps* deps = decoded_deps.GetDexFileDeps(*primary_dex_file_);
-    bool found = false;
-    for (const auto& entry : deps->fields_) {
-      constexpr dex::StringIndex kNewTypeIndex(0);
-      if (entry.GetDeclaringClassIndex() != kNewTypeIndex) {
-        deps->fields_.insert(VerifierDeps::FieldResolution(entry.GetDexFieldIndex(),
-                                                           entry.GetAccessFlags(),
-                                                           kNewTypeIndex));
-        found = true;
-        break;
-      }
-    }
-    ASSERT_TRUE(found);
-    new_class_loader.Assign(soa.Decode<mirror::ClassLoader>(LoadDex("VerifierDeps")));
-    ASSERT_FALSE(decoded_deps.ValidateDependencies(new_class_loader, soa.Self()));
-  }
-
-  // Mess up with methods.
-  {
-    VerifierDeps decoded_deps(dex_files_, ArrayRef<const uint8_t>(buffer));
-    VerifierDeps::DexFileDeps* deps = decoded_deps.GetDexFileDeps(*primary_dex_file_);
-    bool found = false;
-    std::set<VerifierDeps::MethodResolution>* methods = &deps->methods_;
-    for (const auto& entry : *methods) {
-      if (entry.IsResolved()) {
-        methods->insert(VerifierDeps::MethodResolution(entry.GetDexMethodIndex(),
-                                                       VerifierDeps::kUnresolvedMarker,
-                                                       entry.GetDeclaringClassIndex()));
-        found = true;
-        break;
-      }
-    }
-    ASSERT_TRUE(found);
-    new_class_loader.Assign(soa.Decode<mirror::ClassLoader>(LoadDex("VerifierDeps")));
-    ASSERT_FALSE(decoded_deps.ValidateDependencies(new_class_loader, soa.Self()));
-  }
-
-  {
-    VerifierDeps decoded_deps(dex_files_, ArrayRef<const uint8_t>(buffer));
-    VerifierDeps::DexFileDeps* deps = decoded_deps.GetDexFileDeps(*primary_dex_file_);
-    bool found = false;
-    std::set<VerifierDeps::MethodResolution>* methods = &deps->methods_;
-    for (const auto& entry : *methods) {
-      if (!entry.IsResolved()) {
-        constexpr dex::StringIndex kStringIndexZero(0);  // We know there is a class there.
-        methods->insert(VerifierDeps::MethodResolution(0 /* we know there is a method there */,
-                                                       VerifierDeps::kUnresolvedMarker - 1,
-                                                       kStringIndexZero));
-        found = true;
-        break;
-      }
-    }
-    ASSERT_TRUE(found);
-    new_class_loader.Assign(soa.Decode<mirror::ClassLoader>(LoadDex("VerifierDeps")));
-    ASSERT_FALSE(decoded_deps.ValidateDependencies(new_class_loader, soa.Self()));
-  }
-
-  {
-    VerifierDeps decoded_deps(dex_files_, ArrayRef<const uint8_t>(buffer));
-    VerifierDeps::DexFileDeps* deps = decoded_deps.GetDexFileDeps(*primary_dex_file_);
-    bool found = false;
-    std::set<VerifierDeps::MethodResolution>* methods = &deps->methods_;
-    for (const auto& entry : *methods) {
-      if (entry.IsResolved()) {
-        methods->insert(VerifierDeps::MethodResolution(entry.GetDexMethodIndex(),
-                                                       entry.GetAccessFlags() - 1,
-                                                       entry.GetDeclaringClassIndex()));
-        found = true;
-        break;
-      }
-    }
-    ASSERT_TRUE(found);
-    new_class_loader.Assign(soa.Decode<mirror::ClassLoader>(LoadDex("VerifierDeps")));
-    ASSERT_FALSE(decoded_deps.ValidateDependencies(new_class_loader, soa.Self()));
-  }
-
-  {
-    VerifierDeps decoded_deps(dex_files_, ArrayRef<const uint8_t>(buffer));
-    VerifierDeps::DexFileDeps* deps = decoded_deps.GetDexFileDeps(*primary_dex_file_);
-    bool found = false;
-    std::set<VerifierDeps::MethodResolution>* methods = &deps->methods_;
-    for (const auto& entry : *methods) {
-      constexpr dex::StringIndex kNewTypeIndex(0);
-      if (entry.IsResolved() && entry.GetDeclaringClassIndex() != kNewTypeIndex) {
-        methods->insert(VerifierDeps::MethodResolution(entry.GetDexMethodIndex(),
-                                                       entry.GetAccessFlags(),
-                                                       kNewTypeIndex));
-        found = true;
-        break;
-      }
-    }
-    ASSERT_TRUE(found);
-    new_class_loader.Assign(soa.Decode<mirror::ClassLoader>(LoadDex("VerifierDeps")));
-    ASSERT_FALSE(decoded_deps.ValidateDependencies(new_class_loader, soa.Self()));
-  }
+  // Mess with methods.
+  ASSERT_FALSE(RunValidation([](VerifierDeps::DexFileDeps& deps) {
+        std::set<VerifierDeps::MethodResolution>* methods = &deps.methods_;
+        for (const auto& entry : *methods) {
+          if (entry.IsResolved()) {
+            methods->insert(VerifierDeps::MethodResolution(entry.GetDexMethodIndex(),
+                                                          VerifierDeps::kUnresolvedMarker,
+                                                          entry.GetDeclaringClassIndex()));
+            return;
+          }
+        }
+        LOG(FATAL) << "Could not find any resolved methods";
+        UNREACHABLE();
+      }, buffer, &error_msg));
+  ASSERT_FALSE(RunValidation([](VerifierDeps::DexFileDeps& deps) {
+        std::set<VerifierDeps::MethodResolution>* methods = &deps.methods_;
+        for (const auto& entry : *methods) {
+          if (!entry.IsResolved()) {
+            constexpr dex::StringIndex kStringIndexZero(0);  // We know there is a class there.
+            methods->insert(VerifierDeps::MethodResolution(0 /* we know there is a method there */,
+                                                          VerifierDeps::kUnresolvedMarker - 1,
+                                                          kStringIndexZero));
+            return;
+          }
+        }
+        LOG(FATAL) << "Could not find any unresolved methods";
+        UNREACHABLE();
+      }, buffer, &error_msg));
+  ASSERT_FALSE(RunValidation([](VerifierDeps::DexFileDeps& deps) {
+        std::set<VerifierDeps::MethodResolution>* methods = &deps.methods_;
+        for (const auto& entry : *methods) {
+          if (entry.IsResolved()) {
+            methods->insert(VerifierDeps::MethodResolution(entry.GetDexMethodIndex(),
+                                                          entry.GetAccessFlags() - 1,
+                                                          entry.GetDeclaringClassIndex()));
+            return;
+          }
+        }
+        LOG(FATAL) << "Could not find any resolved methods";
+        UNREACHABLE();
+      }, buffer, &error_msg));
+  ASSERT_FALSE(RunValidation([](VerifierDeps::DexFileDeps& deps) {
+        std::set<VerifierDeps::MethodResolution>* methods = &deps.methods_;
+        for (const auto& entry : *methods) {
+          constexpr dex::StringIndex kNewTypeIndex(0);
+          if (entry.IsResolved() && entry.GetDeclaringClassIndex() != kNewTypeIndex) {
+            methods->insert(VerifierDeps::MethodResolution(entry.GetDexMethodIndex(),
+                                                          entry.GetAccessFlags(),
+                                                          kNewTypeIndex));
+            return;
+          }
+        }
+        LOG(FATAL) << "Could not find any suitable methods";
+        UNREACHABLE();
+      }, buffer, &error_msg));
 }
 
 TEST_F(VerifierDepsTest, CompilerDriver) {
diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc
index ad1dda4..1f18172 100644
--- a/dex2oat/dex2oat.cc
+++ b/dex2oat/dex2oat.cc
@@ -49,7 +49,6 @@
 #include "base/os.h"
 #include "base/scoped_flock.h"
 #include "base/stl_util.h"
-#include "base/stringpiece.h"
 #include "base/time_utils.h"
 #include "base/timing_logger.h"
 #include "base/unix_file/fd_file.h"
diff --git a/dex2oat/linker/arm/relative_patcher_thumb2_test.cc b/dex2oat/linker/arm/relative_patcher_thumb2_test.cc
index d8cbbaf..04a897e 100644
--- a/dex2oat/linker/arm/relative_patcher_thumb2_test.cc
+++ b/dex2oat/linker/arm/relative_patcher_thumb2_test.cc
@@ -140,50 +140,63 @@
                       | (((diff >> 20) & 1) << 26);     // S
   }
 
-  bool Create2MethodsWithGap(const ArrayRef<const uint8_t>& method1_code,
-                             const ArrayRef<const LinkerPatch>& method1_patches,
-                             const ArrayRef<const uint8_t>& method3_code,
-                             const ArrayRef<const LinkerPatch>& method3_patches,
-                             uint32_t distance_without_thunks) {
+  uint32_t Create2MethodsWithGap(const ArrayRef<const uint8_t>& method1_code,
+                                 const ArrayRef<const LinkerPatch>& method1_patches,
+                                 const ArrayRef<const uint8_t>& last_method_code,
+                                 const ArrayRef<const LinkerPatch>& last_method_patches,
+                                 uint32_t distance_without_thunks) {
     CHECK_EQ(distance_without_thunks % kArmAlignment, 0u);
     uint32_t method1_offset =
         kTrampolineSize + CodeAlignmentSize(kTrampolineSize) + sizeof(OatQuickMethodHeader);
     AddCompiledMethod(MethodRef(1u), method1_code, method1_patches);
+    const uint32_t gap_start = method1_offset + method1_code.size();
 
-    // We want to put the method3 at a very precise offset.
-    const uint32_t method3_offset = method1_offset + distance_without_thunks;
-    CHECK_ALIGNED(method3_offset, kArmAlignment);
+    // We want to put the last method at a very precise offset.
+    const uint32_t last_method_offset = method1_offset + distance_without_thunks;
+    CHECK_ALIGNED(last_method_offset, kArmAlignment);
+    const uint32_t gap_end = last_method_offset - sizeof(OatQuickMethodHeader);
 
-    // Calculate size of method2 so that we put method3 at the correct place.
-    const uint32_t method1_end = method1_offset + method1_code.size();
-    const uint32_t method2_offset =
-        method1_end + CodeAlignmentSize(method1_end) + sizeof(OatQuickMethodHeader);
-    const uint32_t method2_size = (method3_offset - sizeof(OatQuickMethodHeader) - method2_offset);
-    std::vector<uint8_t> method2_raw_code(method2_size);
-    ArrayRef<const uint8_t> method2_code(method2_raw_code);
-    AddCompiledMethod(MethodRef(2u), method2_code);
+    // Fill the gap with intermediate methods in chunks of 2MiB and the first in [2MiB, 4MiB).
+    // (This allows deduplicating the small chunks to avoid using 32MiB of memory for +-16MiB
+    // offsets by this test. Making the first chunk bigger makes it easy to give all intermediate
+    // methods the same alignment of the end, so the thunk insertion adds a predictable size as
+    // long as it's after the first chunk.)
+    uint32_t method_idx = 2u;
+    constexpr uint32_t kSmallChunkSize = 2 * MB;
+    std::vector<uint8_t> gap_code;
+    uint32_t gap_size = gap_end - gap_start;
+    uint32_t num_small_chunks = std::max(gap_size / kSmallChunkSize, 1u) - 1u;
+    uint32_t chunk_start = gap_start;
+    uint32_t chunk_size = gap_size - num_small_chunks * kSmallChunkSize;
+    for (uint32_t i = 0; i <= num_small_chunks; ++i) {  // num_small_chunks+1 iterations.
+      uint32_t chunk_code_size =
+          chunk_size - CodeAlignmentSize(chunk_start) - sizeof(OatQuickMethodHeader);
+      gap_code.resize(chunk_code_size, 0u);
+      AddCompiledMethod(MethodRef(method_idx), ArrayRef<const uint8_t>(gap_code));
+      method_idx += 1u;
+      chunk_start += chunk_size;
+      chunk_size = kSmallChunkSize;  // For all but the first chunk.
+      DCHECK_EQ(CodeAlignmentSize(gap_end), CodeAlignmentSize(chunk_start));
+    }
 
-    AddCompiledMethod(MethodRef(3u), method3_code, method3_patches);
-
+    // Add the last method and link
+    AddCompiledMethod(MethodRef(method_idx), last_method_code, last_method_patches);
     Link();
 
     // Check assumptions.
     CHECK_EQ(GetMethodOffset(1), method1_offset);
-    CHECK_EQ(GetMethodOffset(2), method2_offset);
-    auto result3 = method_offset_map_.FindMethodOffset(MethodRef(3));
-    CHECK(result3.first);
+    auto last_result = method_offset_map_.FindMethodOffset(MethodRef(method_idx));
+    CHECK(last_result.first);
     // There may be a thunk before method2.
-    if (result3.second == method3_offset + 1 /* thumb mode */) {
-      return false;  // No thunk.
-    } else {
+    if (last_result.second != last_method_offset + 1 /* thumb mode */) {
+      // Thunk present. Check that there's only one.
       uint32_t thunk_end =
-          CompiledCode::AlignCode(method3_offset - sizeof(OatQuickMethodHeader),
-                                  InstructionSet::kThumb2) +
-          MethodCallThunkSize();
+          CompiledCode::AlignCode(gap_end, InstructionSet::kThumb2) + MethodCallThunkSize();
       uint32_t header_offset = thunk_end + CodeAlignmentSize(thunk_end);
-      CHECK_EQ(result3.second, header_offset + sizeof(OatQuickMethodHeader) + 1 /* thumb mode */);
-      return true;   // Thunk present.
+      CHECK_EQ(last_result.second,
+               header_offset + sizeof(OatQuickMethodHeader) + 1 /* thumb mode */);
     }
+    return method_idx;
   }
 
   uint32_t GetMethodOffset(uint32_t method_idx) {
@@ -447,32 +460,37 @@
 
 TEST_F(Thumb2RelativePatcherTest, CallTrampolineTooFar) {
   constexpr uint32_t missing_method_index = 1024u;
-  auto method3_raw_code = GenNopsAndBl(3u, kBlPlus0);
-  constexpr uint32_t bl_offset_in_method3 = 3u * 2u;  // After NOPs.
-  ArrayRef<const uint8_t> method3_code(method3_raw_code);
-  ASSERT_EQ(bl_offset_in_method3 + 4u, method3_code.size());
-  const LinkerPatch method3_patches[] = {
-      LinkerPatch::RelativeCodePatch(bl_offset_in_method3, nullptr, missing_method_index),
+  auto last_method_raw_code = GenNopsAndBl(3u, kBlPlus0);
+  constexpr uint32_t bl_offset_in_last_method = 3u * 2u;  // After NOPs.
+  ArrayRef<const uint8_t> last_method_code(last_method_raw_code);
+  ASSERT_EQ(bl_offset_in_last_method + 4u, last_method_code.size());
+  const LinkerPatch last_method_patches[] = {
+      LinkerPatch::RelativeCodePatch(bl_offset_in_last_method, nullptr, missing_method_index),
   };
 
   constexpr uint32_t just_over_max_negative_disp = 16 * MB + 2 - 4u /* PC adjustment */;
-  bool thunk_in_gap = Create2MethodsWithGap(kNopCode,
-                                            ArrayRef<const LinkerPatch>(),
-                                            method3_code,
-                                            ArrayRef<const LinkerPatch>(method3_patches),
-                                            just_over_max_negative_disp - bl_offset_in_method3);
-  ASSERT_FALSE(thunk_in_gap);  // There should be a thunk but it should be after the method2.
+  uint32_t last_method_idx = Create2MethodsWithGap(
+      kNopCode,
+      ArrayRef<const LinkerPatch>(),
+      last_method_code,
+      ArrayRef<const LinkerPatch>(last_method_patches),
+      just_over_max_negative_disp - bl_offset_in_last_method);
+  uint32_t method1_offset = GetMethodOffset(1u);
+  uint32_t last_method_offset = GetMethodOffset(last_method_idx);
+  ASSERT_EQ(method1_offset,
+            last_method_offset + bl_offset_in_last_method - just_over_max_negative_disp);
   ASSERT_FALSE(method_offset_map_.FindMethodOffset(MethodRef(missing_method_index)).first);
 
   // Check linked code.
-  uint32_t method3_offset = GetMethodOffset(3u);
-  uint32_t thunk_offset = CompiledCode::AlignCode(method3_offset + method3_code.size(),
-                                                  InstructionSet::kThumb2);
-  uint32_t diff = thunk_offset - (method3_offset + bl_offset_in_method3 + 4u /* PC adjustment */);
-  ASSERT_EQ(diff & 1u, 0u);
+  uint32_t thunk_offset = CompiledCode::AlignCode(
+      last_method_offset + last_method_code.size(), InstructionSet::kThumb2);
+  uint32_t diff =
+      thunk_offset - (last_method_offset + bl_offset_in_last_method + 4u /* PC adjustment */);
+  ASSERT_TRUE(IsAligned<2u>(diff));
   ASSERT_LT(diff >> 1, 1u << 8);  // Simple encoding, (diff >> 1) fits into 8 bits.
   auto expected_code = GenNopsAndBl(3u, kBlPlus0 | ((diff >> 1) & 0xffu));
-  EXPECT_TRUE(CheckLinkedMethod(MethodRef(3u), ArrayRef<const uint8_t>(expected_code)));
+  EXPECT_TRUE(CheckLinkedMethod(MethodRef(last_method_idx),
+                                ArrayRef<const uint8_t>(expected_code)));
   EXPECT_TRUE(CheckThunk(thunk_offset));
 }
 
@@ -481,17 +499,18 @@
   constexpr uint32_t bl_offset_in_method1 = 3u * 2u;  // After NOPs.
   ArrayRef<const uint8_t> method1_code(method1_raw_code);
   ASSERT_EQ(bl_offset_in_method1 + 4u, method1_code.size());
+  const uint32_t kExpectedLastMethodIdx = 9u;  // Based on 2MiB chunks in Create2MethodsWithGap().
   const LinkerPatch method1_patches[] = {
-      LinkerPatch::RelativeCodePatch(bl_offset_in_method1, nullptr, 3u),
+      LinkerPatch::RelativeCodePatch(bl_offset_in_method1, nullptr, kExpectedLastMethodIdx),
   };
 
   constexpr uint32_t max_positive_disp = 16 * MB - 2u + 4u /* PC adjustment */;
-  bool thunk_in_gap = Create2MethodsWithGap(method1_code,
-                                            ArrayRef<const LinkerPatch>(method1_patches),
-                                            kNopCode,
-                                            ArrayRef<const LinkerPatch>(),
-                                            bl_offset_in_method1 + max_positive_disp);
-  ASSERT_FALSE(thunk_in_gap);  // There should be no thunk.
+  uint32_t last_method_idx = Create2MethodsWithGap(method1_code,
+                                                   ArrayRef<const LinkerPatch>(method1_patches),
+                                                   kNopCode,
+                                                   ArrayRef<const LinkerPatch>(),
+                                                   bl_offset_in_method1 + max_positive_disp);
+  ASSERT_EQ(kExpectedLastMethodIdx, last_method_idx);
 
   // Check linked code.
   auto expected_code = GenNopsAndBl(3u, kBlPlusMax);
@@ -499,25 +518,28 @@
 }
 
 TEST_F(Thumb2RelativePatcherTest, CallOtherAlmostTooFarBefore) {
-  auto method3_raw_code = GenNopsAndBl(2u, kBlPlus0);
-  constexpr uint32_t bl_offset_in_method3 = 2u * 2u;  // After NOPs.
-  ArrayRef<const uint8_t> method3_code(method3_raw_code);
-  ASSERT_EQ(bl_offset_in_method3 + 4u, method3_code.size());
-  const LinkerPatch method3_patches[] = {
-      LinkerPatch::RelativeCodePatch(bl_offset_in_method3, nullptr, 1u),
+  auto last_method_raw_code = GenNopsAndBl(2u, kBlPlus0);
+  constexpr uint32_t bl_offset_in_last_method = 2u * 2u;  // After NOPs.
+  ArrayRef<const uint8_t> last_method_code(last_method_raw_code);
+  ASSERT_EQ(bl_offset_in_last_method + 4u, last_method_code.size());
+  const LinkerPatch last_method_patches[] = {
+      LinkerPatch::RelativeCodePatch(bl_offset_in_last_method, nullptr, 1u),
   };
 
-  constexpr uint32_t just_over_max_negative_disp = 16 * MB - 4u /* PC adjustment */;
-  bool thunk_in_gap = Create2MethodsWithGap(kNopCode,
-                                            ArrayRef<const LinkerPatch>(),
-                                            method3_code,
-                                            ArrayRef<const LinkerPatch>(method3_patches),
-                                            just_over_max_negative_disp - bl_offset_in_method3);
-  ASSERT_FALSE(thunk_in_gap);  // There should be no thunk.
+  constexpr uint32_t max_negative_disp = 16 * MB - 4u /* PC adjustment */;
+  uint32_t last_method_idx = Create2MethodsWithGap(kNopCode,
+                                                   ArrayRef<const LinkerPatch>(),
+                                                   last_method_code,
+                                                   ArrayRef<const LinkerPatch>(last_method_patches),
+                                                   max_negative_disp - bl_offset_in_last_method);
+  uint32_t method1_offset = GetMethodOffset(1u);
+  uint32_t last_method_offset = GetMethodOffset(last_method_idx);
+  ASSERT_EQ(method1_offset, last_method_offset + bl_offset_in_last_method - max_negative_disp);
 
   // Check linked code.
   auto expected_code = GenNopsAndBl(2u, kBlMinusMax);
-  EXPECT_TRUE(CheckLinkedMethod(MethodRef(3u), ArrayRef<const uint8_t>(expected_code)));
+  EXPECT_TRUE(CheckLinkedMethod(MethodRef(last_method_idx),
+                                ArrayRef<const uint8_t>(expected_code)));
 }
 
 TEST_F(Thumb2RelativePatcherTest, CallOtherJustTooFarAfter) {
@@ -525,61 +547,78 @@
   constexpr uint32_t bl_offset_in_method1 = 2u * 2u;  // After NOPs.
   ArrayRef<const uint8_t> method1_code(method1_raw_code);
   ASSERT_EQ(bl_offset_in_method1 + 4u, method1_code.size());
+  const uint32_t kExpectedLastMethodIdx = 9u;  // Based on 2MiB chunks in Create2MethodsWithGap().
   const LinkerPatch method1_patches[] = {
-      LinkerPatch::RelativeCodePatch(bl_offset_in_method1, nullptr, 3u),
+      LinkerPatch::RelativeCodePatch(bl_offset_in_method1, nullptr, kExpectedLastMethodIdx),
   };
 
   constexpr uint32_t just_over_max_positive_disp = 16 * MB + 4u /* PC adjustment */;
-  bool thunk_in_gap = Create2MethodsWithGap(method1_code,
-                                            ArrayRef<const LinkerPatch>(method1_patches),
-                                            kNopCode,
-                                            ArrayRef<const LinkerPatch>(),
-                                            bl_offset_in_method1 + just_over_max_positive_disp);
-  ASSERT_TRUE(thunk_in_gap);
+  uint32_t last_method_idx = Create2MethodsWithGap(
+      method1_code,
+      ArrayRef<const LinkerPatch>(method1_patches),
+      kNopCode,
+      ArrayRef<const LinkerPatch>(),
+      bl_offset_in_method1 + just_over_max_positive_disp);
+  ASSERT_EQ(kExpectedLastMethodIdx, last_method_idx);
+  uint32_t method_after_thunk_idx = last_method_idx;
+  if (sizeof(OatQuickMethodHeader) < kArmAlignment) {
+    // The thunk needs to start on a kArmAlignment-aligned address before the address where the
+    // last method would have been if there was no thunk. If the size of the OatQuickMethodHeader
+    // is at least kArmAlignment, the thunk start shall fit between the previous filler method
+    // and that address. Otherwise, it shall be inserted before that filler method.
+    method_after_thunk_idx -= 1u;
+  }
 
   uint32_t method1_offset = GetMethodOffset(1u);
-  uint32_t method3_offset = GetMethodOffset(3u);
-  ASSERT_TRUE(IsAligned<kArmAlignment>(method3_offset));
-  uint32_t method3_header_offset = method3_offset - sizeof(OatQuickMethodHeader);
+  uint32_t method_after_thunk_offset = GetMethodOffset(method_after_thunk_idx);
+  ASSERT_TRUE(IsAligned<kArmAlignment>(method_after_thunk_offset));
+  uint32_t method_after_thunk_header_offset =
+      method_after_thunk_offset - sizeof(OatQuickMethodHeader);
   uint32_t thunk_size = MethodCallThunkSize();
-  uint32_t thunk_offset = RoundDown(method3_header_offset - thunk_size, kArmAlignment);
+  uint32_t thunk_offset = RoundDown(method_after_thunk_header_offset - thunk_size, kArmAlignment);
   DCHECK_EQ(thunk_offset + thunk_size + CodeAlignmentSize(thunk_offset + thunk_size),
-            method3_header_offset);
+            method_after_thunk_header_offset);
   ASSERT_TRUE(IsAligned<kArmAlignment>(thunk_offset));
   uint32_t diff = thunk_offset - (method1_offset + bl_offset_in_method1 + 4u /* PC adjustment */);
-  ASSERT_EQ(diff & 1u, 0u);
-  ASSERT_GE(diff, 16 * MB - (1u << 9));  // Simple encoding, unknown bits fit into the low 8 bits.
-  auto expected_code = GenNopsAndBl(2u, 0xf3ffd700 | ((diff >> 1) & 0xffu));
+  ASSERT_TRUE(IsAligned<2u>(diff));
+  ASSERT_GE(diff, 16 * MB - (1u << 22));  // Simple encoding, unknown bits fit into imm10:imm11:0.
+  auto expected_code =
+      GenNopsAndBl(2u, 0xf000d000 | ((diff >> 1) & 0x7ffu) | (((diff >> 12) & 0x3ffu) << 16));
   EXPECT_TRUE(CheckLinkedMethod(MethodRef(1u), ArrayRef<const uint8_t>(expected_code)));
   CheckThunk(thunk_offset);
 }
 
 TEST_F(Thumb2RelativePatcherTest, CallOtherJustTooFarBefore) {
-  auto method3_raw_code = GenNopsAndBl(3u, kBlPlus0);
-  constexpr uint32_t bl_offset_in_method3 = 3u * 2u;  // After NOPs.
-  ArrayRef<const uint8_t> method3_code(method3_raw_code);
-  ASSERT_EQ(bl_offset_in_method3 + 4u, method3_code.size());
-  const LinkerPatch method3_patches[] = {
-      LinkerPatch::RelativeCodePatch(bl_offset_in_method3, nullptr, 1u),
+  auto last_method_raw_code = GenNopsAndBl(3u, kBlPlus0);
+  constexpr uint32_t bl_offset_in_last_method = 3u * 2u;  // After NOPs.
+  ArrayRef<const uint8_t> last_method_code(last_method_raw_code);
+  ASSERT_EQ(bl_offset_in_last_method + 4u, last_method_code.size());
+  const LinkerPatch last_method_patches[] = {
+      LinkerPatch::RelativeCodePatch(bl_offset_in_last_method, nullptr, 1u),
   };
 
   constexpr uint32_t just_over_max_negative_disp = 16 * MB + 2 - 4u /* PC adjustment */;
-  bool thunk_in_gap = Create2MethodsWithGap(kNopCode,
-                                            ArrayRef<const LinkerPatch>(),
-                                            method3_code,
-                                            ArrayRef<const LinkerPatch>(method3_patches),
-                                            just_over_max_negative_disp - bl_offset_in_method3);
-  ASSERT_FALSE(thunk_in_gap);  // There should be a thunk but it should be after the method2.
+  uint32_t last_method_idx = Create2MethodsWithGap(
+      kNopCode,
+      ArrayRef<const LinkerPatch>(),
+      last_method_code,
+      ArrayRef<const LinkerPatch>(last_method_patches),
+      just_over_max_negative_disp - bl_offset_in_last_method);
+  uint32_t method1_offset = GetMethodOffset(1u);
+  uint32_t last_method_offset = GetMethodOffset(last_method_idx);
+  ASSERT_EQ(method1_offset,
+            last_method_offset + bl_offset_in_last_method - just_over_max_negative_disp);
 
   // Check linked code.
-  uint32_t method3_offset = GetMethodOffset(3u);
-  uint32_t thunk_offset = CompiledCode::AlignCode(method3_offset + method3_code.size(),
-                                                  InstructionSet::kThumb2);
-  uint32_t diff = thunk_offset - (method3_offset + bl_offset_in_method3 + 4u /* PC adjustment */);
-  ASSERT_EQ(diff & 1u, 0u);
+  uint32_t thunk_offset = CompiledCode::AlignCode(
+      last_method_offset + last_method_code.size(), InstructionSet::kThumb2);
+  uint32_t diff =
+      thunk_offset - (last_method_offset + bl_offset_in_last_method + 4u /* PC adjustment */);
+  ASSERT_TRUE(IsAligned<2u>(diff));
   ASSERT_LT(diff >> 1, 1u << 8);  // Simple encoding, (diff >> 1) fits into 8 bits.
   auto expected_code = GenNopsAndBl(3u, kBlPlus0 | ((diff >> 1) & 0xffu));
-  EXPECT_TRUE(CheckLinkedMethod(MethodRef(3u), ArrayRef<const uint8_t>(expected_code)));
+  EXPECT_TRUE(CheckLinkedMethod(MethodRef(last_method_idx),
+                                ArrayRef<const uint8_t>(expected_code)));
   EXPECT_TRUE(CheckThunk(thunk_offset));
 }
 
diff --git a/dex2oat/linker/arm64/relative_patcher_arm64_test.cc b/dex2oat/linker/arm64/relative_patcher_arm64_test.cc
index 678574b..9e54bbf 100644
--- a/dex2oat/linker/arm64/relative_patcher_arm64_test.cc
+++ b/dex2oat/linker/arm64/relative_patcher_arm64_test.cc
@@ -118,7 +118,7 @@
     AddCompiledMethod(MethodRef(1u), method1_code, method1_patches);
     const uint32_t gap_start = method1_offset + method1_code.size();
 
-    // We want to put the method3 at a very precise offset.
+    // We want to put the last method at a very precise offset.
     const uint32_t last_method_offset = method1_offset + distance_without_thunks;
     CHECK_ALIGNED(last_method_offset, kArm64Alignment);
     const uint32_t gap_end = last_method_offset - sizeof(OatQuickMethodHeader);
@@ -638,7 +638,9 @@
 
   constexpr uint32_t just_over_max_negative_disp = 128 * MB + 4;
   uint32_t last_method_idx = Create2MethodsWithGap(
-      kNopCode, ArrayRef<const LinkerPatch>(), last_method_code,
+      kNopCode,
+      ArrayRef<const LinkerPatch>(),
+      last_method_code,
       ArrayRef<const LinkerPatch>(last_method_patches),
       just_over_max_negative_disp - bl_offset_in_last_method);
   uint32_t method1_offset = GetMethodOffset(1u);
@@ -651,7 +653,7 @@
   uint32_t thunk_offset =
       CompiledCode::AlignCode(last_method_offset + last_method_code.size(), InstructionSet::kArm64);
   uint32_t diff = thunk_offset - (last_method_offset + bl_offset_in_last_method);
-  CHECK_ALIGNED(diff, 4u);
+  ASSERT_TRUE(IsAligned<4u>(diff));
   ASSERT_LT(diff, 128 * MB);
   auto expected_code = GenNopsAndBl(1u, kBlPlus0 | (diff >> 2));
   EXPECT_TRUE(CheckLinkedMethod(MethodRef(last_method_idx),
@@ -664,9 +666,9 @@
   constexpr uint32_t bl_offset_in_method1 = 1u * 4u;  // After NOPs.
   ArrayRef<const uint8_t> method1_code(method1_raw_code);
   ASSERT_EQ(bl_offset_in_method1 + 4u, method1_code.size());
-  uint32_t expected_last_method_idx = 65;  // Based on 2MiB chunks in Create2MethodsWithGap().
+  const uint32_t kExpectedLastMethodIdx = 65u;  // Based on 2MiB chunks in Create2MethodsWithGap().
   const LinkerPatch method1_patches[] = {
-      LinkerPatch::RelativeCodePatch(bl_offset_in_method1, nullptr, expected_last_method_idx),
+      LinkerPatch::RelativeCodePatch(bl_offset_in_method1, nullptr, kExpectedLastMethodIdx),
   };
 
   constexpr uint32_t max_positive_disp = 128 * MB - 4u;
@@ -675,7 +677,7 @@
                                                    kNopCode,
                                                    ArrayRef<const LinkerPatch>(),
                                                    bl_offset_in_method1 + max_positive_disp);
-  ASSERT_EQ(expected_last_method_idx, last_method_idx);
+  ASSERT_EQ(kExpectedLastMethodIdx, last_method_idx);
 
   uint32_t method1_offset = GetMethodOffset(1u);
   uint32_t last_method_offset = GetMethodOffset(last_method_idx);
@@ -716,9 +718,9 @@
   constexpr uint32_t bl_offset_in_method1 = 0u * 4u;  // After NOPs.
   ArrayRef<const uint8_t> method1_code(method1_raw_code);
   ASSERT_EQ(bl_offset_in_method1 + 4u, method1_code.size());
-  uint32_t expected_last_method_idx = 65;  // Based on 2MiB chunks in Create2MethodsWithGap().
+  const uint32_t kExpectedLastMethodIdx = 65u;  // Based on 2MiB chunks in Create2MethodsWithGap().
   const LinkerPatch method1_patches[] = {
-      LinkerPatch::RelativeCodePatch(bl_offset_in_method1, nullptr, expected_last_method_idx),
+      LinkerPatch::RelativeCodePatch(bl_offset_in_method1, nullptr, kExpectedLastMethodIdx),
   };
 
   constexpr uint32_t just_over_max_positive_disp = 128 * MB;
@@ -728,7 +730,7 @@
       kNopCode,
       ArrayRef<const LinkerPatch>(),
       bl_offset_in_method1 + just_over_max_positive_disp);
-  ASSERT_EQ(expected_last_method_idx, last_method_idx);
+  ASSERT_EQ(kExpectedLastMethodIdx, last_method_idx);
   uint32_t method_after_thunk_idx = last_method_idx;
   if (sizeof(OatQuickMethodHeader) < kArm64Alignment) {
     // The thunk needs to start on a kArm64Alignment-aligned address before the address where the
@@ -747,8 +749,9 @@
   uint32_t thunk_offset = RoundDown(method_after_thunk_header_offset - thunk_size, kArm64Alignment);
   DCHECK_EQ(thunk_offset + thunk_size + CodeAlignmentSize(thunk_offset + thunk_size),
             method_after_thunk_header_offset);
+  ASSERT_TRUE(IsAligned<kArm64Alignment>(thunk_offset));
   uint32_t diff = thunk_offset - (method1_offset + bl_offset_in_method1);
-  CHECK_ALIGNED(diff, 4u);
+  ASSERT_TRUE(IsAligned<4u>(diff));
   ASSERT_LT(diff, 128 * MB);
   auto expected_code = GenNopsAndBl(0u, kBlPlus0 | (diff >> 2));
   EXPECT_TRUE(CheckLinkedMethod(MethodRef(1u), ArrayRef<const uint8_t>(expected_code)));
@@ -778,7 +781,7 @@
   uint32_t thunk_offset =
       CompiledCode::AlignCode(last_method_offset + last_method_code.size(), InstructionSet::kArm64);
   uint32_t diff = thunk_offset - (last_method_offset + bl_offset_in_last_method);
-  CHECK_ALIGNED(diff, 4u);
+  ASSERT_TRUE(IsAligned<4u>(diff));
   ASSERT_LT(diff, 128 * MB);
   auto expected_code = GenNopsAndBl(1u, kBlPlus0 | (diff >> 2));
   EXPECT_TRUE(CheckLinkedMethod(MethodRef(last_method_idx),
diff --git a/dexlayout/dex_verify.cc b/dexlayout/dex_verify.cc
index 718d66f..83a7c69 100644
--- a/dexlayout/dex_verify.cc
+++ b/dexlayout/dex_verify.cc
@@ -22,6 +22,8 @@
 
 #include <inttypes.h>
 
+#include <set>
+
 #include "android-base/stringprintf.h"
 
 namespace art {
diff --git a/dexlayout/dexdiag.cc b/dexlayout/dexdiag.cc
index 28d4048..ca9018d 100644
--- a/dexlayout/dexdiag.cc
+++ b/dexlayout/dexdiag.cc
@@ -22,12 +22,14 @@
 
 #include <iostream>
 #include <memory>
+#include <string>
+#include <string_view>
 #include <vector>
 
 #include "android-base/stringprintf.h"
 
 #include "base/logging.h"  // For InitLogging.
-#include "base/stringpiece.h"
+#include "base/string_view_cpp20.h"
 
 #include "dexlayout.h"
 #include "dex/dex_file.h"
@@ -462,14 +464,14 @@
   std::vector<std::string> name_filters;
   // TODO: add option to track usage by class name, etc.
   for (int i = 1; i < argc - 1; ++i) {
-    const StringPiece option(argv[i]);
+    const std::string_view option(argv[i]);
     if (option == "--help") {
       Usage(argv[0]);
       return EXIT_SUCCESS;
     } else if (option == "--verbose") {
       g_verbose = true;
-    } else if (option.starts_with("--contains=")) {
-      std::string contains(option.substr(strlen("--contains=")).data());
+    } else if (StartsWith(option, "--contains=")) {
+      std::string contains(option.substr(strlen("--contains=")));
       name_filters.push_back(contains);
     } else {
       Usage(argv[0]);
diff --git a/dexlayout/dexlayout.h b/dexlayout/dexlayout.h
index 535f789..60076bf 100644
--- a/dexlayout/dexlayout.h
+++ b/dexlayout/dexlayout.h
@@ -25,6 +25,8 @@
 
 #include <stdint.h>
 #include <stdio.h>
+
+#include <set>
 #include <unordered_map>
 
 #include "dex/compact_dex_level.h"
diff --git a/dexoptanalyzer/dexoptanalyzer.cc b/dexoptanalyzer/dexoptanalyzer.cc
index 92850f7..988c612 100644
--- a/dexoptanalyzer/dexoptanalyzer.cc
+++ b/dexoptanalyzer/dexoptanalyzer.cc
@@ -15,14 +15,16 @@
  */
 
 #include <string>
+#include <string_view>
 
-#include "base/logging.h"  // For InitLogging.
-#include "base/mutex.h"
-#include "base/os.h"
-#include "base/utils.h"
 #include "android-base/stringprintf.h"
 #include "android-base/strings.h"
 #include "base/file_utils.h"
+#include "base/logging.h"  // For InitLogging.
+#include "base/mutex.h"
+#include "base/os.h"
+#include "base/string_view_cpp20.h"
+#include "base/utils.h"
 #include "compiler_filter.h"
 #include "class_loader_context.h"
 #include "dex/dex_file.h"
@@ -155,56 +157,57 @@
     }
 
     for (int i = 0; i < argc; ++i) {
-      const StringPiece option(argv[i]);
+      const char* raw_option = argv[i];
+      const std::string_view option(raw_option);
       if (option == "--assume-profile-changed") {
         assume_profile_changed_ = true;
-      } else if (option.starts_with("--dex-file=")) {
-        dex_file_ = option.substr(strlen("--dex-file=")).ToString();
-      } else if (option.starts_with("--compiler-filter=")) {
-        std::string filter_str = option.substr(strlen("--compiler-filter=")).ToString();
-        if (!CompilerFilter::ParseCompilerFilter(filter_str.c_str(), &compiler_filter_)) {
-          Usage("Invalid compiler filter '%s'", option.data());
+      } else if (StartsWith(option, "--dex-file=")) {
+        dex_file_ = std::string(option.substr(strlen("--dex-file=")));
+      } else if (StartsWith(option, "--compiler-filter=")) {
+        const char* filter_str = raw_option + strlen("--compiler-filter=");
+        if (!CompilerFilter::ParseCompilerFilter(filter_str, &compiler_filter_)) {
+          Usage("Invalid compiler filter '%s'", raw_option);
         }
-      } else if (option.starts_with("--isa=")) {
-        std::string isa_str = option.substr(strlen("--isa=")).ToString();
-        isa_ = GetInstructionSetFromString(isa_str.c_str());
+      } else if (StartsWith(option, "--isa=")) {
+        const char* isa_str = raw_option + strlen("--isa=");
+        isa_ = GetInstructionSetFromString(isa_str);
         if (isa_ == InstructionSet::kNone) {
-          Usage("Invalid isa '%s'", option.data());
+          Usage("Invalid isa '%s'", raw_option);
         }
-      } else if (option.starts_with("--image=")) {
-        image_ = option.substr(strlen("--image=")).ToString();
+      } else if (StartsWith(option, "--image=")) {
+        image_ = std::string(option.substr(strlen("--image=")));
       } else if (option == "--runtime-arg") {
         if (i + 1 == argc) {
           Usage("Missing argument for --runtime-arg\n");
         }
         ++i;
         runtime_args_.push_back(argv[i]);
-      } else if (option.starts_with("--android-data=")) {
+      } else if (StartsWith(option, "--android-data=")) {
         // Overwrite android-data if needed (oat file assistant relies on a valid directory to
         // compute dalvik-cache folder). This is mostly used in tests.
-        std::string new_android_data = option.substr(strlen("--android-data=")).ToString();
-        setenv("ANDROID_DATA", new_android_data.c_str(), 1);
-      } else if (option.starts_with("--downgrade")) {
+        const char* new_android_data = raw_option + strlen("--android-data=");
+        setenv("ANDROID_DATA", new_android_data, 1);
+      } else if (option == "--downgrade") {
         downgrade_ = true;
-      } else if (option.starts_with("--oat-fd")) {
-        oat_fd_ = std::stoi(option.substr(strlen("--oat-fd=")).ToString(), nullptr, 0);
+      } else if (StartsWith(option, "--oat-fd=")) {
+        oat_fd_ = std::stoi(std::string(option.substr(strlen("--oat-fd="))), nullptr, 0);
         if (oat_fd_ < 0) {
           Usage("Invalid --oat-fd %d", oat_fd_);
         }
-      } else if (option.starts_with("--vdex-fd")) {
-        vdex_fd_ = std::stoi(option.substr(strlen("--vdex-fd=")).ToString(), nullptr, 0);
+      } else if (StartsWith(option, "--vdex-fd=")) {
+        vdex_fd_ = std::stoi(std::string(option.substr(strlen("--vdex-fd="))), nullptr, 0);
         if (vdex_fd_ < 0) {
           Usage("Invalid --vdex-fd %d", vdex_fd_);
         }
-      } else if (option.starts_with("--zip-fd")) {
-          zip_fd_ = std::stoi(option.substr(strlen("--zip-fd=")).ToString(), nullptr, 0);
-          if (zip_fd_ < 0) {
-            Usage("Invalid --zip-fd %d", zip_fd_);
-          }
-      } else if (option.starts_with("--class-loader-context=")) {
-        context_str_ = option.substr(strlen("--class-loader-context=")).ToString();
+      } else if (StartsWith(option, "--zip-fd=")) {
+        zip_fd_ = std::stoi(std::string(option.substr(strlen("--zip-fd="))), nullptr, 0);
+        if (zip_fd_ < 0) {
+          Usage("Invalid --zip-fd %d", zip_fd_);
+        }
+      } else if (StartsWith(option, "--class-loader-context=")) {
+        context_str_ = std::string(option.substr(strlen("--class-loader-context=")));
       } else {
-        Usage("Unknown argument '%s'", option.data());
+        Usage("Unknown argument '%s'", raw_option);
       }
     }
 
diff --git a/imgdiag/imgdiag.cc b/imgdiag/imgdiag.cc
index a1edd00..5cd77e0 100644
--- a/imgdiag/imgdiag.cc
+++ b/imgdiag/imgdiag.cc
@@ -1674,23 +1674,27 @@
  protected:
   using Base = CmdlineArgs;
 
-  ParseStatus ParseCustom(const StringPiece& option, std::string* error_msg) override {
+  ParseStatus ParseCustom(const char* raw_option,
+                          size_t raw_option_length,
+                          std::string* error_msg) override {
+    DCHECK_EQ(strlen(raw_option), raw_option_length);
     {
-      ParseStatus base_parse = Base::ParseCustom(option, error_msg);
+      ParseStatus base_parse = Base::ParseCustom(raw_option, raw_option_length, error_msg);
       if (base_parse != kParseUnknownArgument) {
         return base_parse;
       }
     }
 
-    if (option.starts_with("--image-diff-pid=")) {
-      const char* image_diff_pid = option.substr(strlen("--image-diff-pid=")).data();
+    std::string_view option(raw_option, raw_option_length);
+    if (StartsWith(option, "--image-diff-pid=")) {
+      const char* image_diff_pid = raw_option + strlen("--image-diff-pid=");
 
       if (!android::base::ParseInt(image_diff_pid, &image_diff_pid_)) {
         *error_msg = "Image diff pid out of range";
         return kParseError;
       }
-    } else if (option.starts_with("--zygote-diff-pid=")) {
-      const char* zygote_diff_pid = option.substr(strlen("--zygote-diff-pid=")).data();
+    } else if (StartsWith(option, "--zygote-diff-pid=")) {
+      const char* zygote_diff_pid = raw_option + strlen("--zygote-diff-pid=");
 
       if (!android::base::ParseInt(zygote_diff_pid, &zygote_diff_pid_)) {
         *error_msg = "Zygote diff pid out of range";
diff --git a/libartbase/base/stl_util.h b/libartbase/base/stl_util.h
index 9d944b1..1e071ce 100644
--- a/libartbase/base/stl_util.h
+++ b/libartbase/base/stl_util.h
@@ -18,7 +18,6 @@
 #define ART_LIBARTBASE_BASE_STL_UTIL_H_
 
 #include <algorithm>
-#include <set>
 #include <sstream>
 
 #include <android-base/logging.h>
@@ -136,12 +135,6 @@
   }
 };
 
-// Merge `other` entries into `to_update`.
-template <typename T>
-static inline void MergeSets(std::set<T>& to_update, const std::set<T>& other) {
-  to_update.insert(other.begin(), other.end());
-}
-
 // Returns a copy of the passed vector that doesn't memory-own its entries.
 template <typename T>
 static inline std::vector<T*> MakeNonOwningPointerVector(const std::vector<std::unique_ptr<T>>& src) {
diff --git a/libartbase/base/stringpiece.h b/libartbase/base/stringpiece.h
deleted file mode 100644
index e8cc2c3..0000000
--- a/libartbase/base/stringpiece.h
+++ /dev/null
@@ -1,289 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef ART_LIBARTBASE_BASE_STRINGPIECE_H_
-#define ART_LIBARTBASE_BASE_STRINGPIECE_H_
-
-#include <string.h>
-#include <string>
-
-#include <android-base/logging.h>
-
-namespace art {
-
-// A string-like object that points to a sized piece of memory.
-//
-// Functions or methods may use const StringPiece& parameters to accept either
-// a "const char*" or a "string" value that will be implicitly converted to
-// a StringPiece.  The implicit conversion means that it is often appropriate
-// to include this .h file in other files rather than forward-declaring
-// StringPiece as would be appropriate for most other Google classes.
-class StringPiece {
- public:
-  // standard STL container boilerplate
-  typedef char value_type;
-  typedef const char* pointer;
-  typedef const char& reference;
-  typedef const char& const_reference;
-  typedef size_t size_type;
-  typedef ptrdiff_t difference_type;
-  static constexpr size_type npos = size_type(-1);
-  typedef const char* const_iterator;
-  typedef const char* iterator;
-  typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
-  typedef std::reverse_iterator<iterator> reverse_iterator;
-
-  // We provide non-explicit singleton constructors so users can pass
-  // in a "const char*" or a "string" wherever a "StringPiece" is
-  // expected.
-  StringPiece() : ptr_(nullptr), length_(0) { }
-  StringPiece(const char* str)  // NOLINT implicit constructor desired
-    : ptr_(str), length_((str == nullptr) ? 0 : strlen(str)) { }
-  StringPiece(const std::string& str)  // NOLINT implicit constructor desired
-    : ptr_(str.data()), length_(str.size()) { }
-  StringPiece(const char* offset, size_t len) : ptr_(offset), length_(len) { }
-
-  // data() may return a pointer to a buffer with embedded NULs, and the
-  // returned buffer may or may not be null terminated.  Therefore it is
-  // typically a mistake to pass data() to a routine that expects a NUL
-  // terminated string.
-  const char* data() const { return ptr_; }
-  size_type size() const { return length_; }
-  size_type length() const { return length_; }
-  bool empty() const { return length_ == 0; }
-
-  void clear() {
-    ptr_ = nullptr;
-    length_ = 0;
-  }
-  void set(const char* data_in, size_type len) {
-    ptr_ = data_in;
-    length_ = len;
-  }
-  void set(const char* str) {
-    ptr_ = str;
-    if (str != nullptr) {
-      length_ = strlen(str);
-    } else {
-      length_ = 0;
-    }
-  }
-  void set(const void* data_in, size_type len) {
-    ptr_ = reinterpret_cast<const char*>(data_in);
-    length_ = len;
-  }
-
-  char operator[](size_type i) const {
-    DCHECK_LT(i, length_);
-    return ptr_[i];
-  }
-
-  void remove_prefix(size_type n) {
-    ptr_ += n;
-    length_ -= n;
-  }
-
-  void remove_suffix(size_type n) {
-    length_ -= n;
-  }
-
-  int compare(const StringPiece& x) const {
-    int r = memcmp(ptr_, x.ptr_, std::min(length_, x.length_));
-    if (r == 0) {
-      if (length_ < x.length_) r = -1;
-      else if (length_ > x.length_) r = +1;
-    }
-    return r;
-  }
-
-  std::string as_string() const {
-    return std::string(data(), size());
-  }
-  // We also define ToString() here, since many other string-like
-  // interfaces name the routine that converts to a C++ string
-  // "ToString", and it's confusing to have the method that does that
-  // for a StringPiece be called "as_string()".  We also leave the
-  // "as_string()" method defined here for existing code.
-  std::string ToString() const {
-    return std::string(data(), size());
-  }
-
-  void CopyToString(std::string* target) const {
-    target->assign(ptr_, length_);
-  }
-
-  void AppendToString(std::string* target) const;
-
-  // Does "this" start with "x"
-  bool starts_with(const StringPiece& x) const {
-    return ((length_ >= x.length_) &&
-            (memcmp(ptr_, x.ptr_, x.length_) == 0));
-  }
-
-  // Does "this" end with "x"
-  bool ends_with(const StringPiece& x) const {
-    return ((length_ >= x.length_) &&
-            (memcmp(ptr_ + (length_-x.length_), x.ptr_, x.length_) == 0));
-  }
-
-  iterator begin() const { return ptr_; }
-  iterator end() const { return ptr_ + length_; }
-  const_reverse_iterator rbegin() const {
-    return const_reverse_iterator(ptr_ + length_);
-  }
-  const_reverse_iterator rend() const {
-    return const_reverse_iterator(ptr_);
-  }
-
-  size_type copy(char* buf, size_type n, size_type pos = 0) const {
-    size_type ret = std::min(length_ - pos, n);
-    memcpy(buf, ptr_ + pos, ret);
-    return ret;
-  }
-
-  size_type find(const StringPiece& s, size_type pos = 0) const {
-    if (length_ == 0 || pos > static_cast<size_type>(length_)) {
-      return npos;
-    }
-    const char* result = std::search(ptr_ + pos, ptr_ + length_, s.ptr_, s.ptr_ + s.length_);
-    const size_type xpos = result - ptr_;
-    return xpos + s.length_ <= length_ ? xpos : npos;
-  }
-
-  size_type find(char c, size_type pos = 0) const {
-    if (length_ == 0 || pos >= length_) {
-      return npos;
-    }
-    const char* result = std::find(ptr_ + pos, ptr_ + length_, c);
-    return result != ptr_ + length_ ? result - ptr_ : npos;
-  }
-
-  size_type rfind(const StringPiece& s, size_type pos = npos) const {
-    if (length_ < s.length_) return npos;
-    const size_t ulen = length_;
-    if (s.length_ == 0) return std::min(ulen, pos);
-
-    const char* last = ptr_ + std::min(ulen - s.length_, pos) + s.length_;
-    const char* result = std::find_end(ptr_, last, s.ptr_, s.ptr_ + s.length_);
-    return result != last ? result - ptr_ : npos;
-  }
-
-  size_type rfind(char c, size_type pos = npos) const {
-    if (length_ == 0) return npos;
-    for (int i = std::min(pos, static_cast<size_type>(length_ - 1));
-         i >= 0; --i) {
-      if (ptr_[i] == c) {
-        return i;
-      }
-    }
-    return npos;
-  }
-
-  StringPiece substr(size_type pos, size_type n = npos) const {
-    if (pos > static_cast<size_type>(length_)) pos = length_;
-    if (n > length_ - pos) n = length_ - pos;
-    return StringPiece(ptr_ + pos, n);
-  }
-
-  int Compare(const StringPiece& rhs) const {
-    const int r = memcmp(data(), rhs.data(), std::min(size(), rhs.size()));
-    if (r != 0) {
-      return r;
-    }
-    if (size() < rhs.size()) {
-      return -1;
-    } else if (size() > rhs.size()) {
-      return 1;
-    }
-    return 0;
-  }
-
- private:
-  // Pointer to char data, not necessarily zero terminated.
-  const char* ptr_;
-  // Length of data.
-  size_type length_;
-};
-
-// This large function is defined inline so that in a fairly common case where
-// one of the arguments is a literal, the compiler can elide a lot of the
-// following comparisons.
-inline bool operator==(const StringPiece& x, const StringPiece& y) {
-  StringPiece::size_type len = x.size();
-  if (len != y.size()) {
-    return false;
-  }
-
-  const char* p1 = x.data();
-  const char* p2 = y.data();
-  if (p1 == p2) {
-    return true;
-  }
-  if (len == 0) {
-    return true;
-  }
-
-  // Test last byte in case strings share large common prefix
-  if (p1[len-1] != p2[len-1]) return false;
-  if (len == 1) return true;
-
-  // At this point we can, but don't have to, ignore the last byte.  We use
-  // this observation to fold the odd-length case into the even-length case.
-  len &= ~1;
-
-  return memcmp(p1, p2, len) == 0;
-}
-
-inline bool operator==(const StringPiece& x, const char* y) {
-  if (y == nullptr) {
-    return x.size() == 0;
-  } else {
-    return strncmp(x.data(), y, x.size()) == 0 && y[x.size()] == '\0';
-  }
-}
-
-inline bool operator!=(const StringPiece& x, const StringPiece& y) {
-  return !(x == y);
-}
-
-inline bool operator!=(const StringPiece& x, const char* y) {
-  return !(x == y);
-}
-
-inline bool operator<(const StringPiece& x, const StringPiece& y) {
-  return x.Compare(y) < 0;
-}
-
-inline bool operator>(const StringPiece& x, const StringPiece& y) {
-  return y < x;
-}
-
-inline bool operator<=(const StringPiece& x, const StringPiece& y) {
-  return !(x > y);
-}
-
-inline bool operator>=(const StringPiece& x, const StringPiece& y) {
-  return !(x < y);
-}
-
-inline std::ostream& operator<<(std::ostream& o, const StringPiece& piece) {
-  o.write(piece.data(), piece.size());
-  return o;
-}
-
-}  // namespace art
-
-#endif  // ART_LIBARTBASE_BASE_STRINGPIECE_H_
diff --git a/libartbase/base/utils.cc b/libartbase/base/utils.cc
index b989d9e..30423a4 100644
--- a/libartbase/base/utils.cc
+++ b/libartbase/base/utils.cc
@@ -190,45 +190,6 @@
 #endif
 }
 
-static void ParseStringAfterChar(const std::string& s,
-                                 char c,
-                                 std::string* parsed_value,
-                                 UsageFn Usage) {
-  std::string::size_type colon = s.find(c);
-  if (colon == std::string::npos) {
-    Usage("Missing char %c in option %s\n", c, s.c_str());
-  }
-  // Add one to remove the char we were trimming until.
-  *parsed_value = s.substr(colon + 1);
-}
-
-void ParseDouble(const std::string& option,
-                 char after_char,
-                 double min,
-                 double max,
-                 double* parsed_value,
-                 UsageFn Usage) {
-  std::string substring;
-  ParseStringAfterChar(option, after_char, &substring, Usage);
-  bool sane_val = true;
-  double value;
-  if ((false)) {
-    // TODO: this doesn't seem to work on the emulator.  b/15114595
-    std::stringstream iss(substring);
-    iss >> value;
-    // Ensure that we have a value, there was no cruft after it and it satisfies a sensible range.
-    sane_val = iss.eof() && (value >= min) && (value <= max);
-  } else {
-    char* end = nullptr;
-    value = strtod(substring.c_str(), &end);
-    sane_val = *end == '\0' && value >= min && value <= max;
-  }
-  if (!sane_val) {
-    Usage("Invalid double value %s for option %s\n", substring.c_str(), option.c_str());
-  }
-  *parsed_value = value;
-}
-
 void SleepForever() {
   while (true) {
     usleep(1000000);
diff --git a/libartbase/base/utils.h b/libartbase/base/utils.h
index 11472a8..9284950 100644
--- a/libartbase/base/utils.h
+++ b/libartbase/base/utils.h
@@ -30,7 +30,6 @@
 #include "enums.h"
 #include "globals.h"
 #include "macros.h"
-#include "stringpiece.h"
 
 namespace art {
 
@@ -91,44 +90,6 @@
   return reinterpret_cast<const void*>(code);
 }
 
-using UsageFn = void (*)(const char*, ...);
-
-template <typename T>
-static void ParseIntOption(const StringPiece& option,
-                            const std::string& option_name,
-                            T* out,
-                            UsageFn usage,
-                            bool is_long_option = true) {
-  std::string option_prefix = option_name + (is_long_option ? "=" : "");
-  DCHECK(option.starts_with(option_prefix)) << option << " " << option_prefix;
-  const char* value_string = option.substr(option_prefix.size()).data();
-  int64_t parsed_integer_value = 0;
-  if (!android::base::ParseInt(value_string, &parsed_integer_value)) {
-    usage("Failed to parse %s '%s' as an integer", option_name.c_str(), value_string);
-  }
-  *out = dchecked_integral_cast<T>(parsed_integer_value);
-}
-
-template <typename T>
-static void ParseUintOption(const StringPiece& option,
-                            const std::string& option_name,
-                            T* out,
-                            UsageFn usage,
-                            bool is_long_option = true) {
-  ParseIntOption(option, option_name, out, usage, is_long_option);
-  if (*out < 0) {
-    usage("%s passed a negative value %d", option_name.c_str(), *out);
-    *out = 0;
-  }
-}
-
-void ParseDouble(const std::string& option,
-                 char after_char,
-                 double min,
-                 double max,
-                 double* parsed_value,
-                 UsageFn Usage);
-
 #if defined(__BIONIC__)
 struct Arc4RandomGenerator {
   typedef uint32_t result_type;
diff --git a/libdexfile/Android.bp b/libdexfile/Android.bp
index f83f18c..feb5e38 100644
--- a/libdexfile/Android.bp
+++ b/libdexfile/Android.bp
@@ -228,28 +228,9 @@
         "libdexfile",
     ],
 
-    // TODO(b/120670568): Enable this when linking bug is fixed.
-    // stubs: {
-    //     symbol_file: "external/libdexfile_external.map.txt",
-    //     versions: ["1"],
-    // },
-
-    // Hide symbols using version scripts for targets that support it, i.e. all
-    // but Darwin.
-    // TODO(b/120670568): Clean this up when stubs above is enabled.
-    target: {
-        android: {
-            version_script: "external/libdexfile_external.map.txt",
-        },
-        linux_bionic: {
-            version_script: "external/libdexfile_external.map.txt",
-        },
-        linux_glibc: {
-            version_script: "external/libdexfile_external.map.txt",
-        },
-        windows: {
-            version_script: "external/libdexfile_external.map.txt",
-        },
+    stubs: {
+        symbol_file: "external/libdexfile_external.map.txt",
+        versions: ["1"],
     },
 }
 
diff --git a/oatdump/Android.bp b/oatdump/Android.bp
index 8849a7a..fd0dee3 100644
--- a/oatdump/Android.bp
+++ b/oatdump/Android.bp
@@ -99,6 +99,8 @@
     ],
     // We need this to resolve libartpalette symbols
     // correctly. Multiple source libraries depend on it.
+    // TODO(b/122885634): This is also necessary for the static lib ordering bug
+    // with APEX stubs.
     group_static_libs: true,
 }
 
diff --git a/oatdump/oatdump.cc b/oatdump/oatdump.cc
index 89826c6..655c2c9 100644
--- a/oatdump/oatdump.cc
+++ b/oatdump/oatdump.cc
@@ -3358,20 +3358,24 @@
  protected:
   using Base = CmdlineArgs;
 
-  ParseStatus ParseCustom(const StringPiece& option, std::string* error_msg) override {
+  ParseStatus ParseCustom(const char* raw_option,
+                          size_t raw_option_length,
+                          std::string* error_msg) override {
+    DCHECK_EQ(strlen(raw_option), raw_option_length);
     {
-      ParseStatus base_parse = Base::ParseCustom(option, error_msg);
+      ParseStatus base_parse = Base::ParseCustom(raw_option, raw_option_length, error_msg);
       if (base_parse != kParseUnknownArgument) {
         return base_parse;
       }
     }
 
-    if (option.starts_with("--oat-file=")) {
-      oat_filename_ = option.substr(strlen("--oat-file=")).data();
-    } else if (option.starts_with("--dex-file=")) {
-      dex_filename_ = option.substr(strlen("--dex-file=")).data();
-    } else if (option.starts_with("--image=")) {
-      image_location_ = option.substr(strlen("--image=")).data();
+    std::string_view option(raw_option, raw_option_length);
+    if (StartsWith(option, "--oat-file=")) {
+      oat_filename_ = raw_option + strlen("--oat-file=");
+    } else if (StartsWith(option, "--dex-file=")) {
+      dex_filename_ = raw_option + strlen("--dex-file=");
+    } else if (StartsWith(option, "--image=")) {
+      image_location_ = raw_option + strlen("--image=");
     } else if (option == "--no-dump:vmap") {
       dump_vmap_ = false;
     } else if (option =="--dump:code_info_stack_maps") {
@@ -3380,32 +3384,32 @@
       disassemble_code_ = false;
     } else if (option =="--header-only") {
       dump_header_only_ = true;
-    } else if (option.starts_with("--symbolize=")) {
-      oat_filename_ = option.substr(strlen("--symbolize=")).data();
+    } else if (StartsWith(option, "--symbolize=")) {
+      oat_filename_ = raw_option + strlen("--symbolize=");
       symbolize_ = true;
-    } else if (option.starts_with("--only-keep-debug")) {
+    } else if (StartsWith(option, "--only-keep-debug")) {
       only_keep_debug_ = true;
-    } else if (option.starts_with("--class-filter=")) {
-      class_filter_ = option.substr(strlen("--class-filter=")).data();
-    } else if (option.starts_with("--method-filter=")) {
-      method_filter_ = option.substr(strlen("--method-filter=")).data();
-    } else if (option.starts_with("--list-classes")) {
+    } else if (StartsWith(option, "--class-filter=")) {
+      class_filter_ = raw_option + strlen("--class-filter=");
+    } else if (StartsWith(option, "--method-filter=")) {
+      method_filter_ = raw_option + strlen("--method-filter=");
+    } else if (StartsWith(option, "--list-classes")) {
       list_classes_ = true;
-    } else if (option.starts_with("--list-methods")) {
+    } else if (StartsWith(option, "--list-methods")) {
       list_methods_ = true;
-    } else if (option.starts_with("--export-dex-to=")) {
-      export_dex_location_ = option.substr(strlen("--export-dex-to=")).data();
-    } else if (option.starts_with("--addr2instr=")) {
-      if (!android::base::ParseUint(option.substr(strlen("--addr2instr=")).data(), &addr2instr_)) {
+    } else if (StartsWith(option, "--export-dex-to=")) {
+      export_dex_location_ = raw_option + strlen("--export-dex-to=");
+    } else if (StartsWith(option, "--addr2instr=")) {
+      if (!android::base::ParseUint(raw_option + strlen("--addr2instr="), &addr2instr_)) {
         *error_msg = "Address conversion failed";
         return kParseError;
       }
-    } else if (option.starts_with("--app-image=")) {
-      app_image_ = option.substr(strlen("--app-image=")).data();
-    } else if (option.starts_with("--app-oat=")) {
-      app_oat_ = option.substr(strlen("--app-oat=")).data();
-    } else if (option.starts_with("--dump-imt=")) {
-      imt_dump_ = option.substr(strlen("--dump-imt=")).data();
+    } else if (StartsWith(option, "--app-image=")) {
+      app_image_ = raw_option + strlen("--app-image=");
+    } else if (StartsWith(option, "--app-oat=")) {
+      app_oat_ = raw_option + strlen("--app-oat=");
+    } else if (StartsWith(option, "--dump-imt=")) {
+      imt_dump_ = std::string(option.substr(strlen("--dump-imt=")));
     } else if (option == "--dump-imt-stats") {
       imt_stat_dump_ = true;
     } else {
diff --git a/openjdkjvmti/ti_thread.cc b/openjdkjvmti/ti_thread.cc
index 051db4c..1021648 100644
--- a/openjdkjvmti/ti_thread.cc
+++ b/openjdkjvmti/ti_thread.cc
@@ -47,6 +47,7 @@
 #include "mirror/class.h"
 #include "mirror/object-inl.h"
 #include "mirror/string.h"
+#include "mirror/throwable.h"
 #include "nativehelper/scoped_local_ref.h"
 #include "nativehelper/scoped_utf_chars.h"
 #include "obj_ptr.h"
@@ -83,6 +84,17 @@
   }
 
   void ThreadStart(art::Thread* self) override REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    // Needs to be checked first because we might start these threads before we actually send the
+    // VMInit event.
+    if (self->IsSystemDaemon()) {
+      // System daemon threads are things like the finalizer or gc thread. It would be dangerous to
+      // allow agents to get in the way of these threads starting up. These threads include things
+      // like the HeapTaskDaemon and the finalizer daemon.
+      //
+      // This event can happen during the time before VMInit or just after zygote fork. Since the
+      // second is hard to distinguish we unfortunately cannot really check the state here.
+      return;
+    }
     if (!started) {
       // Runtime isn't started. We only expect at most the signal handler or JIT threads to be
       // started here.
@@ -132,16 +144,35 @@
   gThreadCallback.Post<ArtJvmtiEvent::kThreadStart>(art::Thread::Current());
 }
 
+
+static void WaitForSystemDaemonStart(art::Thread* self) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+  {
+    art::ScopedThreadStateChange strc(self, art::kNative);
+    JNIEnv* jni = self->GetJniEnv();
+    jni->CallStaticVoidMethod(art::WellKnownClasses::java_lang_Daemons,
+                              art::WellKnownClasses::java_lang_Daemons_waitForDaemonStart);
+  }
+  if (self->IsExceptionPending()) {
+    LOG(WARNING) << "Exception occured when waiting for system daemons to start: "
+                 << self->GetException()->Dump();
+    self->ClearException();
+  }
+}
+
 void ThreadUtil::CacheData() {
   // We must have started since it is now safe to cache our data;
   gThreadCallback.started = true;
-  art::ScopedObjectAccess soa(art::Thread::Current());
+  art::Thread* self = art::Thread::Current();
+  art::ScopedObjectAccess soa(self);
   art::ObjPtr<art::mirror::Class> thread_class =
       soa.Decode<art::mirror::Class>(art::WellKnownClasses::java_lang_Thread);
   CHECK(thread_class != nullptr);
   context_class_loader_ = thread_class->FindDeclaredInstanceField("contextClassLoader",
                                                                   "Ljava/lang/ClassLoader;");
   CHECK(context_class_loader_ != nullptr);
+  // Now wait for all required system threads to come up before allowing the rest of loading to
+  // continue.
+  WaitForSystemDaemonStart(self);
 }
 
 void ThreadUtil::Unregister() {
diff --git a/profman/profman.cc b/profman/profman.cc
index 82d9df0..b29e743 100644
--- a/profman/profman.cc
+++ b/profman/profman.cc
@@ -25,6 +25,7 @@
 #include <iostream>
 #include <set>
 #include <string>
+#include <string_view>
 #include <unordered_set>
 #include <vector>
 
@@ -36,7 +37,7 @@
 #include "base/mem_map.h"
 #include "base/scoped_flock.h"
 #include "base/stl_util.h"
-#include "base/stringpiece.h"
+#include "base/string_view_cpp20.h"
 #include "base/time_utils.h"
 #include "base/unix_file/fd_file.h"
 #include "base/utils.h"
@@ -188,6 +189,33 @@
   exit(1);
 }
 
+template <typename T>
+static void ParseUintOption(const char* raw_option,
+                            std::string_view option_prefix,
+                            T* out) {
+  DCHECK(EndsWith(option_prefix, "="));
+  DCHECK(StartsWith(raw_option, option_prefix)) << raw_option << " " << option_prefix;
+  const char* value_string = raw_option + option_prefix.size();
+  int64_t parsed_integer_value = 0;
+  if (!android::base::ParseInt(value_string, &parsed_integer_value)) {
+    std::string option_name(option_prefix.substr(option_prefix.size() - 1u));
+    Usage("Failed to parse %s '%s' as an integer", option_name.c_str(), value_string);
+  }
+  if (parsed_integer_value < 0) {
+    std::string option_name(option_prefix.substr(option_prefix.size() - 1u));
+    Usage("%s passed a negative value %" PRId64, option_name.c_str(), parsed_integer_value);
+  }
+  if (static_cast<uint64_t>(parsed_integer_value) >
+      static_cast<std::make_unsigned_t<T>>(std::numeric_limits<T>::max())) {
+    std::string option_name(option_prefix.substr(option_prefix.size() - 1u));
+    Usage("%s passed a value %" PRIu64 " above max (%" PRIu64 ")",
+          option_name.c_str(),
+          static_cast<uint64_t>(parsed_integer_value),
+          static_cast<uint64_t>(std::numeric_limits<T>::max()));
+  }
+  *out = dchecked_integral_cast<T>(parsed_integer_value);
+}
+
 // TODO(calin): This class has grown too much from its initial design. Split the functionality
 // into smaller, more contained pieces.
 class ProfMan final {
@@ -226,7 +254,8 @@
     }
 
     for (int i = 0; i < argc; ++i) {
-      const StringPiece option(argv[i]);
+      const char* raw_option = argv[i];
+      const std::string_view option(raw_option);
       const bool log_options = false;
       if (log_options) {
         LOG(INFO) << "profman: option[" << i << "]=" << argv[i];
@@ -235,66 +264,60 @@
         dump_only_ = true;
       } else if (option == "--dump-classes-and-methods") {
         dump_classes_and_methods_ = true;
-      } else if (option.starts_with("--create-profile-from=")) {
-        create_profile_from_file_ = option.substr(strlen("--create-profile-from=")).ToString();
-      } else if (option.starts_with("--dump-output-to-fd=")) {
-        ParseUintOption(option, "--dump-output-to-fd", &dump_output_to_fd_, Usage);
+      } else if (StartsWith(option, "--create-profile-from=")) {
+        create_profile_from_file_ = std::string(option.substr(strlen("--create-profile-from=")));
+      } else if (StartsWith(option, "--dump-output-to-fd=")) {
+        ParseUintOption(raw_option, "--dump-output-to-fd=", &dump_output_to_fd_);
       } else if (option == "--generate-boot-image-profile") {
         generate_boot_image_profile_ = true;
-      } else if (option.starts_with("--boot-image-class-threshold=")) {
-        ParseUintOption(option,
-                        "--boot-image-class-threshold",
-                        &boot_image_options_.image_class_theshold,
-                        Usage);
-      } else if (option.starts_with("--boot-image-clean-class-threshold=")) {
-        ParseUintOption(option,
-                        "--boot-image-clean-class-threshold",
-                        &boot_image_options_.image_class_clean_theshold,
-                        Usage);
-      } else if (option.starts_with("--boot-image-sampled-method-threshold=")) {
-        ParseUintOption(option,
-                        "--boot-image-sampled-method-threshold",
-                        &boot_image_options_.compiled_method_threshold,
-                        Usage);
-      } else if (option.starts_with("--profile-file=")) {
-        profile_files_.push_back(option.substr(strlen("--profile-file=")).ToString());
-      } else if (option.starts_with("--profile-file-fd=")) {
-        ParseFdForCollection(option, "--profile-file-fd", &profile_files_fd_);
-      } else if (option.starts_with("--reference-profile-file=")) {
-        reference_profile_file_ = option.substr(strlen("--reference-profile-file=")).ToString();
-      } else if (option.starts_with("--reference-profile-file-fd=")) {
-        ParseUintOption(option, "--reference-profile-file-fd", &reference_profile_file_fd_, Usage);
-      } else if (option.starts_with("--dex-location=")) {
-        dex_locations_.push_back(option.substr(strlen("--dex-location=")).ToString());
-      } else if (option.starts_with("--apk-fd=")) {
-        ParseFdForCollection(option, "--apk-fd", &apks_fd_);
-      } else if (option.starts_with("--apk=")) {
-        apk_files_.push_back(option.substr(strlen("--apk=")).ToString());
-      } else if (option.starts_with("--generate-test-profile=")) {
-        test_profile_ = option.substr(strlen("--generate-test-profile=")).ToString();
-      } else if (option.starts_with("--generate-test-profile-num-dex=")) {
-        ParseUintOption(option,
-                        "--generate-test-profile-num-dex",
-                        &test_profile_num_dex_,
-                        Usage);
-      } else if (option.starts_with("--generate-test-profile-method-percentage")) {
-        ParseUintOption(option,
-                        "--generate-test-profile-method-percentage",
-                        &test_profile_method_percerntage_,
-                        Usage);
-      } else if (option.starts_with("--generate-test-profile-class-percentage")) {
-        ParseUintOption(option,
-                        "--generate-test-profile-class-percentage",
-                        &test_profile_class_percentage_,
-                        Usage);
-      } else if (option.starts_with("--generate-test-profile-seed=")) {
-        ParseUintOption(option, "--generate-test-profile-seed", &test_profile_seed_, Usage);
-      } else if (option.starts_with("--copy-and-update-profile-key")) {
+      } else if (StartsWith(option, "--boot-image-class-threshold=")) {
+        ParseUintOption(raw_option,
+                        "--boot-image-class-threshold=",
+                        &boot_image_options_.image_class_theshold);
+      } else if (StartsWith(option, "--boot-image-clean-class-threshold=")) {
+        ParseUintOption(raw_option,
+                        "--boot-image-clean-class-threshold=",
+                        &boot_image_options_.image_class_clean_theshold);
+      } else if (StartsWith(option, "--boot-image-sampled-method-threshold=")) {
+        ParseUintOption(raw_option,
+                        "--boot-image-sampled-method-threshold=",
+                        &boot_image_options_.compiled_method_threshold);
+      } else if (StartsWith(option, "--profile-file=")) {
+        profile_files_.push_back(std::string(option.substr(strlen("--profile-file="))));
+      } else if (StartsWith(option, "--profile-file-fd=")) {
+        ParseFdForCollection(raw_option, "--profile-file-fd=", &profile_files_fd_);
+      } else if (StartsWith(option, "--reference-profile-file=")) {
+        reference_profile_file_ = std::string(option.substr(strlen("--reference-profile-file=")));
+      } else if (StartsWith(option, "--reference-profile-file-fd=")) {
+        ParseUintOption(raw_option, "--reference-profile-file-fd=", &reference_profile_file_fd_);
+      } else if (StartsWith(option, "--dex-location=")) {
+        dex_locations_.push_back(std::string(option.substr(strlen("--dex-location="))));
+      } else if (StartsWith(option, "--apk-fd=")) {
+        ParseFdForCollection(raw_option, "--apk-fd=", &apks_fd_);
+      } else if (StartsWith(option, "--apk=")) {
+        apk_files_.push_back(std::string(option.substr(strlen("--apk="))));
+      } else if (StartsWith(option, "--generate-test-profile=")) {
+        test_profile_ = std::string(option.substr(strlen("--generate-test-profile=")));
+      } else if (StartsWith(option, "--generate-test-profile-num-dex=")) {
+        ParseUintOption(raw_option,
+                        "--generate-test-profile-num-dex=",
+                        &test_profile_num_dex_);
+      } else if (StartsWith(option, "--generate-test-profile-method-percentage=")) {
+        ParseUintOption(raw_option,
+                        "--generate-test-profile-method-percentage=",
+                        &test_profile_method_percerntage_);
+      } else if (StartsWith(option, "--generate-test-profile-class-percentage=")) {
+        ParseUintOption(raw_option,
+                        "--generate-test-profile-class-percentage=",
+                        &test_profile_class_percentage_);
+      } else if (StartsWith(option, "--generate-test-profile-seed=")) {
+        ParseUintOption(raw_option, "--generate-test-profile-seed=", &test_profile_seed_);
+      } else if (option == "--copy-and-update-profile-key") {
         copy_and_update_profile_key_ = true;
-      } else if (option.starts_with("--store-aggregation-counters")) {
+      } else if (option == "--store-aggregation-counters") {
         store_aggregation_counters_ = true;
       } else {
-        Usage("Unknown argument '%s'", option.data());
+        Usage("Unknown argument '%s'", raw_option);
       }
     }
 
@@ -1265,11 +1288,11 @@
   }
 
  private:
-  static void ParseFdForCollection(const StringPiece& option,
-                                   const char* arg_name,
+  static void ParseFdForCollection(const char* raw_option,
+                                   std::string_view option_prefix,
                                    std::vector<int>* fds) {
     int fd;
-    ParseUintOption(option, arg_name, &fd, Usage);
+    ParseUintOption(raw_option, option_prefix, &fd);
     fds->push_back(fd);
   }
 
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index 2e1f364..148fdba 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -1449,6 +1449,7 @@
 
   static void UpdateInternStrings(
       gc::space::ImageSpace* space,
+      bool use_preresolved_strings,
       const SafeMap<mirror::String*, mirror::String*>& intern_remap)
       REQUIRES_SHARED(Locks::mutator_lock_);
 };
@@ -1464,8 +1465,10 @@
   ScopedTrace app_image_timing("AppImage:Updating");
 
   Thread* const self = Thread::Current();
-  gc::Heap* const heap = Runtime::Current()->GetHeap();
+  Runtime* const runtime = Runtime::Current();
+  gc::Heap* const heap = runtime->GetHeap();
   const ImageHeader& header = space->GetImageHeader();
+  bool load_app_image_startup_cache = runtime->LoadAppImageStartupCache();
   {
     // Register dex caches with the class loader.
     WriterMutexLock mu(self, *Locks::classlinker_classes_lock_);
@@ -1479,6 +1482,10 @@
         class_linker->RegisterDexFileLocked(*dex_file, dex_cache, class_loader.Get());
       }
 
+      if (!load_app_image_startup_cache) {
+        dex_cache->ClearPreResolvedStrings();
+      }
+
       if (kIsDebugBuild) {
         CHECK(new_class_set != nullptr);
         mirror::TypeDexCacheType* const types = dex_cache->GetResolvedTypes();
@@ -1545,11 +1552,13 @@
 
 void AppImageLoadingHelper::UpdateInternStrings(
     gc::space::ImageSpace* space,
+    bool use_preresolved_strings,
     const SafeMap<mirror::String*, mirror::String*>& intern_remap) {
   const uint8_t* target_base = space->Begin();
   const ImageSection& sro_section =
       space->GetImageHeader().GetImageStringReferenceOffsetsSection();
   const size_t num_string_offsets = sro_section.Size() / sizeof(AppImageReferenceOffsetInfo);
+  InternTable* const intern_table = Runtime::Current()->GetInternTable();
 
   VLOG(image)
       << "ClassLinker:AppImage:InternStrings:imageStringReferenceOffsetCount = "
@@ -1582,24 +1591,29 @@
         WriteBarrier::ForEveryFieldWrite(dex_cache);
         dex_cache->GetStrings()[string_index].store(
             mirror::StringDexCachePair(it->second, source.index));
+      } else if (!use_preresolved_strings) {
+        dex_cache->GetStrings()[string_index].store(
+            mirror::StringDexCachePair(intern_table->InternStrong(referred_string), source.index));
       }
     } else if (HasDexCachePreResolvedStringNativeRefTag(base_offset)) {
-      base_offset = ClearDexCacheNativeRefTags(base_offset);
-      DCHECK_ALIGNED(base_offset, 2);
+      if (use_preresolved_strings) {
+        base_offset = ClearDexCacheNativeRefTags(base_offset);
+        DCHECK_ALIGNED(base_offset, 2);
 
-      ObjPtr<mirror::DexCache> dex_cache =
-          reinterpret_cast<mirror::DexCache*>(space->Begin() + base_offset);
-      uint32_t string_index = sro_base[offset_index].second;
+        ObjPtr<mirror::DexCache> dex_cache =
+            reinterpret_cast<mirror::DexCache*>(space->Begin() + base_offset);
+        uint32_t string_index = sro_base[offset_index].second;
 
-      ObjPtr<mirror::String> referred_string =
-          dex_cache->GetPreResolvedStrings()[string_index].Read();
-      DCHECK(referred_string != nullptr);
+        ObjPtr<mirror::String> referred_string =
+            dex_cache->GetPreResolvedStrings()[string_index].Read();
+        DCHECK(referred_string != nullptr);
 
-      auto it = intern_remap.find(referred_string.Ptr());
-      if (it != intern_remap.end()) {
-        // Because we are not using a helper function we need to mark the GC card manually.
-        WriteBarrier::ForEveryFieldWrite(dex_cache);
-        dex_cache->GetPreResolvedStrings()[string_index] = GcRoot<mirror::String>(it->second);
+        auto it = intern_remap.find(referred_string.Ptr());
+        if (it != intern_remap.end()) {
+          // Because we are not using a helper function we need to mark the GC card manually.
+          WriteBarrier::ForEveryFieldWrite(dex_cache);
+          dex_cache->GetPreResolvedStrings()[string_index] = GcRoot<mirror::String>(it->second);
+        }
       }
     } else {
       uint32_t raw_member_offset = sro_base[offset_index].second;
@@ -1622,6 +1636,13 @@
                                 /* kCheckTransaction= */ false,
                                 kVerifyNone,
                                 /* kIsVolatile= */ false>(member_offset, it->second);
+      } else if (!use_preresolved_strings) {
+        obj_ptr->SetFieldObject</* kTransactionActive= */ false,
+                                /* kCheckTransaction= */ false,
+                                kVerifyNone,
+                                /* kIsVolatile= */ false>(
+            member_offset,
+            intern_table->InternStrong(referred_string));
       }
     }
   }
@@ -1632,13 +1653,16 @@
   // the strings they point to.
   ScopedTrace timing("AppImage:InternString");
 
-  InternTable* const intern_table = Runtime::Current()->GetInternTable();
+  Runtime* const runtime = Runtime::Current();
+  InternTable* const intern_table = runtime->GetInternTable();
+
+  const bool load_startup_cache = runtime->LoadAppImageStartupCache();
 
   // Add the intern table, removing any conflicts. For conflicts, store the new address in a map
   // for faster lookup.
   // TODO: Optimize with a bitmap or bloom filter
   SafeMap<mirror::String*, mirror::String*> intern_remap;
-  intern_table->AddImageStringsToTable(space, [&](InternTable::UnorderedSet& interns)
+  auto func = [&](InternTable::UnorderedSet& interns)
       REQUIRES_SHARED(Locks::mutator_lock_)
       REQUIRES(Locks::intern_table_lock_) {
     const size_t non_boot_image_strings = intern_table->CountInterns(
@@ -1681,14 +1705,23 @@
         CHECK(intern_table->LookupStrongLocked(string) == nullptr) << string->ToModifiedUtf8();
       }
     }
-  });
+  };
 
-  VLOG(image) << "AppImage:conflictingInternStrings = " << intern_remap.size();
+  bool update_intern_strings;
+  if (load_startup_cache) {
+    // Only add the intern table if we are using the startup cache. Otherwise,
+    // UpdateInternStrings adds the strings to the intern table.
+    intern_table->AddImageStringsToTable(space, func);
+    update_intern_strings = kIsDebugBuild || !intern_remap.empty();
+    VLOG(image) << "AppImage:conflictingInternStrings = " << intern_remap.size();
+  } else {
+    update_intern_strings = true;
+  }
 
   // For debug builds, always run the code below to get coverage.
-  if (kIsDebugBuild || !intern_remap.empty()) {
+  if (update_intern_strings) {
     // Slow path case is when there are conflicting intern strings to fix up.
-    UpdateInternStrings(space, intern_remap);
+    UpdateInternStrings(space, /*use_preresolved_strings=*/ load_startup_cache, intern_remap);
   }
 }
 
diff --git a/runtime/class_linker_test.cc b/runtime/class_linker_test.cc
index c9bdbda..91cea79 100644
--- a/runtime/class_linker_test.cc
+++ b/runtime/class_linker_test.cc
@@ -409,7 +409,7 @@
 
   void AssertDexFileClass(ObjPtr<mirror::ClassLoader> class_loader, const std::string& descriptor)
       REQUIRES_SHARED(Locks::mutator_lock_) {
-    ASSERT_TRUE(descriptor != nullptr);
+    ASSERT_FALSE(descriptor.empty());
     Thread* self = Thread::Current();
     StackHandleScope<1> hs(self);
     Handle<mirror::Class> klass(
diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc
index 5473b52..8335799 100644
--- a/runtime/gc/heap.cc
+++ b/runtime/gc/heap.cc
@@ -3999,7 +3999,7 @@
 // Return the ratio of the weighted native + java allocated bytes to its target value.
 // A return value > 1.0 means we should collect. Significantly larger values mean we're falling
 // behind.
-inline float Heap::NativeMemoryOverTarget(size_t current_native_bytes) {
+inline float Heap::NativeMemoryOverTarget(size_t current_native_bytes, bool is_gc_concurrent) {
   // Collection check for native allocation. Does not enforce Java heap bounds.
   // With adj_start_bytes defined below, effectively checks
   // <java bytes allocd> + c1*<old native allocd> + c2*<new native allocd) >= adj_start_bytes,
@@ -4016,17 +4016,22 @@
         + old_native_bytes / kOldNativeDiscountFactor;
     size_t add_bytes_allowed = static_cast<size_t>(
         NativeAllocationGcWatermark() * HeapGrowthMultiplier());
-    size_t adj_start_bytes = concurrent_start_bytes_ + add_bytes_allowed / kNewNativeDiscountFactor;
+    size_t java_gc_start_bytes = is_gc_concurrent
+        ? concurrent_start_bytes_
+        : target_footprint_.load(std::memory_order_relaxed);
+    size_t adj_start_bytes = UnsignedSum(java_gc_start_bytes,
+                                         add_bytes_allowed / kNewNativeDiscountFactor);
     return static_cast<float>(GetBytesAllocated() + weighted_native_bytes)
          / static_cast<float>(adj_start_bytes);
   }
 }
 
-inline void Heap::CheckConcurrentGCForNative(Thread* self) {
+inline void Heap::CheckGCForNative(Thread* self) {
+  bool is_gc_concurrent = IsGcConcurrent();
   size_t current_native_bytes = GetNativeBytes();
-  float gc_urgency = NativeMemoryOverTarget(current_native_bytes);
+  float gc_urgency = NativeMemoryOverTarget(current_native_bytes, is_gc_concurrent);
   if (UNLIKELY(gc_urgency >= 1.0)) {
-    if (IsGcConcurrent()) {
+    if (is_gc_concurrent) {
       RequestConcurrentGC(self, kGcCauseForNativeAlloc, /*force_full=*/true);
       if (gc_urgency > kStopForNativeFactor
           && current_native_bytes > kHugeNativeAllocs) {
@@ -4045,7 +4050,7 @@
 // About kNotifyNativeInterval allocations have occurred. Check whether we should garbage collect.
 void Heap::NotifyNativeAllocations(JNIEnv* env) {
   native_objects_notified_.fetch_add(kNotifyNativeInterval, std::memory_order_relaxed);
-  CheckConcurrentGCForNative(ThreadForEnv(env));
+  CheckGCForNative(ThreadForEnv(env));
 }
 
 // Register a native allocation with an explicit size.
@@ -4057,7 +4062,7 @@
       native_objects_notified_.fetch_add(1, std::memory_order_relaxed);
   if (objects_notified % kNotifyNativeInterval == kNotifyNativeInterval - 1
       || bytes > kCheckImmediatelyThreshold) {
-    CheckConcurrentGCForNative(ThreadForEnv(env));
+    CheckGCForNative(ThreadForEnv(env));
   }
 }
 
diff --git a/runtime/gc/heap.h b/runtime/gc/heap.h
index 6bdba12..18dfbf5 100644
--- a/runtime/gc/heap.h
+++ b/runtime/gc/heap.h
@@ -917,9 +917,13 @@
     return main_space_backup_ != nullptr;
   }
 
+  // Size_t saturating arithmetic
   static ALWAYS_INLINE size_t UnsignedDifference(size_t x, size_t y) {
     return x > y ? x - y : 0;
   }
+  static ALWAYS_INLINE size_t UnsignedSum(size_t x, size_t y) {
+    return x + y >= x ? x + y : std::numeric_limits<size_t>::max();
+  }
 
   static ALWAYS_INLINE bool AllocatorHasAllocationStack(AllocatorType allocator_type) {
     return
@@ -950,13 +954,13 @@
 
   // Checks whether we should garbage collect:
   ALWAYS_INLINE bool ShouldConcurrentGCForJava(size_t new_num_bytes_allocated);
-  float NativeMemoryOverTarget(size_t current_native_bytes);
+  float NativeMemoryOverTarget(size_t current_native_bytes, bool is_gc_concurrent);
   ALWAYS_INLINE void CheckConcurrentGCForJava(Thread* self,
                                               size_t new_num_bytes_allocated,
                                               ObjPtr<mirror::Object>* obj)
       REQUIRES_SHARED(Locks::mutator_lock_)
       REQUIRES(!*pending_task_lock_, !*gc_complete_lock_);
-  void CheckConcurrentGCForNative(Thread* self)
+  void CheckGCForNative(Thread* self)
       REQUIRES(!*pending_task_lock_, !*gc_complete_lock_);
 
   accounting::ObjectStack* GetMarkStack() {
diff --git a/runtime/mirror/dex_cache-inl.h b/runtime/mirror/dex_cache-inl.h
index 47b621a..f0ad931 100644
--- a/runtime/mirror/dex_cache-inl.h
+++ b/runtime/mirror/dex_cache-inl.h
@@ -110,8 +110,7 @@
   WriteBarrier::ForEveryFieldWrite(this);
 }
 
-inline void DexCache::SetPreResolvedString(dex::StringIndex string_idx,
-                                           ObjPtr<String> resolved) {
+inline void DexCache::SetPreResolvedString(dex::StringIndex string_idx, ObjPtr<String> resolved) {
   DCHECK(resolved != nullptr);
   DCHECK_LT(string_idx.index_, GetDexFile()->NumStringIds());
   GetPreResolvedStrings()[string_idx.index_] = GcRoot<mirror::String>(resolved);
@@ -122,6 +121,17 @@
   WriteBarrier::ForEveryFieldWrite(this);
 }
 
+inline void DexCache::ClearPreResolvedStrings() {
+  SetFieldPtr64</*kTransactionActive=*/false,
+                /*kCheckTransaction=*/false,
+                kVerifyNone,
+                GcRoot<mirror::String>*>(PreResolvedStringsOffset(), nullptr);
+  SetField32</*kTransactionActive=*/false,
+             /*bool kCheckTransaction=*/false,
+             kVerifyNone,
+             /*kIsVolatile=*/false>(NumPreResolvedStringsOffset(), 0);
+}
+
 inline void DexCache::ClearString(dex::StringIndex string_idx) {
   DCHECK(Runtime::Current()->IsAotCompiler());
   uint32_t slot_idx = StringSlotIndex(string_idx);
diff --git a/runtime/mirror/dex_cache.h b/runtime/mirror/dex_cache.h
index c742928..b5619f8 100644
--- a/runtime/mirror/dex_cache.h
+++ b/runtime/mirror/dex_cache.h
@@ -283,6 +283,11 @@
                             ObjPtr<mirror::String> resolved)
       ALWAYS_INLINE REQUIRES_SHARED(Locks::mutator_lock_);
 
+  // Clear the preresolved string cache to prevent further usage. Not thread safe, so should only
+  // be called when the string cache is guaranteed to not be accessed.
+  void ClearPreResolvedStrings()
+      ALWAYS_INLINE REQUIRES_SHARED(Locks::mutator_lock_);
+
   // Clear a string for a string_idx, used to undo string intern transactions to make sure
   // the string isn't kept live.
   void ClearString(dex::StringIndex string_idx) REQUIRES_SHARED(Locks::mutator_lock_);
diff --git a/runtime/native/dalvik_system_ZygoteHooks.cc b/runtime/native/dalvik_system_ZygoteHooks.cc
index 26fc5e9..891ecef 100644
--- a/runtime/native/dalvik_system_ZygoteHooks.cc
+++ b/runtime/native/dalvik_system_ZygoteHooks.cc
@@ -149,6 +149,7 @@
   HIDDEN_API_ENFORCEMENT_POLICY_MASK = (1 << 12)
                                      | (1 << 13),
   PROFILE_SYSTEM_SERVER              = 1 << 14,
+  USE_APP_IMAGE_STARTUP_CACHE        = 1 << 16,
 
   // bits to shift (flags & HIDDEN_API_ENFORCEMENT_POLICY_MASK) by to get a value
   // corresponding to hiddenapi::EnforcementPolicy
@@ -298,6 +299,10 @@
   bool profile_system_server = (runtime_flags & PROFILE_SYSTEM_SERVER) == PROFILE_SYSTEM_SERVER;
   runtime_flags &= ~PROFILE_SYSTEM_SERVER;
 
+  Runtime::Current()->SetLoadAppImageStartupCacheEnabled(
+      (runtime_flags & USE_APP_IMAGE_STARTUP_CACHE) != 0u);
+  runtime_flags &= ~USE_APP_IMAGE_STARTUP_CACHE;
+
   if (runtime_flags != 0) {
     LOG(ERROR) << StringPrintf("Unknown bits set in runtime_flags: %#x", runtime_flags);
   }
diff --git a/runtime/oat.h b/runtime/oat.h
index 88238d9..b824729 100644
--- a/runtime/oat.h
+++ b/runtime/oat.h
@@ -31,8 +31,8 @@
 class PACKED(4) OatHeader {
  public:
   static constexpr uint8_t kOatMagic[] = { 'o', 'a', 't', '\n' };
-  // Last oat version changed reason: Partial boot image.
-  static constexpr uint8_t kOatVersion[] = { '1', '6', '6', '\0' };
+  // Last oat version changed reason: Add code size to CodeInfo.
+  static constexpr uint8_t kOatVersion[] = { '1', '6', '7', '\0' };
 
   static constexpr const char* kDex2OatCmdLineKey = "dex2oat-cmdline";
   static constexpr const char* kDebuggableKey = "debuggable";
diff --git a/runtime/oat_quick_method_header.h b/runtime/oat_quick_method_header.h
index 8798c69..6c123c4 100644
--- a/runtime/oat_quick_method_header.h
+++ b/runtime/oat_quick_method_header.h
@@ -35,6 +35,8 @@
                        uint32_t code_size)
       : vmap_table_offset_(vmap_table_offset),
         code_size_(code_size) {
+    DCHECK_NE(vmap_table_offset, 0u);
+    DCHECK_NE(code_size, 0u);
   }
 
   static OatQuickMethodHeader* FromCodePointer(const void* code_ptr) {
@@ -58,7 +60,7 @@
   }
 
   bool IsOptimized() const {
-    return GetCodeSize() != 0 && vmap_table_offset_ != 0;
+    return (code_size_ & kCodeSizeMask) != 0 && vmap_table_offset_ != 0;
   }
 
   const uint8_t* GetOptimizedCodeInfoPtr() const {
@@ -76,7 +78,11 @@
   }
 
   uint32_t GetCodeSize() const {
-    return code_size_ & kCodeSizeMask;
+    DCHECK(IsOptimized());
+    size_t code_size1 = code_size_ & kCodeSizeMask;
+    size_t code_size2 = CodeInfo::DecodeCodeSize(GetOptimizedCodeInfoPtr());
+    DCHECK_EQ(code_size1, code_size2);
+    return code_size2;
   }
 
   const uint32_t* GetCodeSizeAddr() const {
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index 1465b14..7ecfdc7 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -869,15 +869,22 @@
                             GetInstructionSetString(kRuntimeISA));
   }
 
-  // Send the initialized phase event. Send it before starting daemons, as otherwise
-  // sending thread events becomes complicated.
+  StartDaemonThreads();
+
+  // Make sure the environment is still clean (no lingering local refs from starting daemon
+  // threads).
+  {
+    ScopedObjectAccess soa(self);
+    self->GetJniEnv()->AssertLocalsEmpty();
+  }
+
+  // Send the initialized phase event. Send it after starting the Daemon threads so that agents
+  // cannot delay the daemon threads from starting forever.
   {
     ScopedObjectAccess soa(self);
     callbacks_->NextRuntimePhase(RuntimePhaseCallback::RuntimePhase::kInit);
   }
 
-  StartDaemonThreads();
-
   {
     ScopedObjectAccess soa(self);
     self->GetJniEnv()->AssertLocalsEmpty();
diff --git a/runtime/runtime.h b/runtime/runtime.h
index ee2c514..81c17a5 100644
--- a/runtime/runtime.h
+++ b/runtime/runtime.h
@@ -823,6 +823,14 @@
     ThreadPool* const thread_pool_;
   };
 
+  bool LoadAppImageStartupCache() const {
+    return load_app_image_startup_cache_;
+  }
+
+  void SetLoadAppImageStartupCacheEnabled(bool enabled) {
+    load_app_image_startup_cache_ = enabled;
+  }
+
  private:
   static void InitPlatformSignalHandlers();
 
@@ -1145,6 +1153,8 @@
 
   uint32_t verifier_logging_threshold_ms_;
 
+  bool load_app_image_startup_cache_ = false;
+
   // Note: See comments on GetFaultMessage.
   friend std::string GetFaultMessageForAbortLogging();
   friend class ScopedThreadPoolUsage;
diff --git a/runtime/stack_map.cc b/runtime/stack_map.cc
index 62dec15..5d30b77 100644
--- a/runtime/stack_map.cc
+++ b/runtime/stack_map.cc
@@ -227,6 +227,7 @@
                     bool verbose,
                     InstructionSet instruction_set) const {
   vios->Stream() << "CodeInfo BitSize=" << size_in_bits_
+    << " CodeSize:" << StackMap::UnpackNativePc(packed_code_size_, instruction_set)
     << " FrameSize:" << packed_frame_size_ * kStackAlignment
     << " CoreSpillMask:" << std::hex << core_spill_mask_
     << " FpSpillMask:" << std::hex << fp_spill_mask_
diff --git a/runtime/stack_map.h b/runtime/stack_map.h
index 87133cf..59da923 100644
--- a/runtime/stack_map.h
+++ b/runtime/stack_map.h
@@ -438,8 +438,15 @@
   // Accumulate code info size statistics into the given Stats tree.
   static void CollectSizeStats(const uint8_t* code_info, /*out*/ Stats* parent);
 
+  ALWAYS_INLINE static size_t DecodeCodeSize(const uint8_t* data,
+                                             InstructionSet isa = kRuntimeISA) {
+    uint32_t packed_code_size = BitMemoryReader(data).ReadVarint();
+    return StackMap::UnpackNativePc(packed_code_size, isa);
+  }
+
   ALWAYS_INLINE static QuickMethodFrameInfo DecodeFrameInfo(const uint8_t* data) {
     BitMemoryReader reader(data);
+    reader.ReadVarint();  // Skip code size.
     return QuickMethodFrameInfo(
         reader.ReadVarint() * kStackAlignment,  // Decode packed_frame_size_ and unpack.
         reader.ReadVarint(),  // core_spill_mask_.
@@ -461,6 +468,7 @@
   // Invokes the callback with member pointer of each header field.
   template<typename Callback>
   ALWAYS_INLINE static void ForEachHeaderField(Callback callback) {
+    callback(&CodeInfo::packed_code_size_);
     callback(&CodeInfo::packed_frame_size_);
     callback(&CodeInfo::core_spill_mask_);
     callback(&CodeInfo::fp_spill_mask_);
@@ -486,6 +494,7 @@
     callback(&CodeInfo::dex_register_catalog_);
   }
 
+  uint32_t packed_code_size_ = 0;  // The size of native PC range.
   uint32_t packed_frame_size_ = 0;  // Frame size in kStackAlignment units.
   uint32_t core_spill_mask_ = 0;
   uint32_t fp_spill_mask_ = 0;
diff --git a/runtime/thread.cc b/runtime/thread.cc
index 4828aae..44b45cf 100644
--- a/runtime/thread.cc
+++ b/runtime/thread.cc
@@ -4258,4 +4258,12 @@
   return priority;
 }
 
+bool Thread::IsSystemDaemon() const {
+  if (GetPeer() == nullptr) {
+    return false;
+  }
+  return jni::DecodeArtField(
+      WellKnownClasses::java_lang_Thread_systemDaemon)->GetBoolean(GetPeer());
+}
+
 }  // namespace art
diff --git a/runtime/thread.h b/runtime/thread.h
index 7a14fd7..ec276b5 100644
--- a/runtime/thread.h
+++ b/runtime/thread.h
@@ -1230,6 +1230,8 @@
     return this == jit_sensitive_thread_;
   }
 
+  bool IsSystemDaemon() const REQUIRES_SHARED(Locks::mutator_lock_);
+
   // Returns true if StrictMode events are traced for the current thread.
   static bool IsSensitiveThread() {
     if (is_sensitive_thread_hook_ != nullptr) {
diff --git a/runtime/verifier/method_verifier_test.cc b/runtime/verifier/method_verifier_test.cc
index 36890a6..09171a4 100644
--- a/runtime/verifier/method_verifier_test.cc
+++ b/runtime/verifier/method_verifier_test.cc
@@ -35,7 +35,7 @@
  protected:
   void VerifyClass(const std::string& descriptor)
       REQUIRES_SHARED(Locks::mutator_lock_) {
-    ASSERT_TRUE(descriptor != nullptr);
+    ASSERT_FALSE(descriptor.empty());
     Thread* self = Thread::Current();
     ObjPtr<mirror::Class> klass = class_linker_->FindSystemClass(self, descriptor.c_str());
 
diff --git a/runtime/verifier/verifier_deps.cc b/runtime/verifier/verifier_deps.cc
index b758159..6793a0a 100644
--- a/runtime/verifier/verifier_deps.cc
+++ b/runtime/verifier/verifier_deps.cc
@@ -17,14 +17,15 @@
 #include "verifier_deps.h"
 
 #include <cstring>
+#include <sstream>
 
 #include "art_field-inl.h"
 #include "art_method-inl.h"
 #include "base/indenter.h"
 #include "base/leb128.h"
 #include "base/mutex-inl.h"
-#include "base/stl_util.h"
 #include "compiler_callbacks.h"
+#include "dex/class_accessor-inl.h"
 #include "dex/dex_file-inl.h"
 #include "mirror/class-inl.h"
 #include "mirror/class_loader.h"
@@ -46,21 +47,22 @@
 VerifierDeps::VerifierDeps(const std::vector<const DexFile*>& dex_files)
     : VerifierDeps(dex_files, /*output_only=*/ true) {}
 
-void VerifierDeps::MergeWith(const VerifierDeps& other,
+void VerifierDeps::MergeWith(std::unique_ptr<VerifierDeps> other,
                              const std::vector<const DexFile*>& dex_files) {
-  DCHECK(dex_deps_.size() == other.dex_deps_.size());
+  DCHECK(other != nullptr);
+  DCHECK_EQ(dex_deps_.size(), other->dex_deps_.size());
   for (const DexFile* dex_file : dex_files) {
     DexFileDeps* my_deps = GetDexFileDeps(*dex_file);
-    const DexFileDeps& other_deps = *other.GetDexFileDeps(*dex_file);
+    DexFileDeps& other_deps = *other->GetDexFileDeps(*dex_file);
     // We currently collect extra strings only on the main `VerifierDeps`,
     // which should be the one passed as `this` in this method.
     DCHECK(other_deps.strings_.empty());
-    MergeSets(my_deps->assignable_types_, other_deps.assignable_types_);
-    MergeSets(my_deps->unassignable_types_, other_deps.unassignable_types_);
-    MergeSets(my_deps->classes_, other_deps.classes_);
-    MergeSets(my_deps->fields_, other_deps.fields_);
-    MergeSets(my_deps->methods_, other_deps.methods_);
-    MergeSets(my_deps->unverified_classes_, other_deps.unverified_classes_);
+    my_deps->assignable_types_.merge(other_deps.assignable_types_);
+    my_deps->unassignable_types_.merge(other_deps.unassignable_types_);
+    my_deps->classes_.merge(other_deps.classes_);
+    my_deps->fields_.merge(other_deps.fields_);
+    my_deps->methods_.merge(other_deps.methods_);
+    my_deps->unverified_classes_.merge(other_deps.unverified_classes_);
   }
 }
 
@@ -690,6 +692,12 @@
   }
 }
 
+static inline std::string ToHex(uint32_t value) {
+  std::stringstream ss;
+  ss << std::hex << value << std::dec;
+  return ss.str();
+}
+
 }  // namespace
 
 void VerifierDeps::Encode(const std::vector<const DexFile*>& dex_files,
@@ -849,9 +857,10 @@
 }
 
 bool VerifierDeps::ValidateDependencies(Handle<mirror::ClassLoader> class_loader,
-                                        Thread* self) const {
+                                        Thread* self,
+                                        /* out */ std::string* error_msg) const {
   for (const auto& entry : dex_deps_) {
-    if (!VerifyDexFile(class_loader, *entry.first, *entry.second, self)) {
+    if (!VerifyDexFile(class_loader, *entry.first, *entry.second, self, error_msg)) {
       return false;
     }
   }
@@ -862,10 +871,10 @@
 // the same lookup pattern.
 static ObjPtr<mirror::Class> FindClassAndClearException(ClassLinker* class_linker,
                                                         Thread* self,
-                                                        const char* name,
+                                                        const std::string& name,
                                                         Handle<mirror::ClassLoader> class_loader)
     REQUIRES_SHARED(Locks::mutator_lock_) {
-  ObjPtr<mirror::Class> result = class_linker->FindClass(self, name, class_loader);
+  ObjPtr<mirror::Class> result = class_linker->FindClass(self, name.c_str(), class_loader);
   if (result == nullptr) {
     DCHECK(self->IsExceptionPending());
     self->ClearException();
@@ -877,7 +886,8 @@
                                        const DexFile& dex_file,
                                        const std::set<TypeAssignability>& assignables,
                                        bool expected_assignability,
-                                       Thread* self) const {
+                                       Thread* self,
+                                       /* out */ std::string* error_msg) const {
   StackHandleScope<2> hs(self);
   ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
   MutableHandle<mirror::Class> source(hs.NewHandle<mirror::Class>(nullptr));
@@ -892,22 +902,19 @@
         FindClassAndClearException(class_linker, self, source_desc.c_str(), class_loader));
 
     if (destination == nullptr) {
-      LOG(INFO) << "VerifiersDeps: Could not resolve class " << destination_desc;
+      *error_msg = "Could not resolve class " + destination_desc;
       return false;
     }
 
     if (source == nullptr) {
-      LOG(INFO) << "VerifierDeps: Could not resolve class " << source_desc;
+      *error_msg = "Could not resolve class " + source_desc;
       return false;
     }
 
     DCHECK(destination->IsResolved() && source->IsResolved());
     if (destination->IsAssignableFrom(source.Get()) != expected_assignability) {
-      LOG(INFO) << "VerifierDeps: Class "
-                << destination_desc
-                << (expected_assignability ? " not " : " ")
-                << "assignable from "
-                << source_desc;
+      *error_msg = "Class " + destination_desc + (expected_assignability ? " not " : " ") +
+          "assignable from " + source_desc;
       return false;
     }
   }
@@ -917,31 +924,27 @@
 bool VerifierDeps::VerifyClasses(Handle<mirror::ClassLoader> class_loader,
                                  const DexFile& dex_file,
                                  const std::set<ClassResolution>& classes,
-                                 Thread* self) const {
+                                 Thread* self,
+                                 /* out */ std::string* error_msg) const {
   StackHandleScope<1> hs(self);
   ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
   MutableHandle<mirror::Class> cls(hs.NewHandle<mirror::Class>(nullptr));
   for (const auto& entry : classes) {
-    const char* descriptor = dex_file.StringByTypeIdx(entry.GetDexTypeIndex());
+    std::string descriptor = dex_file.StringByTypeIdx(entry.GetDexTypeIndex());
     cls.Assign(FindClassAndClearException(class_linker, self, descriptor, class_loader));
 
     if (entry.IsResolved()) {
       if (cls == nullptr) {
-        LOG(INFO) << "VerifierDeps: Could not resolve class " << descriptor;
+        *error_msg = "Could not resolve class " + descriptor;
         return false;
       } else if (entry.GetAccessFlags() != GetAccessFlags(cls.Get())) {
-        LOG(INFO) << "VerifierDeps: Unexpected access flags on class "
-                  << descriptor
-                  << std::hex
-                  << " (expected="
-                  << entry.GetAccessFlags()
-                  << ", actual="
-                  << GetAccessFlags(cls.Get()) << ")"
-                  << std::dec;
+        *error_msg = "Unexpected access flags on class " + descriptor
+            + " (expected=" + ToHex(entry.GetAccessFlags())
+            + ", actual=" + ToHex(GetAccessFlags(cls.Get())) + ")";
         return false;
       }
     } else if (cls != nullptr) {
-      LOG(INFO) << "VerifierDeps: Unexpected successful resolution of class " << descriptor;
+      *error_msg = "Unexpected successful resolution of class " + descriptor;
       return false;
     }
   }
@@ -960,7 +963,8 @@
 bool VerifierDeps::VerifyFields(Handle<mirror::ClassLoader> class_loader,
                                 const DexFile& dex_file,
                                 const std::set<FieldResolution>& fields,
-                                Thread* self) const {
+                                Thread* self,
+                                /* out */ std::string* error_msg) const {
   // Check recorded fields are resolved the same way, have the same recorded class,
   // and have the same recorded flags.
   ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
@@ -977,7 +981,7 @@
     ObjPtr<mirror::Class> cls = FindClassAndClearException(
         class_linker, self, expected_decl_klass.c_str(), class_loader);
     if (cls == nullptr) {
-      LOG(INFO) << "VerifierDeps: Could not resolve class " << expected_decl_klass;
+      *error_msg = "Could not resolve class " + expected_decl_klass;
       return false;
     }
     DCHECK(cls->IsResolved());
@@ -986,25 +990,25 @@
     if (entry.IsResolved()) {
       std::string temp;
       if (field == nullptr) {
-        LOG(INFO) << "VerifierDeps: Could not resolve field "
-                  << GetFieldDescription(dex_file, entry.GetDexFieldIndex());
+        *error_msg = "Could not resolve field " +
+            GetFieldDescription(dex_file, entry.GetDexFieldIndex());
         return false;
       } else if (expected_decl_klass != field->GetDeclaringClass()->GetDescriptor(&temp)) {
-        LOG(INFO) << "VerifierDeps: Unexpected declaring class for field resolution "
-                  << GetFieldDescription(dex_file, entry.GetDexFieldIndex())
-                  << " (expected=" << expected_decl_klass
-                  << ", actual=" << field->GetDeclaringClass()->GetDescriptor(&temp) << ")";
+        *error_msg = "Unexpected declaring class for field resolution "
+            + GetFieldDescription(dex_file, entry.GetDexFieldIndex())
+            + " (expected=" + expected_decl_klass
+            + ", actual=" + field->GetDeclaringClass()->GetDescriptor(&temp) + ")";
         return false;
       } else if (entry.GetAccessFlags() != GetAccessFlags(field)) {
-        LOG(INFO) << "VerifierDeps: Unexpected access flags for resolved field "
-                  << GetFieldDescription(dex_file, entry.GetDexFieldIndex())
-                  << std::hex << " (expected=" << entry.GetAccessFlags()
-                  << ", actual=" << GetAccessFlags(field) << ")" << std::dec;
+        *error_msg = "Unexpected access flags for resolved field "
+            + GetFieldDescription(dex_file, entry.GetDexFieldIndex())
+            + " (expected=" + ToHex(entry.GetAccessFlags())
+            + ", actual=" + ToHex(GetAccessFlags(field)) + ")";
         return false;
       }
     } else if (field != nullptr) {
-      LOG(INFO) << "VerifierDeps: Unexpected successful resolution of field "
-                << GetFieldDescription(dex_file, entry.GetDexFieldIndex());
+      *error_msg = "Unexpected successful resolution of field "
+          + GetFieldDescription(dex_file, entry.GetDexFieldIndex());
       return false;
     }
   }
@@ -1022,7 +1026,8 @@
 bool VerifierDeps::VerifyMethods(Handle<mirror::ClassLoader> class_loader,
                                  const DexFile& dex_file,
                                  const std::set<MethodResolution>& methods,
-                                 Thread* self) const {
+                                 Thread* self,
+                                 /* out */ std::string* error_msg) const {
   ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
   PointerSize pointer_size = class_linker->GetImagePointerSize();
 
@@ -1040,7 +1045,7 @@
     ObjPtr<mirror::Class> cls = FindClassAndClearException(
         class_linker, self, expected_decl_klass.c_str(), class_loader);
     if (cls == nullptr) {
-      LOG(INFO) << "VerifierDeps: Could not resolve class " << expected_decl_klass;
+      *error_msg = "Could not resolve class " + expected_decl_klass;
       return false;
     }
     DCHECK(cls->IsResolved());
@@ -1054,53 +1059,94 @@
     if (entry.IsResolved()) {
       std::string temp;
       if (method == nullptr) {
-        LOG(INFO) << "VerifierDeps: Could not resolve method "
-                  << GetMethodDescription(dex_file, entry.GetDexMethodIndex());
+        *error_msg = "Could not resolve method "
+            + GetMethodDescription(dex_file, entry.GetDexMethodIndex());
         return false;
       } else if (expected_decl_klass != method->GetDeclaringClass()->GetDescriptor(&temp)) {
-        LOG(INFO) << "VerifierDeps: Unexpected declaring class for method resolution "
-                  << GetMethodDescription(dex_file, entry.GetDexMethodIndex())
-                  << " (expected="
-                  << expected_decl_klass
-                  << ", actual="
-                  << method->GetDeclaringClass()->GetDescriptor(&temp)
-                  << ")";
+        *error_msg = "Unexpected declaring class for method resolution "
+            + GetMethodDescription(dex_file, entry.GetDexMethodIndex())
+            + " (expected=" + expected_decl_klass
+            + ", actual=" + method->GetDeclaringClass()->GetDescriptor(&temp) + ")";
         return false;
       } else if (entry.GetAccessFlags() != GetAccessFlags(method)) {
-        LOG(INFO) << "VerifierDeps: Unexpected access flags for resolved method resolution "
-                  << GetMethodDescription(dex_file, entry.GetDexMethodIndex())
-                  << std::hex
-                  << " (expected="
-                  << entry.GetAccessFlags()
-                  << ", actual="
-                  << GetAccessFlags(method) << ")"
-                  << std::dec;
+        *error_msg = "Unexpected access flags for resolved method resolution "
+            + GetMethodDescription(dex_file, entry.GetDexMethodIndex())
+            + " (expected=" + ToHex(entry.GetAccessFlags())
+            + ", actual=" + ToHex(GetAccessFlags(method)) + ")";
         return false;
       }
     } else if (method != nullptr) {
-      LOG(INFO) << "VerifierDeps: Unexpected successful resolution of method "
-                << GetMethodDescription(dex_file, entry.GetDexMethodIndex());
+      *error_msg = "Unexpected successful resolution of method "
+          + GetMethodDescription(dex_file, entry.GetDexMethodIndex());
       return false;
     }
   }
   return true;
 }
 
+bool VerifierDeps::VerifyInternalClasses(Handle<mirror::ClassLoader> class_loader,
+                                         const DexFile& dex_file,
+                                         const std::set<dex::TypeIndex>& unverified_classes,
+                                         Thread* self,
+                                         /* out */ std::string* error_msg) const {
+  ClassLinker* const class_linker = Runtime::Current()->GetClassLinker();
+
+  for (ClassAccessor accessor : dex_file.GetClasses()) {
+    std::string descriptor = accessor.GetDescriptor();
+    ObjPtr<mirror::Class> cls = FindClassAndClearException(class_linker,
+                                                           self,
+                                                           descriptor,
+                                                           class_loader);
+    if (UNLIKELY(cls == nullptr)) {
+      // Could not resolve class from the currently verified dex file.
+      // This can happen when the class fails to link. Check if this
+      // expected by looking in the `unverified_classes` set.
+      if (unverified_classes.find(accessor.GetClassDef().class_idx_) == unverified_classes.end()) {
+        *error_msg = "Failed to resolve internal class " + descriptor;
+        return false;
+      }
+      continue;
+    }
+
+    // Check that the class resolved into the same dex file. Otherwise there is
+    // a different class with the same descriptor somewhere in one of the parent
+    // class loaders.
+    if (&cls->GetDexFile() != &dex_file) {
+      *error_msg = "Class " + descriptor + " redefines a class in a parent class loader "
+          + "(dexFile expected=" + accessor.GetDexFile().GetLocation()
+          + ", actual=" + cls->GetDexFile().GetLocation() + ")";
+      return false;
+    }
+  }
+
+  return true;
+}
+
 bool VerifierDeps::VerifyDexFile(Handle<mirror::ClassLoader> class_loader,
                                  const DexFile& dex_file,
                                  const DexFileDeps& deps,
-                                 Thread* self) const {
-  bool result = VerifyAssignability(
-      class_loader, dex_file, deps.assignable_types_, /* expected_assignability= */ true, self);
-  result = result && VerifyAssignability(
-      class_loader, dex_file, deps.unassignable_types_, /* expected_assignability= */ false, self);
-
-  result = result && VerifyClasses(class_loader, dex_file, deps.classes_, self);
-  result = result && VerifyFields(class_loader, dex_file, deps.fields_, self);
-
-  result = result && VerifyMethods(class_loader, dex_file, deps.methods_, self);
-
-  return result;
+                                 Thread* self,
+                                 /* out */ std::string* error_msg) const {
+  return VerifyInternalClasses(class_loader,
+                               dex_file,
+                               deps.unverified_classes_,
+                               self,
+                               error_msg) &&
+         VerifyAssignability(class_loader,
+                             dex_file,
+                             deps.assignable_types_,
+                             /* expected_assignability= */ true,
+                             self,
+                             error_msg) &&
+         VerifyAssignability(class_loader,
+                             dex_file,
+                             deps.unassignable_types_,
+                             /* expected_assignability= */ false,
+                             self,
+                             error_msg) &&
+         VerifyClasses(class_loader, dex_file, deps.classes_, self, error_msg) &&
+         VerifyFields(class_loader, dex_file, deps.fields_, self, error_msg) &&
+         VerifyMethods(class_loader, dex_file, deps.methods_, self, error_msg);
 }
 
 }  // namespace verifier
diff --git a/runtime/verifier/verifier_deps.h b/runtime/verifier/verifier_deps.h
index dfd4a5c..fb4a4bf 100644
--- a/runtime/verifier/verifier_deps.h
+++ b/runtime/verifier/verifier_deps.h
@@ -63,7 +63,7 @@
 
   // Merge `other` into this `VerifierDeps`'. `other` and `this` must be for the
   // same set of dex files.
-  void MergeWith(const VerifierDeps& other, const std::vector<const DexFile*>& dex_files);
+  void MergeWith(std::unique_ptr<VerifierDeps> other, const std::vector<const DexFile*>& dex_files);
 
   // Record the verification status of the class at `type_idx`.
   static void MaybeRecordVerificationStatus(const DexFile& dex_file,
@@ -114,7 +114,9 @@
   void Dump(VariableIndentationOutputStream* vios) const;
 
   // Verify the encoded dependencies of this `VerifierDeps` are still valid.
-  bool ValidateDependencies(Handle<mirror::ClassLoader> class_loader, Thread* self) const
+  bool ValidateDependencies(Handle<mirror::ClassLoader> class_loader,
+                            Thread* self,
+                            /* out */ std::string* error_msg) const
       REQUIRES_SHARED(Locks::mutator_lock_);
 
   const std::set<dex::TypeIndex>& GetUnverifiedClasses(const DexFile& dex_file) const {
@@ -287,14 +289,31 @@
   bool VerifyDexFile(Handle<mirror::ClassLoader> class_loader,
                      const DexFile& dex_file,
                      const DexFileDeps& deps,
-                     Thread* self) const
+                     Thread* self,
+                     /* out */ std::string* error_msg) const
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
+  // Check that classes which are to be verified using these dependencies
+  // are not eclipsed by classes in parent class loaders, e.g. when vdex was
+  // created against SDK stubs and the app redefines a non-public class on
+  // boot classpath, or simply if a class is added during an OTA. In such cases,
+  // dependencies do not include the dependencies on the presumed-internal class
+  // and verification must fail.
+  // TODO(dbrazdil): Encode a set of redefined classes during full verification.
+  // If such class is found to be redefined at runtime, dependencies remain valid.
+  bool VerifyInternalClasses(Handle<mirror::ClassLoader> class_loader,
+                             const DexFile& dex_file,
+                             const std::set<dex::TypeIndex>& unverified_classes,
+                             Thread* self,
+                             /* out */ std::string* error_msg) const
       REQUIRES_SHARED(Locks::mutator_lock_);
 
   bool VerifyAssignability(Handle<mirror::ClassLoader> class_loader,
                            const DexFile& dex_file,
                            const std::set<TypeAssignability>& assignables,
                            bool expected_assignability,
-                           Thread* self) const
+                           Thread* self,
+                           /* out */ std::string* error_msg) const
       REQUIRES_SHARED(Locks::mutator_lock_);
 
   // Verify that the set of resolved classes at the point of creation
@@ -302,7 +321,8 @@
   bool VerifyClasses(Handle<mirror::ClassLoader> class_loader,
                      const DexFile& dex_file,
                      const std::set<ClassResolution>& classes,
-                     Thread* self) const
+                     Thread* self,
+                     /* out */ std::string* error_msg) const
       REQUIRES_SHARED(Locks::mutator_lock_);
 
   // Verify that the set of resolved fields at the point of creation
@@ -311,7 +331,8 @@
   bool VerifyFields(Handle<mirror::ClassLoader> class_loader,
                     const DexFile& dex_file,
                     const std::set<FieldResolution>& classes,
-                    Thread* self) const
+                    Thread* self,
+                    /* out */ std::string* error_msg) const
       REQUIRES_SHARED(Locks::mutator_lock_)
       REQUIRES(!Locks::verifier_deps_lock_);
 
@@ -321,7 +342,8 @@
   bool VerifyMethods(Handle<mirror::ClassLoader> class_loader,
                      const DexFile& dex_file,
                      const std::set<MethodResolution>& methods,
-                     Thread* self) const
+                     Thread* self,
+                     /* out */ std::string* error_msg) const
       REQUIRES_SHARED(Locks::mutator_lock_);
 
   // Map from DexFiles into dependencies collected from verification of their methods.
diff --git a/runtime/well_known_classes.cc b/runtime/well_known_classes.cc
index 955a455..19fbf63 100644
--- a/runtime/well_known_classes.cc
+++ b/runtime/well_known_classes.cc
@@ -92,6 +92,7 @@
 jmethodID WellKnownClasses::java_lang_ClassNotFoundException_init;
 jmethodID WellKnownClasses::java_lang_Daemons_start;
 jmethodID WellKnownClasses::java_lang_Daemons_stop;
+jmethodID WellKnownClasses::java_lang_Daemons_waitForDaemonStart;
 jmethodID WellKnownClasses::java_lang_Double_valueOf;
 jmethodID WellKnownClasses::java_lang_Float_valueOf;
 jmethodID WellKnownClasses::java_lang_Integer_valueOf;
@@ -132,6 +133,7 @@
 jfieldID WellKnownClasses::java_lang_Thread_name;
 jfieldID WellKnownClasses::java_lang_Thread_priority;
 jfieldID WellKnownClasses::java_lang_Thread_nativePeer;
+jfieldID WellKnownClasses::java_lang_Thread_systemDaemon;
 jfieldID WellKnownClasses::java_lang_Thread_unparkedBeforeStart;
 jfieldID WellKnownClasses::java_lang_ThreadGroup_groups;
 jfieldID WellKnownClasses::java_lang_ThreadGroup_ngroups;
@@ -351,6 +353,7 @@
 
   java_lang_Daemons_start = CacheMethod(env, java_lang_Daemons, true, "start", "()V");
   java_lang_Daemons_stop = CacheMethod(env, java_lang_Daemons, true, "stop", "()V");
+  java_lang_Daemons_waitForDaemonStart = CacheMethod(env, java_lang_Daemons, true, "waitForDaemonStart", "()V");
   java_lang_invoke_MethodHandles_lookup = CacheMethod(env, "java/lang/invoke/MethodHandles", true, "lookup", "()Ljava/lang/invoke/MethodHandles$Lookup;");
   java_lang_invoke_MethodHandles_Lookup_findConstructor = CacheMethod(env, "java/lang/invoke/MethodHandles$Lookup", false, "findConstructor", "(Ljava/lang/Class;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/MethodHandle;");
 
@@ -385,6 +388,7 @@
   java_lang_Thread_name = CacheField(env, java_lang_Thread, false, "name", "Ljava/lang/String;");
   java_lang_Thread_priority = CacheField(env, java_lang_Thread, false, "priority", "I");
   java_lang_Thread_nativePeer = CacheField(env, java_lang_Thread, false, "nativePeer", "J");
+  java_lang_Thread_systemDaemon = CacheField(env, java_lang_Thread, false, "systemDaemon", "Z");
   java_lang_Thread_unparkedBeforeStart = CacheField(env, java_lang_Thread, false, "unparkedBeforeStart", "Z");
   java_lang_ThreadGroup_groups = CacheField(env, java_lang_ThreadGroup, false, "groups", "[Ljava/lang/ThreadGroup;");
   java_lang_ThreadGroup_ngroups = CacheField(env, java_lang_ThreadGroup, false, "ngroups", "I");
diff --git a/runtime/well_known_classes.h b/runtime/well_known_classes.h
index 872b562..3c5144f 100644
--- a/runtime/well_known_classes.h
+++ b/runtime/well_known_classes.h
@@ -101,6 +101,7 @@
   static jmethodID java_lang_ClassNotFoundException_init;
   static jmethodID java_lang_Daemons_start;
   static jmethodID java_lang_Daemons_stop;
+  static jmethodID java_lang_Daemons_waitForDaemonStart;
   static jmethodID java_lang_Double_valueOf;
   static jmethodID java_lang_Float_valueOf;
   static jmethodID java_lang_Integer_valueOf;
@@ -141,6 +142,7 @@
   static jfieldID java_lang_Thread_name;
   static jfieldID java_lang_Thread_priority;
   static jfieldID java_lang_Thread_nativePeer;
+  static jfieldID java_lang_Thread_systemDaemon;
   static jfieldID java_lang_Thread_unparkedBeforeStart;
   static jfieldID java_lang_ThreadGroup_groups;
   static jfieldID java_lang_ThreadGroup_ngroups;
diff --git a/test/005-annotations/expected.txt b/test/005-annotations/expected.txt
index ee5b0c7..b537c8f 100644
--- a/test/005-annotations/expected.txt
+++ b/test/005-annotations/expected.txt
@@ -109,4 +109,4 @@
 
 Get annotation with missing class should not throw
 Got expected TypeNotPresentException
-Got expected NoSuchFieldError
+Got expected Error for renamed enum
diff --git a/test/005-annotations/src/android/test/anno/TestAnnotations.java b/test/005-annotations/src/android/test/anno/TestAnnotations.java
index 8ea8e8e..a3e32f9 100644
--- a/test/005-annotations/src/android/test/anno/TestAnnotations.java
+++ b/test/005-annotations/src/android/test/anno/TestAnnotations.java
@@ -17,6 +17,7 @@
 package android.test.anno;
 
 import java.lang.annotation.Annotation;
+import java.lang.annotation.AnnotationFormatError;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
@@ -241,8 +242,8 @@
                 Annotation[] annos = m.getDeclaredAnnotations();
                 System.out.println("  annotations on METH " + m + ":");
             }
-        } catch (NoSuchFieldError expected) {
-            System.out.println("Got expected NoSuchFieldError");
+        } catch (Error expected) {
+            System.out.println("Got expected Error for renamed enum");
         }
 
         // Test if annotations marked VISIBILITY_BUILD are visible to runtime in M and earlier.
diff --git a/test/719-dm-verify-redefinition/check b/test/719-dm-verify-redefinition/check
index b5003bd..9845eee 100644
--- a/test/719-dm-verify-redefinition/check
+++ b/test/719-dm-verify-redefinition/check
@@ -15,7 +15,7 @@
 # limitations under the License.
 
 # Search for the redefinition line and remove unnecessary tags.
-sed -e 's/^dex2oat[d]\?\(\|32\|64\)\ W.*\] Found redefinition of boot classes\. Not doing fast verification\./Found redefinition of boot classes\. Not doing fast verification\./g' "$2" > "$2.tmp1"
+sed -e 's/^dex2oat[d]\?\(\|32\|64\)\ W.*\] \(Fast verification failed: Class L[^;]*; redefines a class in a parent class loader\).*/\2/g' "$2" > "$2.tmp1"
 # Remove all other dex2oat/dalvikvm log lines.
 grep -v dex2oat "$2.tmp1" | grep -v dalvikvm >> "$2.tmp2"
 
diff --git a/test/719-dm-verify-redefinition/expected.txt b/test/719-dm-verify-redefinition/expected.txt
index 64fb4ea..e1ab7d1 100644
--- a/test/719-dm-verify-redefinition/expected.txt
+++ b/test/719-dm-verify-redefinition/expected.txt
@@ -1,3 +1,3 @@
-Found redefinition of boot classes. Not doing fast verification.
+Fast verification failed: Class Ljava/util/BitSet; redefines a class in a parent class loader
 Hello, world!
 Correct resolution of boot class.
diff --git a/test/VerifierDeps/Iface.smali b/test/VerifierDeps/Iface.smali
index 8607307..1ee2358 100644
--- a/test/VerifierDeps/Iface.smali
+++ b/test/VerifierDeps/Iface.smali
@@ -1,18 +1,16 @@
-# /*
-#  * Copyright (C) 2017 The Android Open Source Project
-#  *
-#  * Licensed under the Apache License, Version 2.0 (the "License");
-#  * you may not use this file except in compliance with the License.
-#  * You may obtain a copy of the License at
-#  *
-#  *      http://www.apache.org/licenses/LICENSE-2.0
-#  *
-#  * Unless required by applicable law or agreed to in writing, software
-#  * distributed under the License is distributed on an "AS IS" BASIS,
-#  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#  * See the License for the specific language governing permissions and
-#  * limitations under the License.
-#  */
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
 
 .class public abstract interface LIface;
 .super Ljava/lang/Object;
diff --git a/test/etc/run-test-jar b/test/etc/run-test-jar
index 660c971..2910920 100755
--- a/test/etc/run-test-jar
+++ b/test/etc/run-test-jar
@@ -1005,12 +1005,14 @@
     export ANDROID_DATA="$DEX_LOCATION"
     export ANDROID_ROOT="${ANDROID_ROOT}"
     export ANDROID_RUNTIME_ROOT="${ANDROID_RUNTIME_ROOT}"
-    export LD_LIBRARY_PATH="${ANDROID_ROOT}/${LIBRARY_DIRECTORY}:${ANDROID_ROOT}/${TEST_DIRECTORY}"
     if [ "$USE_ZIPAPEX" = "y" ]; then
       # Put the zipapex files in front of the ld-library-path
-      export LD_LIBRARY_PATH="${ANDROID_DATA}/zipapex/${LIBRARY_DIRECTORY}:${LD_LIBRARY_PATH}"
+      export LD_LIBRARY_PATH="${ANDROID_DATA}/zipapex/${LIBRARY_DIRECTORY}:${ANDROID_ROOT}/${TEST_DIRECTORY}"
+      export DYLD_LIBRARY_PATH="${ANDROID_DATA}/zipapex/${LIBRARY_DIRECTORY}:${ANDROID_ROOT}/${TEST_DIRECTORY}"
+    else
+      export LD_LIBRARY_PATH="${ANDROID_ROOT}/${LIBRARY_DIRECTORY}:${ANDROID_ROOT}/${TEST_DIRECTORY}"
+      export DYLD_LIBRARY_PATH="${ANDROID_ROOT}/${LIBRARY_DIRECTORY}:${ANDROID_ROOT}/${TEST_DIRECTORY}"
     fi
-    export DYLD_LIBRARY_PATH="${ANDROID_ROOT}/${LIBRARY_DIRECTORY}:${ANDROID_ROOT}/${TEST_DIRECTORY}"
     export PATH="$PATH:$BIN_DIR"
 
     # Temporarily disable address space layout randomization (ASLR).
diff --git a/tools/art_verifier/art_verifier.cc b/tools/art_verifier/art_verifier.cc
index 0ef6c06..b4aa678 100644
--- a/tools/art_verifier/art_verifier.cc
+++ b/tools/art_verifier/art_verifier.cc
@@ -92,28 +92,32 @@
  protected:
   using Base = CmdlineArgs;
 
-  ParseStatus ParseCustom(const StringPiece& option, std::string* error_msg) override {
+  ParseStatus ParseCustom(const char* raw_option,
+                          size_t raw_option_length,
+                          std::string* error_msg) override {
+    DCHECK_EQ(strlen(raw_option), raw_option_length);
     {
-      ParseStatus base_parse = Base::ParseCustom(option, error_msg);
+      ParseStatus base_parse = Base::ParseCustom(raw_option, raw_option_length, error_msg);
       if (base_parse != kParseUnknownArgument) {
         return base_parse;
       }
     }
 
-    if (option.starts_with("--dex-file=")) {
-      dex_filename_ = option.substr(strlen("--dex-file=")).data();
+    std::string_view option(raw_option, raw_option_length);
+    if (StartsWith(option, "--dex-file=")) {
+      dex_filename_ = raw_option + strlen("--dex-file=");
     } else if (option == "--dex-file-verifier") {
       dex_file_verifier_ = true;
     } else if (option == "--verbose") {
       method_verifier_verbose_ = true;
     } else if (option == "--verbose-debug") {
       method_verifier_verbose_debug_ = true;
-    } else if (option.starts_with("--repetitions=")) {
+    } else if (StartsWith(option, "--repetitions=")) {
       char* end;
-      repetitions_ = strtoul(option.substr(strlen("--repetitions=")).data(), &end, 10);
-    } else if (option.starts_with("--api-level=")) {
+      repetitions_ = strtoul(raw_option + strlen("--repetitions="), &end, 10);
+    } else if (StartsWith(option, "--api-level=")) {
       char* end;
-      api_level_ = strtoul(option.substr(strlen("--api-level=")).data(), &end, 10);
+      api_level_ = strtoul(raw_option + strlen("--api-level="), &end, 10);
     } else {
       return kParseUnknownArgument;
     }
diff --git a/tools/hiddenapi/hiddenapi.cc b/tools/hiddenapi/hiddenapi.cc
index 2692f68..141dd22 100644
--- a/tools/hiddenapi/hiddenapi.cc
+++ b/tools/hiddenapi/hiddenapi.cc
@@ -18,6 +18,8 @@
 #include <iostream>
 #include <map>
 #include <set>
+#include <string>
+#include <string_view>
 
 #include "android-base/stringprintf.h"
 #include "android-base/strings.h"
@@ -27,6 +29,7 @@
 #include "base/mem_map.h"
 #include "base/os.h"
 #include "base/stl_util.h"
+#include "base/string_view_cpp20.h"
 #include "base/unix_file/fd_file.h"
 #include "dex/art_dex_file_loader.h"
 #include "dex/class_accessor-inl.h"
@@ -887,45 +890,48 @@
     argc--;
 
     if (argc > 0) {
-      const StringPiece command(argv[0]);
+      const char* raw_command = argv[0];
+      const std::string_view command(raw_command);
       if (command == "encode") {
         for (int i = 1; i < argc; ++i) {
-          const StringPiece option(argv[i]);
-          if (option.starts_with("--input-dex=")) {
-            boot_dex_paths_.push_back(option.substr(strlen("--input-dex=")).ToString());
-          } else if (option.starts_with("--output-dex=")) {
-            output_dex_paths_.push_back(option.substr(strlen("--output-dex=")).ToString());
-          } else if (option.starts_with("--api-flags=")) {
-            api_flags_path_ = option.substr(strlen("--api-flags=")).ToString();
+          const char* raw_option = argv[i];
+          const std::string_view option(raw_option);
+          if (StartsWith(option, "--input-dex=")) {
+            boot_dex_paths_.push_back(std::string(option.substr(strlen("--input-dex="))));
+          } else if (StartsWith(option, "--output-dex=")) {
+            output_dex_paths_.push_back(std::string(option.substr(strlen("--output-dex="))));
+          } else if (StartsWith(option, "--api-flags=")) {
+            api_flags_path_ = std::string(option.substr(strlen("--api-flags=")));
           } else if (option == "--no-force-assign-all") {
             force_assign_all_ = false;
           } else {
-            Usage("Unknown argument '%s'", option.data());
+            Usage("Unknown argument '%s'", raw_option);
           }
         }
         return Command::kEncode;
       } else if (command == "list") {
         for (int i = 1; i < argc; ++i) {
-          const StringPiece option(argv[i]);
-          if (option.starts_with("--boot-dex=")) {
-            boot_dex_paths_.push_back(option.substr(strlen("--boot-dex=")).ToString());
-          } else if (option.starts_with("--public-stub-classpath=")) {
+          const char* raw_option = argv[i];
+          const std::string_view option(raw_option);
+          if (StartsWith(option, "--boot-dex=")) {
+            boot_dex_paths_.push_back(std::string(option.substr(strlen("--boot-dex="))));
+          } else if (StartsWith(option, "--public-stub-classpath=")) {
             stub_classpaths_.push_back(std::make_pair(
-                option.substr(strlen("--public-stub-classpath=")).ToString(),
+                std::string(option.substr(strlen("--public-stub-classpath="))),
                 ApiList::Whitelist()));
-          } else if (option.starts_with("--core-platform-stub-classpath=")) {
+          } else if (StartsWith(option, "--core-platform-stub-classpath=")) {
             stub_classpaths_.push_back(std::make_pair(
-                option.substr(strlen("--core-platform-stub-classpath=")).ToString(),
+                std::string(option.substr(strlen("--core-platform-stub-classpath="))),
                 ApiList::CorePlatformApi()));
-          } else if (option.starts_with("--out-api-flags=")) {
-            api_flags_path_ = option.substr(strlen("--out-api-flags=")).ToString();
+          } else if (StartsWith(option, "--out-api-flags=")) {
+            api_flags_path_ = std::string(option.substr(strlen("--out-api-flags=")));
           } else {
-            Usage("Unknown argument '%s'", option.data());
+            Usage("Unknown argument '%s'", raw_option);
           }
         }
         return Command::kList;
       } else {
-        Usage("Unknown command '%s'", command.data());
+        Usage("Unknown command '%s'", raw_command);
       }
     } else {
       Usage("No command specified");
diff --git a/tools/ti-fast/README.md b/tools/ti-fast/README.md
index a0a7dd7..c8cf180 100644
--- a/tools/ti-fast/README.md
+++ b/tools/ti-fast/README.md
@@ -84,10 +84,15 @@
 
 * `GarbageCollectionFinish`
 
+* `VMStart`
+
+* `VMInit`
+
+* `VMDeath`
+
 All other events cannot be listened for by this agent. Most of these missing
 events either require the use of other functions in order to be called
-(`FramePop`, `ObjectFree`, etc) or are only called once (`VMInit`, `VMDeath`,
-etc).
+(`FramePop`, `ObjectFree`, etc).
 
 #### ART
 >    `art -Xplugin:$ANDROID_HOST_OUT/lib64/libopenjdkjvmti.so '-agentpath:libtifast.so=MethodEntry' -cp tmp/java/helloworld.dex -Xint helloworld`
diff --git a/tools/ti-fast/tifast.cc b/tools/ti-fast/tifast.cc
index d02e549..8a6ac36 100644
--- a/tools/ti-fast/tifast.cc
+++ b/tools/ti-fast/tifast.cc
@@ -88,6 +88,9 @@
     fun(MonitorWaited, EVENT(MONITOR_WAITED), (jvmtiEnv* jvmti, JNIEnv* jni, jthread thread, jobject obj, jboolean b1), (jvmti, jni, jthreadContainer{.thread = thread}, obj, b1)) \
     fun(ResourceExhausted, EVENT(RESOURCE_EXHAUSTED), (jvmtiEnv* jvmti, JNIEnv* jni, jint i1, const void* cv, const char* cc), (jvmti, jni, i1, cv, cc)) \
     fun(VMObjectAlloc, EVENT(VM_OBJECT_ALLOC), (jvmtiEnv* jvmti, JNIEnv* jni, jthread thread, jobject obj, jclass klass, jlong l1), (jvmti, jni, jthreadContainer{.thread = thread}, obj, klass, jlongContainer{.val = l1})) \
+    fun(VMInit, EVENT(VM_INIT), (jvmtiEnv* jvmti, JNIEnv* jni, jthread thread), (jvmti, jni, jthreadContainer{.thread = thread})) \
+    fun(VMStart, EVENT(VM_START), (jvmtiEnv* jvmti, JNIEnv* jni), (jvmti, jni)) \
+    fun(VMDeath, EVENT(VM_DEATH), (jvmtiEnv* jvmti, JNIEnv* jni), (jvmti, jni)) \
 
 #define FOR_ALL_SUPPORTED_NO_JNI_EVENTS(fun) \
     fun(CompiledMethodLoad, EVENT(COMPILED_METHOD_LOAD), (jvmtiEnv* jvmti, jmethodID meth, jint i1, const void* cv1, jint i2, const jvmtiAddrLocationMap* alm, const void* cv2), (jvmti, meth, i1, cv1, i2, alm, cv2)) \